A reverse proxy and an application server run on the same machine. They need full-duplex communication, independent startup, and a stable endpoint name. TCP over the loopback interface can provide that contract, but neither process needs IP routing or transport across a network.
A Unix domain socket provides the socket interface entirely within one host. The server binds a local name, clients connect to it, and both sides exchange data through file descriptors. The kernel can also report the peer's local credentials and transfer an already-open file descriptor between processes.
These properties make Unix domain sockets common for database connections, local proxies, service supervisors, container runtimes, and communication between a privileged helper and an unprivileged worker.
A Unix domain socket uses the address family AF_UNIX, also named AF_LOCAL. The data path remains inside the kernel:
No IP address, port, routing decision, or TCP packet is involved. The kernel identifies local endpoints and moves data between their socket buffers.
Data still crosses the user-kernel boundary. A normal send copies bytes from the sender's buffer into kernel-managed memory, and a receive copies them into the recipient's buffer. Unix domain sockets avoid network-protocol processing, but they do not turn separate address spaces into shared memory.
The same socket APIs remain useful:
socket() creates an endpoint.bind() assigns a local address.listen() marks a stream or sequenced-packet socket as passive.accept() creates a connected server-side socket.connect() establishes the client side.read(), write(), send(), and recv() transfer data.shutdown() closes one direction of a full-duplex connection.close() releases a descriptor.This similarity lets an application use comparable connection-handling code for local and network clients while selecting a different address family.
Unix domain sockets support several communication contracts:
| Type | Connection | Message boundaries | Delivery model |
|---|---|---|---|
SOCK_STREAM | Required | Not preserved | Reliable ordered byte stream |
SOCK_DGRAM | Optional | Preserved | One datagram per send |
SOCK_SEQPACKET | Required | Preserved | Reliable ordered records |
SOCK_STREAM is the common choice for local client-server protocols. It is full-duplex and reliable, but it exposes bytes rather than records. One send() can require several recv() calls, and one recv() can return bytes from several sends.
The application must frame messages with a length prefix, delimiter, fixed-size record, or connection close.
SOCK_DGRAM preserves message boundaries. A receiver obtains one datagram at a time without calling listen() or accept(). Unix domain datagrams can use pathname addresses, allowing a sender to target a named receiver.
If the receive buffer is too small, the remaining bytes of that datagram can be discarded. Code should size buffers from the protocol limit and check truncation indicators when using recvmsg().
SOCK_SEQPACKET combines connection-oriented communication with preserved record boundaries. Records arrive reliably and in order. Linux supports this type for Unix domain sockets, though portability across Unix systems is weaker than for stream sockets.
The socket type belongs to the protocol. Switching a stream protocol to datagrams changes framing, connection, and failure semantics even though both endpoints still use AF_UNIX.
A pathname Unix socket uses a sockaddr_un address:
sun_path stores the pathname. Its capacity is small and varies across systems. Linux provides 108 bytes, including any terminating null byte. Code must check the name before copying it:
The address length can include only the populated portion:
The server calls bind() with this address. The kernel creates a socket entry in the filesystem. The entry stores the endpoint name and metadata; application messages do not become file contents.
Directory traversal permissions control whether a process can reach the pathname. On Linux, socket-file permissions also participate in access checks. Services commonly place sockets under an owned directory in /run and restrict both the directory and socket mode.
A stream server and client follow different setup paths:
The listening descriptor represents the named server endpoint. accept() returns a new descriptor for one client connection. The server keeps listening on the original descriptor while it communicates through accepted descriptors.
listen() includes a backlog argument because pending connection requests consume bounded kernel resources. The exact meaning and behavior of those queues depend on the socket family and kernel implementation. Application code should treat the backlog as a finite admission limit rather than a guarantee that an arbitrary burst will wait.
Closing an accepted socket affects one connection. Closing the listening socket stops new accepts but does not automatically close sockets that were already accepted.
The following POSIX program runs as a one-request server, a client, or a cleanup command. It uses a four-byte length prefix so the stream protocol has an explicit message boundary.
Compile it:
Start the server in one terminal:
Send a request from another:
The two processes print:
The server removes the pathname during normal exit and ordinary error exits. A forceful termination can leave the socket entry behind. After confirming that no live server owns the demo address, remove it with:
The /tmp path keeps the demonstration easy to run. A service should use a private directory with controlled ownership because checking and removing a shared-directory path is vulnerable to races with other users.
The example's four-byte header carries the payload length in network byte order. Network byte order is useful even for local protocols because it gives every implementation one explicit byte representation.
send_frame() calls a loop because write() can accept fewer bytes than requested. receive_frame() also loops because a stream read may return any positive number of currently available bytes. Neither side assumes that one write matches one read.
The receiver validates the length before reading the payload. Without that check, an untrusted client could request an excessive allocation or cause a write beyond the destination buffer.
Stream socket buffers are bounded. A sender can block when the peer stops reading, which propagates backpressure through the connection. Nonblocking mode reports EAGAIN instead. The protocol must decide whether to wait, buffer elsewhere, reject work, or close a slow connection.
A connected Unix stream socket is full-duplex. Closing the descriptor removes both directions once no duplicate references remain.
shutdown() can close one direction:
After queued outgoing bytes are delivered, the peer reads end-of-file from that direction. The calling process can continue reading responses through the other direction.
This half-close is useful when end-of-file frames a request. A client writes the complete request, calls shutdown(..., SHUT_WR), and then reads until the server closes its response direction.
Descriptor duplication affects closure. If another thread or process retains a duplicate socket descriptor, one close() may not produce the peer-visible end condition the application expects.
Closing the listening socket does not remove a pathname socket entry. The filesystem name remains until unlink() succeeds.
A later bind() to the same path fails with EADDRINUSE, even when no server is listening. This stale pathname commonly follows a crash or SIGKILL.
Blindly unlinking before every bind is unsafe in a shared directory. The path may belong to a live server or may have been replaced with another filesystem object. A service should create its socket in an owned directory, track which process owns the path, and remove only that owned entry during cleanup.
Unlinking a live pathname prevents new clients from connecting through that name. Existing connected sockets continue operating because their kernel endpoints no longer depend on pathname lookup.
Linux provides an abstract Unix socket namespace. An abstract address places a null byte in sun_path[0], followed by arbitrary name bytes. It does not create a filesystem entry.
The address length is important because abstract names are not null-terminated strings. The kernel uses the bytes included after sun_path[0].
Abstract sockets disappear after the final reference closes, so they do not leave stale files. They are Linux-specific and have no filesystem permissions. A server using them should rely on peer credentials and another controlled naming or discovery policy.
socketpair() for Related Processessocketpair() creates two already-connected Unix domain sockets:
No address, bind(), listen(), accept(), or connect() is required. A process can create the pair before fork(), retain one endpoint, and give the other endpoint to the child.
Unlike one pipe, a socket pair is full-duplex. Each endpoint can both send and receive. Descriptor ownership still needs discipline: after fork(), each process closes the endpoint it does not use so peer closure can be detected.
On Linux, SOCK_CLOEXEC can be combined with the socket type to prevent unintended inheritance across exec():
Filesystem permissions decide who can reach a pathname, but a server may also need the identity of the process on an accepted connection.
Linux exposes peer credentials through SO_PEERCRED:
The result includes the peer's process ID, user ID, and group ID. Other Unix systems provide related interfaces with different names and details.
Credentials should feed an authorization decision rather than a log message alone. A privileged local service can allow selected users, reject unexpected peers, or map credentials to limited operations.
The check belongs after accept() and before processing privileged requests. Socket-file permissions remain useful because they prevent unauthorized processes from creating connections that the server must then reject.
SCM_RIGHTSUnix domain sockets can carry ancillary data through sendmsg() and recvmsg(). The SCM_RIGHTS control message transfers references to open file descriptions.
fd 7, referring to an open file description.sendmsg with SCM_RIGHTS.fd 4, referring to the same open file description.The number differs on each side because descriptor numbers are per-process. What crosses is the reference, not the integer.
The sender does not transmit the integer 7. Descriptor numbers have meaning only inside one process. The kernel creates a new descriptor in the receiver that refers to the same open kernel object. The receiver's number may be 4, 12, or another available value.
These helpers send and receive one descriptor over a connected Unix domain socket:
At least one ordinary data byte accompanies the control message for portable stream-socket behavior. The receiver also sets FD_CLOEXEC so a later exec() does not leak the new descriptor. Linux can set this atomically by passing MSG_CMSG_CLOEXEC to recvmsg().
The focused receiver accepts exactly one SCM_RIGHTS record containing one descriptor. A general ancillary-data parser must iterate through every control record, validate each length, and close every descriptor it does not retain.
The received descriptor refers to the same open file description as the sender's descriptor. File offset and status flags can therefore be shared. Each process owns its descriptor entry and must close it.
Passing a descriptor does not remove the sender's reference. A transfer-of-ownership protocol requires the sender to close its copy after confirmation. The receiver should authenticate the peer, validate the number and types of ancillary records, and close every unexpected descriptor.
Descriptor passing supports several local designs:
The permission check performed when the sender opened the original resource is not repeated as though the receiver called open() itself. Receiving SCM_RIGHTS grants access to that existing open object, so the channel carrying it is a security boundary.
Loading simulation...
Unix stream sockets normally maintain send and receive buffering. A sender can continue briefly while the receiver is not scheduled, then block or receive EAGAIN when buffer capacity is exhausted.
Each transfer still involves system calls, buffer accounting, and data copies. Batching small logical records can reduce call overhead, while oversized batches can increase latency and memory pressure.
Unix domain sockets usually cost less than TCP loopback because they bypass IP and TCP protocol processing. The difference depends on message size, syscall rate, scheduling, socket type, and kernel version. Benchmarks should preserve the application's actual framing and concurrency rather than comparing isolated calls with different semantics.
Socket buffers provide transport backpressure, not application admission control. A server can accept bytes faster than it can complete the represented jobs. Protocol-level limits are still needed for queued work, request sizes, and per-client resource use.
ss lists Unix domain sockets:
-l selects listening sockets. -a includes listening and connected sockets, while -p requests process information when permissions allow it.
The kernel also exposes socket entries through:
lsof -U reports Unix socket descriptors held by processes. strace can follow setup and traffic:
For a stale pathname, inspect the path and active socket lists before removing it. A filesystem entry alone cannot prove whether a server remains alive under another process or namespace.
Unix domain sockets fit local client-server protocols that need independent startup, full-duplex communication, or several clients. They also support local identity checks and descriptor passing, which byte streams based only on inherited descriptors cannot provide by themselves.
Pathname sockets offer familiar discovery and filesystem access control. Abstract sockets avoid stale files on Linux. socketpair() gives related processes an unnamed, already-connected channel.
The socket API also provides a practical migration boundary. A protocol that already has explicit framing and connection handling can use a local Unix socket without committing every component to shared-memory layout or one process hierarchy.
Unix domain sockets provide local, kernel-mediated communication through the socket API. Stream sockets offer full-duplex byte streams, datagram sockets preserve independent messages, and sequenced-packet sockets combine connections with ordered record delivery.
Pathname sockets use filesystem discovery and permissions but require explicit unlinking. Linux abstract addresses avoid filesystem entries, while socketpair() creates an unnamed connected pair for related processes.
Correct stream protocols handle framing, partial I/O, bounded buffers, directional shutdown, and descriptor lifetime. Unix sockets can also expose peer credentials and pass open file descriptions with SCM_RIGHTS, making the communication channel part of the application's security boundary.
5 quizzes