Connection refused: как проверить, где приложение слушает порт
Приложение запущено, ошибок старта не видно, а браузер сообщает Connection refused. Проверка процесса создаёт ложное ощущение, что сервер обязан отвечать. На деле клиент соединяется с конкретной парой адреса и порта по определённому протоколу. Работающий процесс может слушать совсем другую пару или ещё не открыть сетевой сокет.
Начните с точного адреса запроса: схема, имя узла, порт и место запуска клиента. Для http://localhost:8080 слово localhost относится к окружению клиента. Внутри контейнера оно указывает на сам контейнер. На ноутбуке администратора — на ноутбук. Это разные точки подключения даже при одинаковом тексте URL.
Проверим слушающий TCP-сокет
На Linux-узле, где должно работать приложение, выполните:
ss -ltnp 'sport = :8080'
Опция -l выбирает слушающие сокеты, -t ограничивает вывод TCP, -n оставляет числовые адреса, -p добавляет владельцев процессов, если позволяют права. Фильтр проверяет локальный порт 8080. Пустой результат означает отсутствие подходящего TCP-listener в текущем network namespace; это ещё не ответ о другом контейнере или пространстве имён.
Если строка есть, прочитайте столбец Local Address:Port. Адрес 127.0.0.1:8080 принимает подключения через IPv4 loopback. Он подходит локальному reverse proxy в том же сетевом окружении, но не прямому клиенту с другой машины. Адрес 0.0.0.0:8080 означает прослушивание всех локальных IPv4-адресов. Это адрес привязки, а не адрес, который следует вводить удалённому клиенту.
Для IPv6 возможны [::1]:8080 и [::]:8080. Приём IPv4 через IPv6-сокет зависит от настроек и опции IPV6_V6ONLY. Не делайте вывод о двойном стеке только по одной строке. При необходимости разделите вывод ss -4 и ss -6 и выполните запросы с явным выбором семейства адресов.
Сравним локальный и нужный клиентский путь
curl --noproxy '*' -v --connect-timeout 3 http://127.0.0.1:8080/health
curl --noproxy '*' -v --connect-timeout 3 http://192.0.2.10:8080/health
Адрес 192.0.2.10 здесь учебный: замените его реальным адресом интерфейса сервера. Первый запрос проверяет loopback, второй — выбранный адрес узла. --noproxy '*' исключает влияние HTTP-прокси из переменных окружения для этой проверки. В verbose-выводе видно, к какому адресу curl пытается подключиться и на каком шаге останавливается.
Если loopback отвечает, а адрес узла получает отказ, сравните результат с bind-адресом в ss. При listener только на 127.0.0.1 это ожидаемая картина. Дальнейшее действие зависит от архитектуры: настроить доступ через существующий reverse proxy или изменить bind приложения и правила доступа. Открывать прослушивание на всех интерфейсах без понимания требуемых клиентов необязательно.
Ответ HTTP 404 или 500 означает, что TCP-соединение уже состоялось и сервер отправил HTTP-ответ. Тогда исследуйте маршрут и приложение. Отказ при установлении соединения происходит раньше и не исправляется сменой URL-пути с /health на /.
Проверим имя и сетевое окружение
Если по IP соединение устанавливается, а по имени нет, посмотрите адрес, выбранный клиентом. Имя может указывать на другой узел или сначала разрешаться в IPv6. Для localhost полезны сравнения curl -4 и curl -6. Важно сохранить тот же порт и выполнять запрос из того же окружения, где наблюдалась ошибка.
У контейнерного приложения listener внутри контейнера и публикация порта на host — отдельные настройки. Например, bind на loopback внутри контейнера не равнозначен bind на loopback хоста. Проверяйте ss в нужном namespace и конфигурацию публикации порта соответствующей платформы. Сам факт наличия контейнерного процесса на host не показывает, куда доступен его порт.
Если listener на правильном адресе есть, но удалённый клиент получает отказ, продолжайте по пути клиента: адрес назначения, промежуточный proxy, правила firewall и публикация порта. Активный reject также способен привести к быстрому отказу. Timeout, в свою очередь, требует искать потерянные пакеты, фильтрацию или задержку; это другое наблюдение, хотя пользователь тоже видит неработающий сервис.
Как подтвердить исправление
Повторите запрос с той машины или из того контейнера, где он раньше ломался. Успешный локальный curl после изменения bind не проверяет удалённый путь. Зафиксируйте адрес listener и фактический адрес клиента из вывода. Так результат проверки будет говорить о доступности нужного сервиса, а не просто о существовании процесса с подходящим именем.
Ещё по теме
- Первый самостоятельный релиз сервиса: что проверить до и после обновления
- Linux, Docker или CI/CD: с чего продолжить обучение после первого приложения
Продолжить изучение
Хотите связать Linux, контейнеры и автоматический деплой в один рабочий процесс? Посмотрите программу «DevOps-инженер с нуля: полный курс».
Продолжить на практике
- DevOps-инженер с нуля: полный курс ↗
Практический курс по DevOps для начинающих: от настройки Linux и сетей до сборки контейнеров, автоматизации в GitLab CI/CD, развёртывания в Kubernetes, мониторинга и восстановления данных на собственном компьютере.
