What are UNIX Domain Sockets?
UNIX domain sockets (also called local sockets or IPC sockets) are a way for two processes running on the same machine to communicate. Unlike TCP/IP sockets that send data over a network, UNIX domain sockets work entirely inside the kernel โ data never leaves the machine.
Think of it this way: if you want two processes to talk to each other on the same Linux box, UNIX domain sockets are one of the best tools available. They are faster than TCP/IP (no network stack overhead), support both stream and datagram communication, and can even pass open file descriptors between processes โ something nothing else can do.
The socket API for UNIX domain sockets is almost identical to the TCP/IP socket API. The main difference is the address family (AF_UNIX instead of AF_INET) and how addresses are specified โ using a file system path or an abstract name.
Key Terms in This File
Linux has many IPC mechanisms โ pipes, FIFOs, System V message queues, shared memory. Why use UNIX domain sockets?
| Feature | Pipe / FIFO | Shared Memory | TCP Socket | UNIX Socket |
|---|---|---|---|---|
| Bidirectional | โ (FIFO yes) | โ | โ | โ |
| Unrelated processes | FIFO only | โ | โ | โ |
| Pass file descriptors | โ | โ | โ | โ |
| Works over network | โ | โ | โ | โ |
| Performance | Fast | Very Fast | Slower | Fast |
| Credential passing | โ | โ | โ | โ |
UNIX domain sockets combine the best of both worlds: the ease of the socket API (which developers already know from networking) with high performance of local IPC. They are used by many critical Linux services โ systemd, D-Bus, X11 display server, MySQL, PostgreSQL, and Docker all use UNIX domain sockets.
UNIX domain sockets come in two main types:
- Connection-oriented
- Like a pipe โ byte stream
- Reliable, ordered delivery
- Must connect before sending
- Server: bind โ listen โ accept
- Client: connect
- Connectionless
- Message boundaries preserved
- Reliable on UNIX domain (unlike UDP)
- No need to connect
- Use sendto() / recvfrom()
- Each message goes independently
Important: Unlike UDP datagrams over a network, UNIX domain datagrams are reliable and ordered. The kernel handles them entirely in memory, so they cannot get lost or reordered. However, if the receiver’s buffer is full, the sender will block until space is available.
Every socket needs an address. For UNIX domain sockets, the address is either a file system path or an abstract name. The structure used is struct sockaddr_un defined in <sys/un.h>.
#include <sys/un.h>
struct sockaddr_un {
sa_family_t sun_family; /* Always AF_UNIX */
char sun_path[108]; /* Socket path (or abstract name) */
};
Key points about sockaddr_un:
sun_familymust always beAF_UNIXsun_pathholds the file system path as a null-terminated string- The maximum path length is 108 characters (including the null terminator) on Linux
- For abstract namespace sockets,
sun_path[0]is a null byte ('\0')
When calculating the length to pass to bind(), use the SUN_LEN() macro or calculate manually:
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
int main(void)
{
struct sockaddr_un addr;
socklen_t addr_len;
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, "/tmp/my_socket", sizeof(addr.sun_path) - 1);
/* Calculate the correct length for bind() */
/* Method 1: using offsetof + strlen */
addr_len = offsetof(struct sockaddr_un, sun_path) + strlen(addr.sun_path) + 1;
/* Method 2: using SUN_LEN macro (same result) */
addr_len = SUN_LEN(&addr);
printf("Address family : %d (AF_UNIX = %d)\n", addr.sun_family, AF_UNIX);
printf("Socket path : %s\n", addr.sun_path);
printf("Address length : %u bytes\n", (unsigned)addr_len);
return 0;
}
(2 bytes)
= AF_UNIX
“/tmp/my_socket\0……………..”
Total structure size = 2 + 108 = 110 bytes. But we only pass the bytes actually used (up to and including the null terminator of the path) to bind().
Creating a UNIX domain socket uses the same socket() call as network sockets:
#include <sys/socket.h>
/* Prototype */
int socket(int domain, int type, int protocol);
/* For UNIX domain stream socket */
int fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (fd == -1) {
perror("socket");
exit(EXIT_FAILURE);
}
/* For UNIX domain datagram socket */
int fd = socket(AF_UNIX, SOCK_DGRAM, 0);
if (fd == -1) {
perror("socket");
exit(EXIT_FAILURE);
}
The third argument (protocol) is always 0 for UNIX domain sockets โ there is only one protocol for each socket type in this domain.
When you call bind() on a UNIX domain socket, the kernel creates a special file in the file system at the given path. This file has type socket (shown as s by ls -l).
$ ls -l /tmp/my_socket
srwxrwxrwx 1 ravi ravi 0 Jun 13 10:00 /tmp/my_socket
^
|_ 's' = socket file type
Important rules about socket files:
bind() will fail with EADDRINUSE. Always call unlink() or remove() to delete the old socket file before binding.unlink() in a cleanup handler or at program start)./* Safe bind pattern โ remove old socket file first */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/un.h>
#define SOCKET_PATH "/tmp/my_socket"
int create_and_bind_socket(void)
{
int fd;
struct sockaddr_un addr;
fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (fd == -1) {
perror("socket");
return -1;
}
/* Remove old socket file if it exists */
if (remove(SOCKET_PATH) == -1 && errno != ENOENT) {
perror("remove");
close(fd);
return -1;
}
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
strncpy(addr.sun_path, SOCKET_PATH, sizeof(addr.sun_path) - 1);
if (bind(fd, (struct sockaddr *)&addr, SUN_LEN(&addr)) == -1) {
perror("bind");
close(fd);
return -1;
}
return fd;
}
UNIX Domain Stream Socket โ Server/Client Flow:
UNIX Domain Datagram Socket โ Flow:
#include <sys/socket.h> /* socket(), bind(), connect(), listen(), accept() */
#include <sys/un.h> /* struct sockaddr_un, AF_UNIX */
#include <unistd.h> /* close(), read(), write(), unlink() */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
Compile with:
gcc -Wall -o server server.c
gcc -Wall -o client client.c
AF_UNIX (or AF_LOCAL) sockets communicate between processes on the same machine using a file system path or abstract name as the address. Data stays in the kernel โ no network stack is involved. AF_INET sockets use IP addresses and port numbers and can communicate over a network. AF_UNIX is faster and more efficient for local IPC because there is no TCP/IP header overhead, no checksumming, and no routing.
Yes. Unlike UDP datagrams over the network, UNIX domain datagrams are reliable and ordered. The kernel manages them entirely in memory. Messages are never lost or reordered. However, if the receiver’s socket buffer is full, the sender will block (unlike UDP which just drops packets). This blocking behavior is what makes them reliable.
bind() fails with the error EADDRINUSE (“Address already in use”). The solution is to call unlink() or remove() on the path before calling bind(). Servers typically do this at startup. Also, removing the socket file does not destroy the socket itself โ connected processes can keep using it.
SUN_LEN(ptr) is a macro that computes the correct length of a sockaddr_un structure to pass to bind(), connect(), or sendto(). It calculates: offsetof(sockaddr_un, sun_path) + strlen(sun_path) + 1. You must not pass sizeof(struct sockaddr_un) because that includes the unused bytes at the end of the sun_path array, which can confuse some implementations.
With TCP loopback, the data still goes through the full TCP/IP stack: framing, segmentation, TCP sequence numbers, ACKs, checksum calculation and verification, and multiple memory copies in the kernel network stack. UNIX domain sockets skip all of this. The kernel copies data directly from the sender’s buffer to the receiver’s buffer. Benchmarks typically show UNIX domain sockets are 2โ5x faster than TCP loopback for small messages.
systemd (for IPC between services), D-Bus (desktop interprocess communication), X11 and Wayland display servers, MySQL and PostgreSQL (for local client connections), Docker daemon, Chrome/Chromium (between browser and renderer processes), BlueZ (Bluetooth daemon), CUPS (printing system), and Redis (when configured for local connections).
