Connection refused и ERR_CONNECTION_REFUSED: что значит и как исправить

опубликовано
читать
≈ 10 мин
автор
среда и версия
Debian 12: curl 7.88, Python 3.11 и requests 2.28, OpenSSH 9.2, psql 15, iptables 1.8.9; Node.js 22; Docker 29
содержание

Connection refused переводится как «в соединении отказано». Программа постучалась на адрес и порт, а компьютер по этому адресу сразу ответил отказом: на этом порту никто не слушает. Сеть при этом работает, имя сайта нашлось, пакет дошёл — не нашлось программы, которая приняла бы соединение. Поэтому главный вопрос всегда один: запущен ли сервер и на каком адресе и порту он ждёт.

Одна и та же ошибка выглядит в разных программах по-разному:

  • Chrome: «Не удается получить доступ к сайту. Сайт localhost не позволяет установить соединение» и код ERR_CONNECTION_REFUSED;
  • curl: curl: (7) Failed to connect … Couldn't connect to server;
  • Python: [Errno 111] Connection refused;
  • Node.js: connect ECONNREFUSED 127.0.0.1:3000;
  • PostgreSQL: Connection refused и вопрос «Is the server running on that host…»;
  • SSH: ssh: connect to host … port 22: Connection refused;
  • Java и Minecraft: java.net.ConnectException: Connection refused или Connection refused: getsockopt.

Ниже — как за пару команд найти причину. Весь вывод снят на чистых контейнерах Debian 12: server — машина с сервером, laptop — машина, которая к нему подключается.

Веб / 01

Три ответа на попытку соединиться

Три ответа на попытку соединитьсяСлева клиент curl на машине laptop, справа машина server и её порт 8000. Клиент трижды отправляет запрос на соединение SYN. Случай 01: порт слушает процесс — сервер отвечает SYN-ACK, соединение есть, приходит HTTP/1.0 200 OK. Случай 02: на порту никто не слушает или файрвол делает REJECT — сервер сразу отвечает RST, клиент пишет Connection refused через 0 мс. Случай 03: файрвол делает DROP или адрес недоступен — ответа нет, через 5001 мс клиент пишет Timeout.laptopservercurlшлёт SYNпорт 8000слушаетзакрытмолчит01 процесс слушает портSYN-ACK → 200 OK02 никто не слушает или REJECTRST → Connection refused, 0 ms03 DROP или адрес недоступентишина → Timeout, 5001 ms
Refused приходит мгновенно: машина ответила, что порт закрыт, значит до неё дошло. Timeout — это тишина: пакеты теряются по дороге или их отбрасывает файрвол.

Refused или timed out: что говорит скорость ответа

Подробный режим curl показывает, что именно произошло. Запрос к порту 3000, на котором ничего не запущено:

curl -v http://localhost:3000
*   Trying 127.0.0.1:3000...
* connect to 127.0.0.1 port 3000 failed: Connection refused
*   Trying [::1]:3000...
* connect to ::1 port 3000 failed: Connection refused
* Failed to connect to localhost port 3000 after 0 ms: Couldn't connect to server
* Closing connection 0
curl: (7) Failed to connect to localhost port 3000 after 0 ms: Couldn't connect to server

localhost у curl превратился в два адреса, IPv4 и IPv6, и оба ответили отказом за 0 мс. В обычном выводе curl 7.88 пишет только Couldn't connect to server, а слова Connection refused видны с ключом -v.

Отказ — это ответ. По протоколу TCP машина, у которой на порту никого нет, сразу возвращает пакет RST, «сброс». Таймаут выглядит иначе: ответа нет вовсе, и клиент ждёт, пока не кончится терпение. Адрес 203.0.113.10 взят из диапазона для документации, там никто не отвечает:

curl --connect-timeout 5 http://203.0.113.10
curl: (28) Failed to connect to 203.0.113.10 port 80 after 5001 ms: Timeout was reached

Отсюда правило. Refused — до машины ты достучался, проблема в программе на ней: не запущена, слушает другой порт или другой адрес. Timed out — проблема по дороге: неверный адрес, нет маршрута, файрвол молча отбрасывает пакеты. Как запрос вообще добирается до сервера, рассказано в материале о том, что происходит, когда вводишь адрес сайта.

Шаг 1. Слушает ли кто-нибудь порт

На машине с сервером список портов, которые ждут подключений, показывает ss -ltn: l — только слушающие, t — TCP, n — номера вместо имён. Пока сервер не запущен, список пуст:

ss -ltn
State Recv-Q Send-Q Local Address:Port Peer Address:PortProcess

Запустим в отдельном терминале простой веб-сервер Python на порту 8000:

python3 -m http.server 8000 --bind 127.0.0.1

И посмотрим снова. Ключ p добавляет имя процесса:

ss -ltnp
State  Recv-Q Send-Q Local Address:Port Peer Address:PortProcess
LISTEN 0      5          127.0.0.1:8000      0.0.0.0:*    users:(("python3",pid=32,fd=3))

