Диагностика отказа соединения PHP

Раздел: Администрирование 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 mysql

Php 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.

Ошибка соединения: Connection refused в PHP - comments

En
Php connection refused (php)