Local IPC vs HTTP
Local IPC vs HTTP
When two processes on the same machine need to communicate, you have two paths: stick local (Unix socket, named pipe, shared memory) or go through HTTP-over-loopback. The local-IPC path is faster, simpler, more secure, and harder to expose accidentally. HTTP is easier to consume from browsers and remote consumers.
This is one of the more important "default-choice" decisions in tool design. Most local daemons should default to local IPC.
The two paths
Local IPC HTTP over loopback
───────── ──────────────────
Client process Client process
│ │
│ socket(AF_UNIX, SOCK_STREAM) │ HTTP client (curl, fetch)
▼ ▼
Kernel buffer TCP/IP stack
│ │
▼ ▼
Daemon process loopback (lo) interface
│
▼
TCP/IP stack
│
▼
HTTP server (handler dispatch)
│
▼
Daemon process
HTTP-over-loopback adds two TCP/IP traversals plus HTTP parsing. Local IPC skips both.
Performance
Order-of-magnitude figures (rough, modern hardware, single message):
| Mechanism | Round-trip latency | Throughput (small msgs) |
|---|---|---|
| Function call (in-process) | ~1ns | – |
| Shared memory + lock | ~50ns | – |
| Unix socket (stream) | ~5–10µs | 1M+ msgs/sec |
| TCP loopback | ~15–30µs | 300K msgs/sec |
| HTTP over loopback (no keep-alive) | ~100–200µs | 30K msgs/sec |
| HTTP over loopback (with keep-alive) | ~30–60µs | 100K msgs/sec |
Per-call latency rarely matters. Throughput at scale does. Most CLI/agent invocations are infrequent enough that even HTTP loopback is fine; for a tool like a language server doing 1000s of completions/sec, local IPC is the right default.
Security
Unix socket security via filesystem permissions:
- Socket mode
0600— owner-only. Other users on the same machine cannot connect. - Socket mode
0660+ group ownership — group access. - The directory containing the socket also matters (can't symlink to it from elsewhere).
HTTP-over-loopback security:
- Bound to
127.0.0.1— accessible to any process running as any user on the same machine. - No filesystem-style permission system.
- Can be exposed to the network accidentally (
0.0.0.0bind). - Vulnerable to DNS rebinding attacks if a browser is the client.
For a per-user daemon holding sensitive state (email, debug session with secrets, project state), Unix sockets with an owner-only socket file and an owner-only parent directory are dramatically safer than HTTP-on-localhost.
Browser accessibility
The single biggest reason to use HTTP-over-loopback: browsers can't open Unix sockets directly. If your daemon needs to be consumed from a web app running in the user's browser, HTTP (or WebSocket) is forced.
Workarounds:
- HTTP bridge process — small extra binary that translates HTTP requests to local IPC calls. Browser → bridge (HTTP) → daemon (Unix socket). Common pattern; gives you both worlds.
- Run a local web server inside the daemon — sidesteps the bridge but exposes the daemon to all local processes.
Most "local web UI" tools do the bridge. Some (Jupyter, gitkraken) run web servers directly.
Discoverability
HTTP wins on tooling:
- Browse the API with
curl, Postman, Insomnia - View traffic with browser devtools
- Document with OpenAPI / Swagger
- Mock with
wiremock
Unix sockets need:
socat UNIX-CONNECT:/path/sock STDIOto inspect manually- Custom debug clients
- Less standard documentation tooling
For tools where third parties build clients, HTTP's tooling advantage matters. For tools where the project ships its own clients (mxr, lazydap), local IPC's simplicity wins.
Versioning
Both can be versioned, but conventions differ:
- HTTP: URL paths (
/v1/...,/v2/...), orAccept: application/vnd.foo.v1+jsonheaders. - Local IPC: protocol version field in the envelope. Simpler. mxr and lazydap both put
version: u32in every IpcMessage; clients refuse to talk on mismatch.
When to use HTTP locally
- The daemon needs browser clients.
- The daemon will eventually be exposed remotely (and you want one wire format).
- Third-party clients are a major use case and you want maximal tooling.
- You need OpenAPI / Swagger documentation.
When to use Unix sockets
- All clients are processes you control or co-design with.
- Performance matters (high message rate).
- Security via filesystem permissions is appealing.
- You want simpler, more debuggable wire format.
- Cross-machine isn't a near-term concern.
What mxr and lazydap do
Both use Unix sockets as the core protocol. mxr now also ships a loopback HTTP/WebSocket bridge because it has a real browser client. That did not replace the Unix socket; it wrapped it. The bridge carries its own security work: bearer auth, Host/CORS defenses, security headers, and auth-gated OpenAPI/Swagger routes.
That is the durable lesson: add HTTP when a browser actually needs it, keep the local IPC as the core, and make the bridge own HTTP-specific concerns instead of leaking them into the daemon protocol.
See also
- Unix Domain Sockets — the local IPC mechanism
- Inter-Process Communication (IPC) — broader menu
- TCP vs Unix Sockets vs Named Pipes vs Shared Memory — comparative
- How Processes Talk to Each Other — synthesis