Localhost is Slower Than You Think
When two services on the same machine need to communicate — say, Nginx forwarding requests to PHP-FPM, or a local application talking to a database — developers often reach for TCP because it's familiar: just connect to 127.0.0.1:9000. But for local communication, Unix Domain Sockets (UDS) are almost always the better choice.
A Unix Domain Socket is a socket that uses a filesystem path as its address instead of an IP address and port. You create one at a path like /var/run/php-fpm.sock. Any process with filesystem permission to that path can connect to it. Unlike a named pipe, UDS supports bidirectional, full-duplex communication and preserves message boundaries (in SOCK_DGRAM mode) or provides a reliable byte stream (in SOCK_STREAM mode, analogous to TCP).
Why UDS is faster than TCP loopback:
Even when TCP connects to 127.0.0.1, the data travels through the full network stack:
1. Application calls send()
2. TCP layer adds sequence numbers, checksums, flow control headers
3. IP layer adds source/destination addresses
4. The loopback driver (lo) receives the packet
5. IP layer strips headers, TCP layer strips headers, reassembles stream
6. Application calls recv()
With a Unix Domain Socket, steps 2–5 collapse into a single kernel memory copy from the sender's buffer to the receiver's buffer. There are no checksums (the kernel already guarantees memory integrity), no sequence numbers (the kernel already guarantees ordering), and no routing table lookups.
Benchmark comparison (approximate, single-threaded, 1KB messages):
| Mechanism | Latency | Throughput |
|---|---|---|
| TCP Loopback | 20–50 µs | ~1–2 GB/s |
| Unix Domain Socket | 5–15 µs | ~3–5 GB/s |
| Shared Memory | ~0.1 µs | ~10–20 GB/s |
The killer feature: File Descriptor Passing. UDS can transmit open file descriptors from one process to another using sendmsg() with SCM_RIGHTS ancillary data. This is how systemd socket activation works: systemd opens a listening socket, then passes the already-open file descriptor to the service process when it starts. The service inherits a ready-to-use socket without needing to bind or listen itself. TCP cannot do this.
Real-world examples of UDS:
- docker.sock — the Docker daemon's control socket
- /var/run/postgresql/.s.PGSQL.5432 — PostgreSQL's local socket
- /var/run/php/php8.1-fpm.sock — PHP-FPM
- D-Bus (Linux desktop IPC) — uses UDS under the hood