requests.exceptions.ConnectionError: Failed to establish a new connection
Wrap network calls in a try/except RequestException block with explicit timeout settings and an HTTPAdapter retry strategy.
Root Cause Analysis
This error occurs when Python tries to open a TCP socket connection to a remote server, but the underlying network stack cannot establish the handshake due to DNS resolution failures, unreachable hosts, or firewall rules.
1. Network Layer vs Application Layer
When requests.get() is invoked, urllib3 attempts to resolve the domain name via DNS, acquire an IP address, and initiate a TCP 3-way handshake on the target port. If the host cannot be resolved (NXDOMAIN) or the destination server drops SYN packets, urllib3 raises a MaxRetryError enclosing a socket gaierror or ConnectionRefusedError, which requests bubbles up as requests.exceptions.ConnectionError.
2. Common Causes in Production
In production environments, ConnectionError is typically caused by: (1) misspelled domain names or invalid port numbers, (2) corporate proxies and missing HTTP_PROXY environment variables, (3) target services being offline, or (4) security firewalls blocking outbound traffic.
3. Contrast with Other HTTP Errors
ConnectionError indicates that no HTTP communication ever took place—the client never reached the server. In contrast, HTTPError (like 404 Not Found or 500 Internal Server Error) indicates that the TCP handshake succeeded and the remote web server responded with an HTTP status code. Timeout indicates that the connection or read operation exceeded the configured deadline.
4. Note de Reproductibilité
Cette erreur dépend de l'état du réseau, de la connectivité Internet, de la résolution DNS locale et de la disponibilité des serveurs distants. Elle ne peut pas être reproduite de manière déterministe dans un environnement de test unitaire isolé sans dépendance réseau externe.
Reproduction Code (MCVE)
import requests
# Attempting to connect to an unreachable local address triggers ConnectionError
response = requests.get('http://127.0.0.1:59999/health', timeout=1.0)
Solution 1: Robust Exception Handling with Timeout Configuration
Always configure an explicit tuple timeout (connect_timeout, read_timeout) and catch requests.exceptions.RequestException to handle connection drops gracefully.
import requests
from requests.exceptions import ConnectionError, Timeout, RequestException
target_url = 'http://127.0.0.1:59999/health'
try:
response = requests.get(target_url, timeout=(2.0, 5.0))
response.raise_for_status()
print('Status:', response.status_code)
except ConnectionError as e:
print(f'Handled connection failure: remote service is unreachable ({e.__class__.__name__})')
except Timeout:
print('Request timed out.')
except RequestException as e:
print(f'General HTTP request exception: {e}')
Solution 2: Configure Automatic Retries with urllib3 HTTPAdapter
Mount an HTTPAdapter configured with exponential backoff on a requests.Session to automatically retry transient connection failures.
import requests
from urllib3.util import Retry
from requests.adapters import HTTPAdapter
session = requests.Session()
retries = Retry(
total=3,
backoff_factor=0.5,
status_forcelist=[500, 502, 503, 504],
raise_on_status=False
)
adapter = HTTPAdapter(max_retries=retries)
session.mount('http://', adapter)
session.mount('https://', adapter)
print('Session configured with resilient HTTPAdapter.')
A dangerous practice in production code is catching bare except Exception: and proceeding blindly without verifying response integrity. Always distinguish between ConnectionError (client could not reach server) and HTTPError (server reached and returned error code). Another common pitfall is omitting the timeout parameter: requests with no timeout can hang indefinitely if the socket remains open without sending data. In containerized Docker setups, using localhost or 127.0.0.1 inside a container refers to the container itself rather than the host machine.