TimeoutError: [Errno 110] Connection timed out in Python (tested)

TimeoutError: [Errno 110] Connection timed out (ETIMEDOUT) means nothing answered the connection attempt and the Linux kernel gave up retrying, which took 134 seconds in our test because no timeout was set. Set a timeout and the same failure becomes TimeoutError: timed out with errno None; except TimeoutError catches both.

All blocks ran in Docker (python:3-slim) with one firewall rule, iptables -A OUTPUT -d 203.0.113.5 -j DROP, so connection attempts to that address are silently dropped, exactly like a host behind a firewall that drops instead of rejecting.

1. The error: no timeout set, the kernel gives up after ~2 minutes

import socket, time
# 203.0.113.5 is a documentation address; an iptables rule silently DROPs our SYN packets
t = time.monotonic()
try:
    socket.create_connection(("203.0.113.5", 8080))   # no timeout argument
except OSError as e:
    print(f"after {time.monotonic() - t:.1f}s")
    raise

Output:

after 134.3s
Traceback (most recent call last):
  File "/w/b1.py", line 5, in <module>
    socket.create_connection(("203.0.113.5", 8080))   # no timeout argument
    ~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/local/lib/python3.15/socket.py", line 883, in create_connection
    raise exceptions[0]
  File "/usr/local/lib/python3.15/socket.py", line 868, in create_connection
    sock.connect(sa)
    ~~~~~~~~~~~~^^^^
TimeoutError: [Errno 110] Connection timed out

With no timeout, Python waits as long as the kernel does. Linux resends the SYN packet with exponential backoff (net.ipv4.tcp_syn_retries, default 6) and then fails with ETIMEDOUT: here after 134 seconds. Nothing answered at all: no refusal, no ICMP error, just silence. That points at a firewall that drops packets, a wrong IP, or a server that is down behind a load balancer.

2. With a timeout you get "timed out" instead, with no errno

import errno, socket, time
t = time.monotonic()
try:
    socket.create_connection(("203.0.113.5", 8080), timeout=3)
except OSError as e:
    print(f"{type(e).__name__}: {e!r} after {time.monotonic() - t:.1f}s")
    print("errno:", e.errno, "| isinstance TimeoutError:", isinstance(e, TimeoutError))
    print("socket.timeout is TimeoutError:", socket.timeout is TimeoutError)
print("ETIMEDOUT =", errno.ETIMEDOUT, "->", errno.errorcode[errno.ETIMEDOUT])

Output:

TimeoutError: TimeoutError('timed out') after 3.0s
errno: None | isinstance TimeoutError: True
socket.timeout is TimeoutError: True
ETIMEDOUT = 110 -> ETIMEDOUT

When Python's own timeout fires first, the message is just timed out and errno is None. Both are the same class: since Python 3.10 socket.timeout is an alias of TimeoutError, and errno 110 is mapped to TimeoutError too. So except TimeoutError catches both; do not test e.errno == 110, it misses the common case.

3. requests: timeout=None still ends in ConnectTimeout, after 134 s

import requests, time
for timeout in (None, 3):
    t = time.monotonic()
    try:
        requests.get("http://203.0.113.5:8080/", timeout=timeout)
    except requests.exceptions.RequestException as e:
        print(f"timeout={timeout}: {type(e).__name__} after {time.monotonic() - t:.1f}s")
        print("  ", str(e)[-90:])

Output:

timeout=None: ConnectTimeout after 134.0s
   =8080) at 0x7fb8475b3e00>, 'Connection to 203.0.113.5 timed out. (connect timeout=None)'))
timeout=3: ConnectTimeout after 3.0s
   ort=8080) at 0x7fb847486710>, 'Connection to 203.0.113.5 timed out. (connect timeout=3)'))

requests with no timeout (its default) hangs for the full kernel retry period and then raises ConnectTimeout with (connect timeout=None) in the message. With timeout=3 the same failure costs 3 seconds. Always pass a timeout; (connect, read) tuples such as timeout=(3, 30) let you fail fast on connect while allowing slow responses.

4. Fix: short timeout, bounded retries

import socket, time

def connect(host, port, attempts=3, timeout=3):
    for i in range(1, attempts + 1):
        try:
            return socket.create_connection((host, port), timeout=timeout)
        except TimeoutError as e:          # catches both "[Errno 110]" and "timed out"
            print(f"attempt {i}: {e!r}")
            time.sleep(0.5 * 2 ** i)
    raise TimeoutError(f"{host}:{port} unreachable after {attempts} attempts")

try:
    connect("203.0.113.5", 8080)
except TimeoutError as e:
    print("gave up:", e)

Output:

attempt 1: TimeoutError('timed out')
attempt 2: TimeoutError('timed out')
attempt 3: TimeoutError('timed out')
gave up: 203.0.113.5:8080 unreachable after 3 attempts

Retrying makes sense for a busy or restarting server; for a firewall drop or a wrong address every attempt fails the same way, so cap the attempts and report the host and port. If it never connects: check the address and port, try nc -vz host port from the same machine, and check the server-side firewall or cloud security group (a security group that does not allow the port drops packets, which gives exactly this timeout).

Related: requests ReadTimeout · [Errno 111] Connection refused · [Errno 113] No route to host · ERR_CONNECTION_TIMED_OUT.

Frequently asked questions

Why does Errno 110 take about 2 minutes?

With no timeout, Python waits for the kernel, which retries the SYN (tcp_syn_retries, default 6). We measured 134 seconds. Pass timeout= to fail sooner.

What is the difference between [Errno 110] and 'timed out'?

Errno 110 comes from the kernel when no timeout was set; 'timed out' (errno None) comes from Python's own timeout. Both are TimeoutError.

What causes Connection timed out?

No reply at all: a firewall or security group dropping packets, a wrong IP or port, or a host that is down. A refused port gives Errno 111 instead.

Written with AI assistance. Measured on 2026-10-23 in Docker (python:3-slim, Python 3.15.1, requests 2.34.2 from pip) with an iptables DROP rule for 203.0.113.5. Each block was run verbatim and the output pasted unedited. PlainHeaders is our extension; Python is free.