Диагностика отказа соединения PHP
Диагностика и устранение ошибки Connection refused в PHP
Ошибка Connection refused возникает, когда PHP пытается установить соединение с сервером (базой данных, сокетом, другим приложением), но целевой хост отклоняет подключение. Причины: сервер не запущен, порт не прослушивается, брандмауэр блокирует трафик, неверный адрес или порт.
Как проверить, что целевой сервер запущен и слушает порт?
Самое эффективное решение - проверить состояние службы на стороне сервера. Для MySQL/MariaDB:
# Проверка, запущен ли процесс
sudo systemctl status mysql
# или
ps aux | grep mysql
# Просмотр прослушиваемых портов (Linux)
sudo netstat -tlnp | grep 3306
# или
sudo ss -tlnp | grep mysqlPhp connection refused (ошибка соединения: connection refused в php)
Если порт не отображается, сервер не слушает соединения. Перезапустите службу:
sudo systemctl restart mysql
Типичная ошибка:
Сервер запущен, но слушает только loopback (127.0.0.1), а PHP подключается к внешнему IP. Решение: изменить bind-address в конфигурации MySQL на 0.0.0.0 или конкретный IP.
Как проверить, что PHP использует правильные хост и порт?
Необходимо сверить параметры подключения в коде с фактическими данными сервера.
// Пример PDO с проверкой
$dsn = 'mysql:host=192.168.1.10;port=3306;dbname=test;charset=utf8';
try {
$pdo = new PDO($dsn, 'user', 'pass');
} catch (PDOException $e) {
echo 'Ошибка: ' . $e->getMessage();
}
Проблема:
Если порт указан неверно (например, 3307 вместо 3306) или хост недоступен, PHP получает Connection refused. Проверьте через telnet или nc.
telnet 192.168.1.10 3306
# если соединение закрывается сразу - порт не слушается
Как настроить брандмауэр для разрешения подключений?
Брандмауэр на стороне сервера может блокировать входящие соединения. Для iptables:
# Разрешить входящий TCP на порт 3306 для сети 192.168.1.0/24
sudo iptables -A INPUT -p tcp --dport 3306 -s 192.168.1.0/24 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 3306 -j DROP # остальные блокируются
Для UFW:
sudo ufw allow from 192.168.1.0/24 to any port 3306 proto tcp
Распространенная проблема:
После изменения правил не произошло перезагрузки. Выполните sudo iptables-save или sudo ufw reload. Проверка: sudo iptables -L -n -v.
Как обработать ошибку с повторным подключением и таймаутом?
Иногда временная недоступность сервера решается повторными попытками.
function connectWithRetry($dsn, $user, $pass, $maxRetries = 3) {
$attempt = 0;
while ($attempt < $maxRetries) {
try {
$pdo = new PDO($dsn, $user, $pass, [PDO::ATTR_TIMEOUT => 5]);
return $pdo;
} catch (PDOException $e) {
$attempt++;
if ($attempt === $maxRetries) {
throw $e;
}
sleep(1); // пауза 1 секунда
}
}
}
Ошибка:
Использование бесконечного цикла без лимита приводит к зависанию скрипта. Всегда задавайте максимальное число попыток.
Как использовать fsockopen для диагностики соединения?
Функция fsockopen позволяет проверить доступность порта без подключения к конкретной службе.
$host = '192.168.1.10';
$port = 3306;
$timeout = 5;
$fp = @fsockopen($host, $port, $errno, $errstr, $timeout);
if (!$fp) {
echo "Не удалось соединиться: $errstr ($errno)";
} else {
echo "Порт открыт";
fclose($fp);
}
Проблема:
Если брандмауэр блокирует ICMP, fsockopen может не получить ответа, хотя порт открыт. Используйте также проверки с помощью stream_socket_client с флагом STREAM_CLIENT_CONNECT.
Как разрешить удалённые подключения к MySQL для PHP?
По умолчанию MySQL разрешает подключения только с localhost. Необходимо настроить пользователя с хостом '%' или конкретным IP.
CREATE USER 'user'@'192.168.1.%' IDENTIFIED BY 'password';
GRANT ALL PRIVILEGES ON *.* TO 'user'@'192.168.1.%';
FLUSH PRIVILEGES;
Ошибка:
Забыли изменить bind-address в my.cnf. Без этого MySQL слушает только 127.0.0.1. После изменений перезагрузите сервер.
Расширенные примеры диагностики и обработки Connection refused
Ниже приведены более сложные сценарии с пояснением каждого шага и ожидаемым результатом.
Пример 1. Использование stream_socket_client с таймаутом и флагами
$host = 'tcp://192.168.1.10:3306';
$timeout = 3;
$context = stream_context_create(['socket' => ['bindto' => '0:0']]);
$errno = 0;
$errstr = '';
$socket = @stream_socket_client($host, $errno, $errstr, $timeout, STREAM_CLIENT_CONNECT, $context);
if ($socket) {
echo "Соединение установлено";
fwrite($socket, "\n"); // пример отправки данных
$response = fread($socket, 1024);
echo "Ответ: " . bin2hex($response);
fclose($socket);
} else {
echo "Не удалось подключиться: $errstr ($errno)";
// возможен код ошибки 111 - Connection refused
}
Результат при работающем MySQL: "Соединение установлено" и hex-дамп приветствия MySQL. При отказе: "Не удалось подключиться: Connection refused (111)".
В данном примере используется stream_context_create чтобы указать сетевой интерфейс для исходящего соединения (все интерфейсы). Флаг STREAM_CLIENT_CONNECT обязателен для клиентского сокета.
Пример 2. Проверка нескольких портов на одном хосте
$host = '192.168.1.10';
$ports = [3306, 3307, 5432, 6379];
$results = [];
foreach ($ports as $port) {
$fp = @fsockopen($host, $port, $errno, $errstr, 2);
if ($fp) {
$results[$port] = 'Открыт';
fclose($fp);
} else {
$results[$port] = "Закрыт или отказано ($errno)";
}
}
print_r($results);
Array
(
[3306] => Открыт
[3307] => Закрыт или отказано (111)
[5432] => Открыт
[6379] => Открыт
)
Позволяет быстро определить, какие службы доступны. Connection refused (код 111) означает, что порт не прослушивается.
Пример 3. PDO с настройками таймаута и сохранением ошибки в лог
$options = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_TIMEOUT => 3,
PDO::MYSQL_ATTR_CONNECT_TIMEOUT => 3
];
try {
$pdo = new PDO('mysql:host=192.168.1.10;port=3306;dbname=test', 'user', 'pass', $options);
} catch (PDOException $e) {
error_log("PDO Connection failed: " . $e->getMessage());
// Дополнительная диагностика через fsockopen
$fp = @fsockopen('192.168.1.10', 3306, $errno, $errstr, 2);
if (!$fp) {
error_log("fsockopen also failed: $errstr ($errno)");
}
throw $e; // или другая логика
}
При ошибке в лог попадут оба сообщения: PDOException и результат fsockopen. Это помогает различить проблему на уровне PDO (ошибка аутентификации) и на уровне сети (отказ в соединении).
Пример 4. Использование socket_create для низкоуровневой диагностики
$sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
socket_set_option($sock, SOL_SOCKET, SO_RCVTIMEO, ['sec' => 2, 'usec' => 0]);
socket_set_option($sock, SOL_SOCKET, SO_SNDTIMEO, ['sec' => 2, 'usec' => 0]);
$result = @socket_connect($sock, '192.168.1.10', 3306);
if ($result) {
echo "Соединение установлено";
socket_close($sock);
} else {
$errno = socket_last_error($sock);
$errmsg = socket_strerror($errno);
echo "Ошибка $errno: $errmsg";
socket_clear_error($sock);
}
Ошибка 111: Connection refused. Этот метод дает полный контроль над сокетом, но требует аккуратности с обработкой ошибок.
Пример 5. Проверка доступности порта с помощью exec и netcat
$host = '192.168.1.10';
$port = 3306;
$output = [];
$returnVar = 0;
exec("nc -zv $host $port 2>&1", $output, $returnVar);
if ($returnVar === 0) {
echo "Порт $port открыт";
} else {
echo "Порт $port недоступен: " . implode("\n", $output);
}
Вывод при отказе: "Connection to 192.168.1.10 port 3306 [tcp/mysql] succeeded!" (успех). При неуспехе: "nc: connect to 192.168.1.10 port 3306 (tcp) failed: Connection refused".
Метод требует установленной утилиты netcat, но даёт быструю проверку из PHP.