ConnectionRefusedError: [Errno 111] Connection refused
1 · The lesson
readWhat this error means
Your code reached the machine, and the machine said no. Something answered at that address — the network is fine, DNS resolved, packets arrived — but nothing is listening on that port, so the operating system on the other end refused the connection immediately.
This is the friendliest network failure you can get. A refusal is instant and unambiguous, unlike a timeout, which means nobody answered at all.
When you see it
Traceback (most recent call last): File "app.py", line 12, in <module> conn = psycopg.connect("postgresql://localhost:5432/shop") ConnectionRefusedError: [Errno 111] Connection refused
Through requests, it arrives wrapped:
requests.exceptions.ConnectionError: HTTPConnectionPool(host='localhost', port=8000): Max retries exceeded with url: /api/health (Caused by NewConnectionError('<urllib3.connection.HTTPConnection object>: Failed to establish a new connection: [Errno 111] Connection refused'))
On Windows the number differs — [WinError 10061] — with the same meaning.
Why it happens
Four causes, in the order they actually occur:
1. The service isn't running. Postgres, Redis, or your own dev server is stopped or crashed.
2. It's running on a different port. You connected to 5432; it's on 5433 because another Postgres already had 5432.
3. It's listening on the wrong interface. A server bound to 127.0.0.1 is unreachable from another container or machine — it must bind 0.0.0.0 to accept outside connections. This is the classic "works on my laptop, refuses in Docker".
4. You're in a container and used localhost. Inside a container, localhost is that container, not the host and not a sibling. You need the service name on the compose network, or host.docker.internal.
How to fix it
Check something is actually listening:
ss -ltnp | grep 5432 # Linux lsof -iTCP:5432 -sTCP:LISTEN # macOS netstat -ano | findstr 5432 # Windows
Nothing returned means nothing is listening — start the service.
Check what it's bound to. 127.0.0.1:5432 in that output means local-only. To accept connections from elsewhere, bind 0.0.0.0:
# a dev server that other containers can reach app.run(host="0.0.0.0", port=8000)
setup added so this can run · defines app
# Lightweight mock for objects whose attributes/methods aren't critical class _AutoMock: def __init__(self, name='mock'): self._name = name def __getattr__(self, k): return _AutoMock(self._name + '.' + k) def __call__(self, *a, **kw): print('-> ' + self._name + '() called') return _AutoMock(self._name + '()') def __repr__(self): return '<mock ' + self._name + '>' def __str__(self): return '<mock ' + self._name + '>' def __bool__(self): return True def __iter__(self): return iter([]) def __len__(self): return 0 def __getitem__(self, k): return _AutoMock(self._name + '[...]') def __setitem__(self, k, v): pass def __enter__(self): return self def __exit__(self, *a): return False async def __aenter__(self): return self async def __aexit__(self, *a): return False def __add__(self, o): return self def __radd__(self, o): return self def __sub__(self, o): return self def __mul__(self, o): return self def __rmul__(self, o): return self def __truediv__(self, o): return self def __eq__(self, o): return isinstance(o, _AutoMock) def __hash__(self): return hash(self._name) def __lt__(self, o): return True def __le__(self, o): return True def __gt__(self, o): return False def __ge__(self, o): return False def __mro_entries__(self, bases): return (object,) app = _AutoMock('app')
In Docker Compose, use the service name, not localhost:
services:
api:
environment:
DATABASE_URL: postgresql://db:5432/shop # "db", not "localhost"
db:
image: postgres:16Fail with a message that says what to do, rather than letting the traceback escape:
import socket try: conn = connect_to_db() except ConnectionRefusedError: raise SystemExit( "Nothing is listening on the database port.\n" " Is the container up? docker compose ps\n" " Right port? echo $DATABASE_URL" )
setup added so this can run · defines connect_to_db
# Lightweight mock for objects whose attributes/methods aren't critical class _AutoMock: def __init__(self, name='mock'): self._name = name def __getattr__(self, k): return _AutoMock(self._name + '.' + k) def __call__(self, *a, **kw): print('-> ' + self._name + '() called') return _AutoMock(self._name + '()') def __repr__(self): return '<mock ' + self._name + '>' def __str__(self): return '<mock ' + self._name + '>' def __bool__(self): return True def __iter__(self): return iter([]) def __len__(self): return 0 def __getitem__(self, k): return _AutoMock(self._name + '[...]') def __setitem__(self, k, v): pass def __enter__(self): return self def __exit__(self, *a): return False async def __aenter__(self): return self async def __aexit__(self, *a): return False def __add__(self, o): return self def __radd__(self, o): return self def __sub__(self, o): return self def __mul__(self, o): return self def __rmul__(self, o): return self def __truediv__(self, o): return self def __eq__(self, o): return isinstance(o, _AutoMock) def __hash__(self): return hash(self._name) def __lt__(self, o): return True def __le__(self, o): return True def __gt__(self, o): return False def __ge__(self, o): return False def __mro_entries__(self, bases): return (object,) def connect_to_db(*_a, **_kw): print('-> connect_to_db() called') return _AutoMock('connect_to_db()')
Do not retry a refusal the way you would a timeout. A refusal is a definitive answer; retrying it twenty times just delays the error by twenty seconds. Retry with backoff only during a known startup window — a container waiting for its database is the legitimate case.
When you'd actually see this in real code
- The app container boots faster than the database container and connects before Postgres is accepting connections. Add a healthcheck and
depends_on: condition: service_healthy. - A dev server bound to
127.0.0.1inside Docker: the port is published, but nothing outside the container can reach it. - Redis or RabbitMQ restarted during a deploy and the pool held connections to a process that no longer exists.
- Someone ran a second Postgres locally, so 5432 was taken and yours came up on 5433.
- A firewall or security group that drops rather than rejects gives you a timeout instead — if you see a hang rather than an instant refusal, look at the firewall.
Related errors
- TimeoutError: connection timed out — nobody answered at all, which is a different diagnosis.
- socket.gaierror: Name or service not known — the name never resolved, so no connection was attempted.
- [OSError: [Errno 98] Address already in use](../error-address-in-use/) — the mirror image, on the listening side.
See Also
- All Python errors — the full index, by type and by when it happens.
- Environment & Configuration — keeping host and port out of your code.
- APIs & HTTP