ConnectionRefusedError: [Errno 111] Connection refused (tested)

ConnectionRefusedError: [Errno 111] Connection refused and ConnectionResetError: [Errno 104] Connection reset by peer look alike but mean different things. We reproduced both with local sockets on Python 3.13.5, including the IPv6 localhost trap, and tested a retry that only retries what it should.

1. Errno 111: nothing is listening there

import socket, urllib.request, requests

# nothing is listening on port 8964
try:
    socket.create_connection(("127.0.0.1", 8964), timeout=3)
except OSError as e:
    print(type(e).__name__, e.errno, e)

try:
    urllib.request.urlopen("http://127.0.0.1:8964/", timeout=3)
except OSError as e:
    print(type(e).__name__, e)

try:
    requests.get("http://127.0.0.1:8964/", timeout=3)
except requests.ConnectionError as e:
    print(type(e).__name__, str(e)[-60:])
ConnectionRefusedError 111 [Errno 111] Connection refused
URLError <urlopen error [Errno 111] Connection refused>
ConnectionError stablish a new connection: [Errno 111] Connection refused'))

Measured: the same refusal surfaces three ways: ConnectionRefusedError from socket, URLError wrapping it from urllib, and requests.ConnectionError (“Failed to establish a new connection”) from requests. It came back instantly, not after the timeout: the other machine answered “no” (a TCP RST). That rules out a firewall silently dropping packets (which looks like a timeout instead) and points at one of: the server isn't running, it's on a different port, or it isn't listening on the address you dialled. Errno 111 is the Linux number; macOS reports the same error as 61 and Windows as 10061.

2. Listening on the wrong address

import socket, threading, time

def serve(host, family=socket.AF_INET):
    s = socket.socket(family)
    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    s.bind((host, 8965))
    s.listen()
    def loop():
        while True:
            s.accept()[0].close()
    threading.Thread(target=loop, daemon=True).start()

serve("127.0.0.1")      # server listens on IPv4 loopback only
time.sleep(0.2)

for host in ["127.0.0.1", "localhost", "::1"]:
    try:
        socket.create_connection((host, 8965), timeout=3).close()
        print(host, "-> connected")
    except OSError as e:
        print(host, "->", type(e).__name__, e)
print(socket.getaddrinfo("localhost", 8965, type=socket.SOCK_STREAM)[0][4])
127.0.0.1 -> connected
localhost -> connected
::1 -> ConnectionRefusedError [Errno 111] Connection refused
('::1', 8965, 0, 0)

Measured: a server bound to 127.0.0.1 refuses connections to ::1, the IPv6 loopback. localhost still connected, because on this machine it resolves to ::1 first and socket.create_connection() (which urllib and requests both use) tried the next address, 127.0.0.1, when that was refused. Clients that only try the first address don't get that second chance. The same rule explains the classic Docker case: a server inside a container bound to 127.0.0.1 only accepts connections from inside that container, so bind to 0.0.0.0 there. Check what's really listening with ss -ltn (Linux) or netstat -an: 127.0.0.1:8000 means loopback only, 0.0.0.0:8000 or *:8000 means all IPv4 interfaces.

3. The server isn't up yet

In scripts that start a server, a container or a database and connect right away, the first attempt often lands before the port is open. Retry the refusal specifically, with a growing delay and a limit:

import socket, threading, time

def start_late(delay):
    time.sleep(delay)          # e.g. a database container still booting
    s = socket.socket()
    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    s.bind(("127.0.0.1", 8964))
    s.listen()
    s.accept()[0].close()

def connect_with_retry(addr, attempts=6, delay=0.25):
    for i in range(attempts):
        try:
            return socket.create_connection(addr, timeout=3)
        except ConnectionRefusedError:
            print(f"attempt {i + 1}: refused, retrying in {delay}s")
            time.sleep(delay)
            delay *= 2
    raise ConnectionRefusedError(f"{addr} still refusing after {attempts} attempts")

threading.Thread(target=start_late, args=(1.0,), daemon=True).start()
conn = connect_with_retry(("127.0.0.1", 8964))
print("connected to", conn.getpeername())
attempt 1: refused, retrying in 0.25s
attempt 2: refused, retrying in 0.5s
attempt 3: refused, retrying in 1.0s
connected to ('127.0.0.1', 8964)

Measured: the server came up after about a second; three refusals, then a connection. Catch ConnectionRefusedError only, not every OSError, so that a wrong hostname or a timeout still fails fast.

