Описание проблемы
Мое приложение Next.js имеет конечную точку API (/api/debug), предназначенную для отображения переменных среды и проверки подключения к базе данных. Эта конечная точка работает безупречно в моей локальной среде разработки (http://localhost:3000/api/debug?secret= ... opper-2024), показывая успешное подключение к базе данных.
Однако при развертывании в рабочей среде (пробовал как AWS Amplify, так и хостинг Node.js от Hostinger) подключение к базе данных завершается с ошибкой «Доступ запрещен».
Ответ локальной разработки (Успешно)
Код: Выделить всё
{"timestamp":"2026-02-16T11:34:32.621Z","environment":{"NODE_ENV":"development","MYSQL_HOST":"193.203.184.84","MYSQL_PORT":"3306","MYSQL_USER":"u635686762_toppersAdmin","MYSQL_PASSWORD":"Thi...re1","MYSQL_DATABASE":"u635686762_toppers_track","DATABASE_URL":"NOT SET","JWT_SECRET":"you...nly","NEXT_PUBLIC_API_URL":"http://localhost:3000","NEXT_PUBLIC_AUTH0_DOMAIN":"dev-4smfbxvy7ozq7ohk.us.auth0.com","NEXT_PUBLIC_AUTH0_CLIENT_ID":"4Q4FBQuwZh6kf32T9Y6nmIsAGqcxM5g5","AUTH0_SECRET":"d23...164","AUTH0_BASE_URL":"http://localhost:3000"},"database_test":{"status":"OK","message":"Database connection successful"},"warning":"This endpoint exposes sensitive information. Use only for debugging."}
Код: Выделить всё
{"timestamp": "2026-02-16T11:12:50.315Z","environment": {"NODE_ENV": "production","MYSQL_HOST": "89.116.133.144","MYSQL_PORT": "3306","MYSQL_USER": "u635686762_toppersAdmin","MYSQL_PASSWORD": "Thi...You","MYSQL_DATABASE": "u635686762_toppers_track","DATABASE_URL": "NOT SET","JWT_SECRET": "x9F...3XG","NEXT_PUBLIC_API_URL": "https://topperstrack.in","NEXT_PUBLIC_AUTH0_DOMAIN": "dev-4smfbxvy7ozq7ohk.us.auth0.com","NEXT_PUBLIC_AUTH0_CLIENT_ID": "4Q4FBQuwZh6kf32T9Y6nmIsAGqcxM5g5","AUTH0_SECRET": "d23...164","AUTH0_BASE_URL": "https://topperstrack.in"},"database_test": {"status": "ERROR","message": "Access denied for user 'u635686762_toppersAdmin'@'13.60.50.191' (using password: YES)","code": "ER_ACCESS_DENIED_ERROR","errno": 1045,"sqlState": "28000"},"warning": "This endpoint exposes sensitive information. Use only for debugging.}
Мои настройки
- Фронтенд/Бэкенд: Next.js (с использованием API-маршруты)
- База данных: MySQL, размещенная на бизнес-хостинге Hostinger.
- Библиотека базы данных: mysql2/promise
- Платформы развертывания: AWS Amplify, также пробовал хостинг Node.js от Hostinger.
- Переменные среды: Сохраняются безопасно на соответствующих платформах хостинга.
Код: Выделить всё
import mysql from 'mysql2/promise';
let pool: mysql.Pool;
function getPool() {
if (pool) return pool;
const dbUrl = process.env.DATABASE_URL;
if (dbUrl) {
console.log('🔌 Connecting using DATABASE_URL...');
pool = mysql.createPool(dbUrl);
return pool;
}
const host = process.env.MYSQL_HOST;
const user = process.env.MYSQL_USER || process.env.MYSQL_USERNAME;
const password = process.env.MYSQL_PASSWORD || process.env.MYSQL_PASS;
const database = process.env.MYSQL_DATABASE || process.env.MYSQL_DB;
const port = process.env.MYSQL_PORT ? parseInt(process.env.MYSQL_PORT, 10) : 3306;
const socketPath = process.env.MYSQL_SOCKET_PATH;
if (!host || !user || !password || !database) {
throw new Error('❌ Database config missing.');
}
const connectionConfig: mysql.PoolOptions = {
host,
user,
password,
database,
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0,
enableKeepAlive: true,
keepAliveInitialDelay: 0,
connectTimeout: 60000,
};
if (host === 'localhost' && socketPath) {
console.log(`🔌 Connecting via Local Socket: ${socketPath}`);
connectionConfig.socketPath = socketPath;
} else {
console.log(`🔌 Connecting via TCP to ${host}:${port}`);
connectionConfig.port = port;
}
pool = mysql.createPool(connectionConfig);
return pool;
}
// ... keep your existing export functions (query, queryOne, etc) below ...
export async function query(sql: string, values: any[] = []) {
const connection = await getPool().getConnection();
try {
const [rows] = await connection.execute(sql, values);
return rows;
} finally {
connection.release();
}
}
export async function queryOne(sql: string, values: any[] = []) {
const rows = await query(sql, values);
return (rows as any[])?.[0] || null;
}
export async function execute(sql: string, values: any[] = []) {
const connection = await getPool().getConnection();
try {
const [result] = await connection.execute(sql, values);
return result;
} finally {
connection.release();
}
}
export async function closePool() {
if (pool) {
await pool.end();
}
}
Код: Выделить всё
import { NextRequest, NextResponse } from 'next/server';
/**
* Debug endpoint to see what environment variables are loaded in production
* GET /api/debug
*/
export async function GET(request: NextRequest) {
// Security: Only allow in development or with a secret param
const secret = request.nextUrl.searchParams.get('secret');
const isProduction = process.env.NODE_ENV === 'production';
const debugSecret = 'debug-topper-2024'; // Change this to something secret
if (isProduction && secret !== debugSecret) {
return NextResponse.json(
{ error: 'Unauthorized. Add ?secret=your-secret to query parameter' },
{ status: 403 }
);
}
const envVars: Record = {};
// Show all relevant environment variables
const keysToCheck = [
'NODE_ENV',
'MYSQL_HOST',
'MYSQL_PORT',
'MYSQL_USER',
'MYSQL_PASSWORD',
'MYSQL_DATABASE',
'DATABASE_URL',
'JWT_SECRET',
'NEXT_PUBLIC_API_URL',
'NEXT_PUBLIC_AUTH0_DOMAIN',
'NEXT_PUBLIC_AUTH0_CLIENT_ID',
'AUTH0_SECRET',
'AUTH0_BASE_URL',
];
keysToCheck.forEach((key) => {
const value = process.env[key];
if (!value) {
envVars[key] = 'NOT SET';
} else if (key.includes('PASSWORD') || key.includes('SECRET')) {
// Mask sensitive values, but show first 3 and last 3 chars
const masked = value.length > 6
? `${value.substring(0, 3)}...${value.substring(value.length - 3)}`
: '***';
envVars[key] = masked;
} else {
envVars[key] = value;
}
});
// Try database connection
let dbTest: Record = { status: 'SKIPPED' };
try {
const { query } = await import('@/lib/mysql-db');
const result = await query('SELECT 1 as test');
dbTest = {
status: 'OK',
message: 'Database connection successful'
};
} catch (error: any) {
// Include structured error info for debugging (code/errno/message)
dbTest = {
status: 'ERROR',
message: error instanceof Error ? error.message : String(error),
code: error?.code || null,
errno: error?.errno || null,
sqlState: error?.sqlState || null,
};
// Also print to server logs for deeper inspection
console.error('DB debug error:', {
message: dbTest.message,
code: dbTest.code,
errno: dbTest.errno,
sqlState: dbTest.sqlState,
});
}
return NextResponse.json(
{
timestamp: new Date().toISOString(),
environment: envVars,
database_test: dbTest,
warning: 'This endpoint exposes sensitive information. Use only for debugging.'
},
{ status: 200 }
);
}
Код: Выделить всё
MYSQL_HOST=89.116.133.144
MYSQL_PORT=3306
MYSQL_USER=u635686762_toppersAdmin
MYSQL_PASSWORD=ThisIsYou$here1 (masked in output as Thi...You)
MYSQL_DATABASE=u635686762_toppers_track
1. Успешное локальное подключение: я могу подключиться к базе данных со своей локальной машины разработки, используя те же учетные данные.
2. Успешное подключение HeidiSQL: я могу подключиться к базе данных с помощью HeidiSQL (или аналогичного клиентского программного обеспечения) с другого компьютера, используя те же учетные данные.
3. Hostinger Remote MySQL: В моей Hostinger cPanel включен «Удаленный MySQL», а пользователь u635686762_toppersAdmin явно настроен на разрешение подключений с любого хоста (%).
4. Брандмауэр: настройки брандмауэра Hostinger по умолчанию активны, но подстановочный знак % должен обходить большинство ограничений на основе IP для пользователя MySQL.
5. Точность пароля: я трижды проверил, что переменная среды MYSQL_PASSWORD в рабочей среде побайтно идентична фактическому паролю базы данных. Я даже жестко запрограммировал учетные данные непосредственно в mysql-db.ts, чтобы исключить проблемы с анализом переменных среды (см. код ниже), но все равно столкнулся с той же ошибкой «Отказано в доступе».
6. Поддержка хостинга: Я связался со службой поддержки хостинга, они просто предлагают один за другим блоги (которые я прочитал и выполнил все данные им инструкции), которые не беспокоили и не проверяли все, и они говорят, что в моем коде есть проблема, и не получили от них поддержки после того, как попробовали 5-6 раз.
**Конкретная проблема: **
Ошибка: доступ запрещен для пользователя 'u635686762_toppersAdmin'@'13.60.50.191' указывает, что сервер MySQL видит попытку подключения, исходящую с IP-адреса 13.60.50.191. Этот IP-адрес принадлежит AWS (предположительно экземпляру Amplify). Несмотря на то, что пользователь u635686762_toppersAdmin настроен для % (любой хост), соединение с этого конкретного IP-адреса AWS отклоняется.
**Мой вопрос: **
Учитывая, что удаленный доступ включен для % и учетные данные работают с моего локального компьютера и других клиентов, почему мое приложение Node.js (развернуто на AWS Amplify или собственном хостинге Node.js компании Hostinger) отказ в доступе!
Что может блокировать этот конкретный IP-адрес или соединение, исходящее от облачного провайдера, такого как AWS, даже с подстановочными разрешениями хоста для пользователя MySQL? Существуют ли дополнительные сетевые конфигурации, специфичные для Hostinger, или нюансы привилегий пользователя MySQL, которые я мог упустить?
Будем очень признательны за любую информацию или дальнейшие шаги по отладке!
Подробнее здесь: https://stackoverflow.com/questions/798 ... ostinger-b
Мобильная версия