PythonMastery
reference 3 min read · lesson 33 of 60 in Errors

socket.gaierror: [Errno -2] Name or service not known

1 · The lesson

read

What this error means

DNS could not turn the hostname into an IP address. No connection was ever attempted, because there was no address to connect to. gaierror is short for getaddrinfo error: the name lookup itself failed, before any connection was made.

When you see it

python
Traceback (most recent call last):
  File "sync.py", line 8, in <module>
    r = requests.get("https://api.internl.com/v1/users")
socket.gaierror: [Errno -2] Name or service not known

Through requests, wrapped:

python
requests.exceptions.ConnectionError: HTTPSConnectionPool(host='db', port=5432):
Max retries exceeded (Caused by NewConnectionError(
'Failed to establish a new connection: [Errno -2] Name or service not known'))

Why it happens

  • A typo in the hostname. api.internl.com above is missing an a.
  • The name only exists on a network you are not connected to. An internal service name resolves inside the VPN or the cluster, and nowhere else.
  • A Docker Compose service name used outside that network. db resolves from another container on the same network, but means nothing on your laptop.
  • A hostname with stray whitespace. A trailing newline read from a file ("api.example.com\n") can't resolve when you call socket directly. (Through requests, an empty or padded host fails earlier, as InvalidURL.)
  • DNS itself is broken in the container: no resolver is configured, or /etc/resolv.conf is empty.

How to fix it

Resolve the name by hand first. This tells you whether the problem is in your code or in the network:

bash
getent hosts api.example.com      # Linux
dig +short api.example.com
nslookup api.example.com          # Windows

If you get no result, the name is wrong or DNS is unreachable, and no change to your Python code will fix it.

Check the value you really passed. This is the most common cause, and the easiest to miss:

python
import os

host = os.environ.get("API_HOST")
if not host:
    raise SystemExit("API_HOST is not set, so there is nothing to connect to")

print(f"connecting to {host!r}")     # !r makes '' and stray whitespace visible
+ setup added so this can run · defines
import os  # noqa: F401
os.environ.setdefault("API_HOST", "example-api-host")

The !r matters. A trailing newline causes this error with a direct socket call, and without !r the printed value looks correct.

Inside Docker, use the service name from the same network:

yaml
services:
  api:
    environment:
      DB_HOST: db          # resolves on the compose network
    depends_on: [db]
  db:
    image: postgres:16

When the cause is configuration and not a bug, say what failed:

python
import socket
import requests

try:
    r = requests.get(url, timeout=(3.05, 10))
except requests.exceptions.ConnectionError as exc:
    err = exc
    while err is not None and not isinstance(err, socket.gaierror):
        err = err.__cause__ or err.__context__     # requests wraps it several layers deep
    if err is not None:
        raise SystemExit(f"Cannot resolve {url!r}. Check the hostname and your DNS.")
    raise
+ setup added so this can run · defines url
# 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,)

url = _AutoMock('url')

When you'd see this in real code

  • A .env file copied from another machine still points to an internal hostname that only resolves on the office VPN.
  • A deploy where a service was renamed in Compose but one environment variable still names the old one.
  • A hostname read from a file with a trailing newline. This one takes people the longest to find, because the config "looks fine". (An empty host shows up as InvalidURL instead.)
  • A scheduled container without DNS configured: the same code works when you run it by hand, but fails when the timer runs it.
  • [ConnectionRefusedError: [Errno 111] Connection refused](../error-connection-refused/): the name resolved, but nothing was listening.
  • TimeoutError / requests.exceptions.ReadTimeout: the name resolved, but the server did not answer in time.

See Also

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