4. Errno 104: the other side reset the connection

import socket, struct, threading, time

def rude_server():
    s = socket.socket()
    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    s.bind(("127.0.0.1", 8966))
    s.listen()
    conn, _ = s.accept()
    conn.recv(1024)
    # SO_LINGER with timeout 0: close() sends RST instead of FIN
    conn.setsockopt(socket.SOL_SOCKET, socket.SO_LINGER, struct.pack("ii", 1, 0))
    conn.close()

threading.Thread(target=rude_server, daemon=True).start()
time.sleep(0.2)

c = socket.create_connection(("127.0.0.1", 8966), timeout=3)
c.sendall(b"GET / HTTP/1.1\r\nHost: x\r\n\r\n")
time.sleep(0.2)
try:
    print(c.recv(1024))
except OSError as e:
    print(type(e).__name__, e.errno, e)
ConnectionResetError 104 [Errno 104] Connection reset by peer

Measured: ConnectionResetError: [Errno 104] Connection reset by peer is different: the connection was established and the other side then aborted it with a RST instead of closing it normally. We forced it with SO_LINGER set to 0, which is what a crashing process, a proxy/load balancer killing an idle connection, or a server rejecting what you sent effectively does. For comparison, a normal close is not an error at all:

import socket, threading, time

def polite_server():
    s = socket.socket()
    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    s.bind(("127.0.0.1", 8967))
    s.listen()
    conn, _ = s.accept()
    conn.recv(1024)
    conn.close()          # normal close: FIN, not RST

threading.Thread(target=polite_server, daemon=True).start()
time.sleep(0.2)

c = socket.create_connection(("127.0.0.1", 8967), timeout=3)
c.sendall(b"hello")
time.sleep(0.2)
print(repr(c.recv(1024)))
b''

Measured: after a clean close, recv() returns b''. So an Errno 104 means something on the other end gave up abruptly. Common causes: the server crashed or hit a limit while handling your request (check its logs, not yours); an idle keep-alive connection was dropped by a proxy and then reused (retry once on a fresh connection); you spoke the wrong protocol, for example plain HTTP to a TLS port. In requests we measured it wrapped as ConnectionError(ProtocolError(it shows up wrapped as ConnectionError('Connection aborted.', ConnectionResetError(104, ...)).#x27;Connection aborted.it shows up wrapped as ConnectionError('Connection aborted.', ConnectionResetError(104, ...)).#x27;, ConnectionResetError(104, it shows up wrapped as ConnectionError('Connection aborted.', ConnectionResetError(104, ...)).#x27;Connection reset by peerit shows up wrapped as ConnectionError('Connection aborted.', ConnectionResetError(104, ...)).#x27;))).

Frequently asked questions

What does ConnectionRefusedError: [Errno 111] Connection refused mean?

Nothing accepted the connection at that address and port, and the other host said so immediately. We measured it returning instantly from socket, urllib and requests when no server was listening. Check the server is running, the port, and the address it's bound to.

Why is localhost refused but 127.0.0.1 works (or the reverse)?

localhost can resolve to ::1 (IPv6) before 127.0.0.1. We measured a server bound to 127.0.0.1 refusing ::1; Python's create_connection then fell back to 127.0.0.1 and connected, but clients that only try the first address fail.

How do I wait for a server to start before connecting?

Retry on ConnectionRefusedError with a growing delay and a maximum number of attempts. We measured a server that started after one second: three refused attempts, then a connection.

What does ConnectionResetError: [Errno 104] Connection reset by peer mean?

The connection was open and the other side aborted it with a TCP reset. We reproduced it with a server closing with SO_LINGER 0; a normal close instead returns b'' from recv().

Is errno 111 the same as 10061 or 61?

Yes, it's the same 'connection refused' condition: 111 is the Linux errno, 61 is macOS and 10061 is the Windows WSAECONNREFUSED code. Python raises ConnectionRefusedError for all of them.

Written with AI assistance. Measured on 2026-10-09 with Python 3.13.5 and requests 2.32.3 on Linux, against local sockets only: a closed port via socket, urllib and requests; a server bound to 127.0.0.1 dialled as 127.0.0.1, localhost and ::1; a server that starts one second late with a retry loop; and a server that closes with SO_LINGER 0 (reset) versus a normal close. Every code block was run verbatim and its output pasted from that run. PlainHeaders is our extension; socket and urllib are built in, requests is a third-party library.