PythonMastery
reference 4 min read · lesson 31 of 60 in Errors

ConnectionRefusedError: [Errno 111] Connection refused

1 · The lesson

read

What this error means

Your code reached the machine, and the machine said no. Something answered at that address: the network works, DNS resolved the name and the packets arrived. But nothing is listening on that port, so the operating system on the other end refused the connection immediately.

This is one of the easiest network errors to diagnose. A refusal comes immediately and has only one meaning. A timeout is different: it means nobody answered at all.

When you see it

python
Traceback (most recent call last):
  File "app.py", line 12, in <module>
    sock = socket.create_connection(("localhost", 5432))
ConnectionRefusedError: [Errno 111] Connection refused

Through requests, it arrives wrapped:

python
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 is different ([WinError 10061]), but the meaning is the same.

Why it happens

There are four common causes:

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, but it is on 5433 because another Postgres already used 5432.
3. It's listening on the wrong interface. Another container or machine can't reach a server bound to 127.0.0.1. To accept outside connections, the server must bind to 0.0.0.0. This is the typical "works on my laptop, refused in Docker" problem.
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 that something is listening:

bash
ss -ltnp | grep 5432        # Linux
lsof -iTCP:5432 -sTCP:LISTEN # macOS
netstat -ano | findstr 5432  # Windows

If the command prints nothing, nothing is listening. Start the service.

Check which address it is bound to. 127.0.0.1:5432 in that output means it accepts only local connections. To accept connections from elsewhere, bind 0.0.0.0:

python
# 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:

yaml
services:
  api:
    environment:
      DATABASE_URL: postgresql://db:5432/shop   # "db", not "localhost"
  db:
    image: postgres:16

Fail with a message that says what to do, rather than letting the traceback escape:

python
import socket

try:
    conn = connect_to_db()
except (ConnectionRefusedError, OSError) as exc:     # database drivers wrap it (psycopg: OperationalError)
    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 retry a timeout. A refusal is a clear answer. Retrying it twenty times only delays the same error. Retry with backoff only while a service is known to be starting. A container that waits for its database to start is the valid case.

When you'd 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.1 inside Docker: the port is published, but nothing outside the container can reach it.
  • Redis or RabbitMQ restarted during a deploy, and the connection pool still 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 packets (instead of rejecting them) gives you a timeout instead. If the program hangs instead of failing immediately, check the firewall.

See Also

Found this useful? Share it with a friend who’s learning Python.