Unix Domain Sockets and Local IPC
Unix Domain Sockets and Local IPC
Overview
Unix domain sockets (UDS) provide local IPC with stream or datagram semantics—like TCP/UDP but bound to filesystem paths (or abstract names on Linux). Go’s net package supports them as networks "unix" and "unixgram".
Use UDS when:
- Local sidecar ↔︎ app communication
- Privileged helper (root) spoken to by unprivileged client
- Avoid exposing a TCP port on
127.0.0.1
Stream server and client
// server
path := "/tmp/myapp.sock"
_ = os.Remove(path) // clean stale socket file
ln, err := net.Listen("unix", path)
if err != nil {
log.Fatal(err)
}
defer ln.Close()
_ = os.Chmod(path, 0o660) // restrict who can connect
for {
conn, err := ln.Accept()
if err != nil {
continue
}
go func(c net.Conn) {
defer c.Close()
io.Copy(c, c) // echo
}(conn)
}// client
conn, err := net.Dial("unix", "/tmp/myapp.sock")Lifecycle of the socket file
| Event | Action |
|---|---|
| Before listen | Remove stale path if safe |
| After listen | Chmod / directory permissions |
| Shutdown | Close listener; optionally Remove path |
If the process dies without removing the path, next start gets address already in use until unlink.
Abstract namespace (Linux)
Linux allows abstract sockets: path starts with @ or null byte—no filesystem node.
ln, err := net.Listen("unix", "@myapp.rpc") // Go uses @ conventionPros: no leftover files. Cons: Linux-only; different permission model (not file mode).
Datagram unixgram
conn, err := net.ListenPacket("unixgram", "/tmp/myapp.dgram.sock")
buf := make([]byte, 2048)
n, addr, err := conn.ReadFrom(buf)
_, err = conn.WriteTo(buf[:n], addr)Message-oriented; size limits apply.
HTTP over Unix sockets
ln, err := net.Listen("unix", path)
server := &http.Server{Handler: mux}
go server.Serve(ln)
// client
httpc := http.Client{
Transport: &http.Transport{
DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) {
var d net.Dialer
return d.DialContext(ctx, "unix", path)
},
},
}
resp, err := httpc.Get("http://localhost/healthz") // host ignored by dialerDocker engine API and many local agents use this pattern.
Credentials and peer auth (advanced)
On Linux, SO_PEERCRED can report peer UID/PID—useful for authorizing local clients. Requires golang.org/x/sys/unix and careful API use. For many apps, filesystem permissions on the socket path are enough.
Timeouts
conn, err := d.DialContext(ctx, "unix", path)
_ = conn.SetDeadline(time.Now().Add(5 * time.Second))Same discipline as TCP—don’t hang forever on a stuck peer.
Minimal tool pair: udsecho
# terminal 1
go run ./cmd/udsecho serve -path /tmp/echo.sock
# terminal 2
go run ./cmd/udsecho client -path /tmp/echo.sock -msg helloRules of thumb
| Do | Don’t |
|---|---|
| Chmod socket paths tightly | World-writable /tmp/*.sock for secrets |
| Remove stale sockets on start | Ignore EADDRINUSE forever |
| Prefer UDS for local-only APIs | Bind Docker API-style services on 0.0.0.0 by accident |
| Document path location | Scatter sockets without XDG/runtime dir |
Runtime dir tip: XDG_RUNTIME_DIR or /run/user/$(id -u)/ for user services.
Try next
- HTTP health endpoint served only on a Unix socket.
- Two processes: one sends JSON lines over
unixgram. - Start twice without
Remove; fix the stale socket handling.