Порт 8000 слушает python3. Запрос на этот порт проходит, на соседний — нет:

curl -sI http://localhost:8000 | head -1
curl http://localhost:8080
HTTP/1.0 200 OK
curl: (7) Failed to connect to localhost port 8080 after 0 ms: Couldn't connect to server

Самая частая причина отказа на своём компьютере — одно из двух. Сервер не запущен или упал при старте: посмотри на терминал, где ты его запускал. Или порт в адресе не тот, что в ss: приложение могло стартовать на 5173, 8000 или 8080, а в браузере набран 3000. В Windows тот же список даёт netstat -ano.

Шаг 2. На каком адресе: 127.0.0.1 или 0.0.0.0

В выводе ss важен не только порт, но и адрес перед ним. 127.0.0.1 значит, что сервер принимает подключения только с этой же машины. Попытка зайти с другого компьютера получает отказ, хотя сервер работает:

curl http://server:8000
curl: (7) Failed to connect to server port 8000 after 0 ms: Couldn't connect to server

Эта команда запущена на laptop, а сервер на server слушает только 127.0.0.1. Чтобы принимать подключения с любого адреса, сервер запускают на 0.0.0.0. Останови его в его терминале через Ctrl+C и запусти снова:

python3 -m http.server 8000 --bind 0.0.0.0
ss -ltnp
State  Recv-Q Send-Q Local Address:Port Peer Address:PortProcess
LISTEN 0      5            0.0.0.0:8000      0.0.0.0:*    users:(("python3",pid=87,fd=3))

Теперь с laptop тот же запрос проходит:

curl -sI http://server:8000 | head -1
HTTP/1.0 200 OK

Многие серверы разработки по умолчанию слушают только 127.0.0.1: так безопаснее. Открыть их для сети просит отдельный флаг — flask run --host 0.0.0.0, uvicorn main:app --host 0.0.0.0, vite --host, python manage.py runserver 0.0.0.0:8000. Делай это только там, где сервер и правда должен быть виден из сети.

Docker: Connection reset вместо refused

В контейнере та же ошибка с адресом даёт другой текст. Опубликуем порт 8000 контейнера на порт 8080 компьютера, а сервер внутри запустим на 127.0.0.1:

docker run -d --name web -p 127.0.0.1:8080:8000 app python3 -m http.server 8000 --bind 127.0.0.1

Docker напечатает идентификатор контейнера. Теперь запрос:

curl http://localhost:8080
curl: (56) Recv failure: Connection reset by peer

Порт 8080 на компьютере слушает сам Docker, поэтому отказа нет: соединение принимается и тут же рвётся. Внутри контейнера 127.0.0.1 — это сам контейнер, а Docker приходит к приложению снаружи, через сетевой интерфейс контейнера. Пересоздадим контейнер с --bind 0.0.0.0:

docker rm -f web
docker run -d --name web -p 127.0.0.1:8080:8000 app python3 -m http.server 8000 --bind 0.0.0.0

Теперь запрос проходит:

curl -sI http://localhost:8080 | head -1
HTTP/1.0 200 OK

Ещё две ошибки с Docker дают честный Connection refused. Первая — порт не опубликован: без -p на компьютере его никто не слушает. Вторая — один контейнер обращается к другому по localhost: внутри контейнера это он сам, а к соседу обращаются по имени сервиса из docker-compose.yml, например db:5432.

Браузер: ERR_CONNECTION_REFUSED

Chrome пишет «Сайт localhost не позволяет установить соединение» и ниже код ERR_CONNECTION_REFUSED. Смысл тот же: браузер постучался на порт, и ему отказали.

Если это localhost или 127.0.0.1, иди по шагам выше: запущен ли сервер разработки, совпадает ли порт, не упал ли он с ошибкой в терминале. Если в коде страницы запрос к API заканчивается этой ошибкой, в JavaScript она превращается в TypeError: Failed to fetch.

Если так не открывается чужой сайт, сначала проверь его с телефона через мобильный интернет. Открывается — значит, причина на твоём компьютере. Например, в файле hosts имя сайта направлено на 127.0.0.1, где никто не слушает, или браузер ходит через выключенный прокси или VPN. Не открывается нигде — сайт лежит на своей стороне, и остаётся ждать.

Python, Node.js, PostgreSQL, SSH: тот же отказ своими словами

Python. Библиотека requests прячет отказ в длинную цепочку исключений, последняя строка которой выглядит так:

python3 -c "import requests; requests.get('http://localhost:3000')" 2>&1 | tail -1
requests.exceptions.ConnectionError: HTTPConnectionPool(host='localhost', port=3000): Max retries exceeded with url: / (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object at 0x7f5326f56290>: Failed to establish a new connection: [Errno 111] Connection refused'))

Errno 111 — это номер ошибки ECONNREFUSED в Linux. «Max retries exceeded» сбивает с толку: никаких повторов не было, запрос не прошёл с первой попытки. Как поймать такое исключение в коде, разобрано в статье про try/except.

Node.js. fetch выбрасывает общее TypeError: fetch failed, а настоящая причина лежит в поле cause:

const res = await fetch('http://localhost:3000/api/users')
console.log(res.status)
TypeError: fetch failed
    at node:internal/deps/undici/undici:15456:13
    at process.processTicksAndRejections (node:internal/process/task_queues:103:5)
    at async file:///home/node/app/fetch.mjs:1:13 {
  [cause]: Error: connect ECONNREFUSED 127.0.0.1:3000
      at TCPConnectWrap.afterConnect [as oncomplete] (node:net:1638:16) {
    errno: -111,
    code: 'ECONNREFUSED',
    syscall: 'connect',
    address: '127.0.0.1',
    port: 3000
  }
}

PostgreSQL. Клиент psql перебирает оба адреса localhost и сам подсказывает, что проверить:

psql -h localhost -U postgres
psql: error: connection to server at "localhost" (::1), port 5432 failed: Connection refused
	Is the server running on that host and accepting TCP/IP connections?
connection to server at "localhost" (127.0.0.1), port 5432 failed: Connection refused
	Is the server running on that host and accepting TCP/IP connections?

Проверь, запущена ли служба (systemctl status postgresql, подробнее в статье про systemctl), на каком порту она работает и что записано в listen_addresses в postgresql.conf. По умолчанию там localhost, поэтому с другой машины будет отказ, пока не впишешь нужный адрес. База в Docker должна быть опубликована через -p 5432:5432.

SSH. На сервере не запущен sshd, или он слушает нестандартный порт:

ssh localhost
ssh: connect to host localhost port 22: Connection refused

Остальные ошибки входа по SSH и как подключиться на другой порт, разобраны в статье про SSH.

Файрвол: REJECT отвечает отказом, DROP молчит

Если ss показывает, что сервер слушает 0.0.0.0, а с другой машины всё равно отказ, посмотри на файрвол. Правило REJECT отвечает на попытку соединиться отказом — снаружи это неотличимо от закрытого порта. На server от root:

iptables -A INPUT -p tcp --dport 8000 -j REJECT

С laptop:

curl -v http://server:8000 2>&1 | grep -E "connect to|curl:"
* connect to 172.17.0.9 port 8000 failed: Connection refused
* Failed to connect to server port 8000 after 0 ms: Couldn't connect to server
curl: (7) Failed to connect to server port 8000 after 0 ms: Couldn't connect to server

Правило DROP молча выбрасывает пакеты, и тот же запрос превращается в таймаут:

iptables -R INPUT 1 -p tcp --dport 8000 -j DROP
curl --connect-timeout 5 http://server:8000
curl: (28) Failed to connect to server port 8000 after 5001 ms: Timeout was reached

На сервере с ufw правила смотрят командой sudo ufw status, открывают порт — sudo ufw allow 8000/tcp. У облачных серверов есть ещё внешний файрвол в панели провайдера, обычно он называется группой безопасности.

Ошибки, на которых спотыкаются

localhost внутри контейнера. Для контейнера localhost — это он сам, а не твой компьютер и не соседний контейнер. Отсюда отказ при подключении к базе по localhost:5432 из контейнера приложения.

Сервер упал, а терминал закрыт. Команда запуска завершилась с ошибкой, окно свернуто, и кажется, что сервер работает. ss -ltnp за секунду покажет, что порт никто не слушает.

Порт открыли в файрволе, а сервер слушает 127.0.0.1. Правило файрвола не поможет: подключение снаружи всё равно придёт на адрес, который сервер не слушает.

Таймаут лечат как отказ. Перезапуск сервера не поможет, если до машины не доходят пакеты. Сначала посмотри, сколько ждала ошибка: 0 мс — это отказ, секунды — это таймаут.

Частые вопросы

Как переводится Connection refused

«В соединении отказано», или «соединение отклонено». Компьютер по нужному адресу ответил, что на этом порту никто не принимает подключения.

Чем Connection refused отличается от Connection timed out

Refused — быстрый явный отказ: машина доступна, но порт закрыт или его закрывает правило REJECT. Timed out — ответа нет вообще: неверный адрес, нет маршрута или файрвол с правилом DROP.

Что значит ECONNREFUSED

Это код той же ошибки в системе: connect() не нашёл никого, кто слушал бы удалённый адрес и порт. Так его показывают Node.js и программы на нём. Строка вроде connect ECONNREFUSED 127.0.0.1:5432 значит: на этой машине нет PostgreSQL на порту 5432, или он не принимает TCP-подключения.

Что значит Connection refused: getsockopt в Minecraft

Игра не смогла подключиться к серверу по указанному адресу и порту: сервер выключен, адрес или порт введён с ошибкой, или порт закрыт на стороне сервера. Если сервер свой, проверь, запущен ли он и совпадает ли порт с server-port в его настройках.

Источники

Выжимки статей в Telegram: одна ловушка, код с выводом и ссылка на полный разбор. t.me/koddo_ru