The port is always ready while the process only starts when someone knocks, a neat trick for rare but heavy services. Every idle daemon costs something: resident memory, periodic wakeups, patch surface, log noise. Multiplied across dozens of seldom-used services on one host, the tax funds nothing. Socket activation inverts the deal — the kernel holds the listening socket, systemd owns it before any daemon exists, and the service process is born only when the first connection arrives carrying real work.
The handoff is what makes it transparent. The client connects to the port as usual, unaware nobody is home; systemd starts the service and passes the already-accepted connection in, so not even the first client waits twice. Done right, on-demand startup is indistinguishable from always-on except on the resource bill — and on the first-hit latency, which is the one cost to budget honestly.
The listener waits first
systemd opens the socket ahead of the service, and on the first connection starts the unit and hands it the accepted file descriptor. Configure the ListenStream once so the socket is born and bound before any daemon is up. The socket unit and service unit pair by name — foo.socket launches foo.service — and Accept=no (the default) hands the whole listening socket to one service instance, while Accept=yes spawns a per-connection instance for simple per-request handlers.
This split also buys dependency-free boot ordering. Because the socket exists from early boot, clients can start in any order and simply block or retry until the service is up, instead of encoding fragile After= chains. Queued connections wait in the kernel backlog rather than failing, so boot races become queue depth rather than errors. The backlog size still deserves sizing: a flood of early clients against a slow-starting service overflows the same as any listen queue.
Service wakes on demand
An idle daemon can stay completely stopped, trading per-instance memory and startup cycles for a single handoff cost. Socket-activated services therefore sleep until the first request justifies running them. The pattern shines for administrative tools, legacy protocol gateways, and scheduled-but-rare workers — services whose idle-to-busy ratio is measured in days, not minutes.
Idle exit completes the loop: the service stops itself after N idle seconds, so the system oscillates between zero cost and full capacity. The socket never closes, so the next client re-triggers startup. Without idle exit the process still accumulates — start on demand plus stop on idle is what converts the trick into steady-state savings.
Trade speed for memory
The price is a short warm-up gap on the first hit, which is fine for jobs that run rarely but wasteful on the always-hot path. Enable it where latency on the initial call is acceptable and memory savings actually matter. Quantify both sides before committing: measure cold-start time from connection to first response, and measure the resident set the daemon would otherwise hold forever. A 300ms start against 800MB saved is an easy yes; a 20-second JVM warmup against 50MB is a no.
Some workloads cannot tolerate the gap no matter the savings. Health-check-driven orchestrators may mark the endpoint down during startup, load balancers may eject the slow first backend, and clients with tight timeouts fail before the service finishes warming. For those, keep the daemon running and spend the memory — or warm the service at boot and let activation cover only the long idle stretches afterwards.
When it breaks
The signature failure is the thundering start: a monitoring sweep opens connections to fifty sleeping services at once, all fifty start simultaneously, and the host OOMs or load-spikes into a brownout. Each activation was reasonable alone; together they stampede. Mitigate with staggered check intervals, per-service start limits, and idle-exit timeouts short enough that the steady-state overlap stays small. Test activation under the monitoring load, not just under one curious curl.
The second failure is the half-adopted protocol: a service that binds the port itself at startup collides with the socket systemd already bound. It fails exactly when activated, the connection hangs, logs blame the address in use. Services must accept the passed file descriptor instead of binding; if upstream code cannot, a tiny shim that inherits the socket and execs the daemon bridges the gap.
Check it quickly
Prove on-demand behavior end to end:
systemctl status foo.socket foo.service
ss -ltnp | grep ':8080'
curl -m 10 localhost:8080/health; systemctl show foo.service -p ActiveState,ExecMainStartTimestamp
Before the curl the service is inactive with the socket listening; after, timestamps show a fresh start triggered by the connection.
Sources
- systemd.socket manual, Official socket unit reference, checked 2026-06-01.
- systemd.service manual, service unit behavior including idle exit, checked 2026-09-05.