Unix Domain Sockets and Local IPC

Updated

September 8, 2026

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 @ convention

Pros: 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 dialer

Docker 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 hello

Rules 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

  1. HTTP health endpoint served only on a Unix socket.
  2. Two processes: one sends JSON lines over unixgram.
  3. Start twice without Remove; fix the stale socket handling.