skip to content

A systemd .socket unit declares ListenStream=8080 and there is a matching .service unit. Describe what systemd does at boot, what happens when the first client connects, and how the daemon ends up serving on a socket it never opened itself.

level: middleimportance: must knowfreq 55%

answer

  1. the socket outlives the process
  2. PID 1 owns the listener
  3. descriptors, not a port number
  4. first free descriptor after stderr
  5. count in one variable, owner in another

basics

~20 s

systemd binds and listens on port 8080 itself when the .socket unit starts; the first incoming connection triggers the matching .service, and systemd passes the already-listening descriptor to it as file descriptor 3, so the daemon never binds the port at all.

solid answer

~40 s

With socket activation the listening socket belongs to systemd, not to the daemon. When the `.socket` unit starts — normally at boot, pulled in by `sockets.target` — systemd creates, binds and listens on port 8080 while no part of the service is running. The first client connection makes systemd start the matching `.service` and hand the already-listening descriptor over: the process is spawned with `LISTEN_FDS=1`, `LISTEN_PID` set to its own PID, and the socket as file descriptor 3. The daemon calls `sd_listen_fds()` (or reads those variables directly), skips `bind()` and `listen()` entirely, and goes straight to `accept()` on fd 3. The client's connection was already queued on that socket by the kernel, so it is served as soon as the process is up rather than being refused.

code

ini · 10 lines
ini
# /etc/systemd/system/myapp.socket
[Unit]
Description=myapp listening socket

[Socket]
ListenStream=8080
Accept=no

[Install]
WantedBy=sockets.target

go deeper

for a junior

Be able to say that the .socket unit is what listens and what you enable, and that the service starts only when the first connection arrives. Knowing that the socket and the service normally share a base name is enough at this level.

for a middle

Explain the handover concretely: systemd calls bind() and listen(), the service inherits the descriptor starting at 3, and LISTEN_FDS and LISTEN_PID tell it how many and who they are for. Say plainly that the daemon must skip its own bind().

for a senior

Show that you have run this in anger — check the listener's owner with ss, confirm the trigger relationship with systemctl status, and recognise EADDRINUSE as a daemon that ignored the passed descriptors. Mention the fallback path that keeps the same binary runnable standalone.

for a principal

Frame it as an interface decision: the activation contract is three environment variables, which makes it portable across languages and cheap to adopt, but it still requires source-level support in every service you want to convert. Be ready to say when that cost is worth paying.

## The idea in one sentence Socket activation moves ownership of the listening socket out of the daemon and into systemd (PID 1). systemd opens the socket early and keeps it open for the whole life of the machine; the daemon behind it is started, stopped and restarted underneath a socket that never closes. ## The socket unit A socket unit is an ordinary unit file with a `[Socket]` section: ```ini # /etc/systemd/system/myapp.socket [Unit] Description=myapp listening socket [Socket] ListenStream=8080 Accept=no [Install] WantedBy=sockets.target ``` `ListenStream=` here means "a stream socket on TCP port 8080". `Accept=no` (the default) means systemd only listens; it does not accept connections itself. The `[Install]` section pulls the socket into `sockets.target`, which is reached very early in boot — this is what you `systemctl enable`, and it is the socket unit you enable, not the service. ## At boot When `myapp.socket` starts, systemd performs the `socket()`, `bind()` and `listen()` calls itself and then does nothing else. `systemctl list-sockets` shows the socket as listening and names the unit it will activate. `ss -tlnp` shows the listener with `users:(("systemd",pid=1,...))` — PID 1 holds it. The service is still inactive; no memory, no threads, no open connections to a database. Because the socket exists from the earliest moments of boot, other units may connect to it before the daemon behind it has ever run. That is the boot-parallelisation payoff: a client unit does not need `After=myapp.service`, because a connection attempt is never refused — it is queued on the listening socket and served whenever the daemon comes up. ## On the first connection The kernel marks the listening socket readable. systemd sees this, starts `myapp.service`, and passes it the descriptor. The service unit is found by name: `myapp.socket` activates `myapp.service` unless the socket unit overrides that with `Service=`. `systemctl status myapp.service` shows this relationship as a `TriggeredBy:` line. ## The handover protocol The contract is deliberately tiny and language-agnostic — it is three environment variables plus inherited descriptors: - `LISTEN_FDS` — how many descriptors were passed. - `LISTEN_PID` — the PID that is supposed to use them. - `LISTEN_FDNAMES` — colon-separated names, from `FileDescriptorName=` in the socket unit, so a daemon given several sockets can tell them apart. The descriptors themselves start at `SD_LISTEN_FDS_START`, which is 3 — immediately after stdin, stdout and stderr. With two `ListenStream=` lines you get fds 3 and 4, in the order the directives appear. `LISTEN_PID` exists because environment variables are inherited across `fork()` and `exec()`. Without the check, any child the daemon spawns would also believe it had been handed sockets. The libsystemd helper does this for you: ```c #include <systemd/sd-daemon.h> int n = sd_listen_fds(1); /* 1 = unset the variables afterwards */ if (n == 1) { int fd = SD_LISTEN_FDS_START; /* == 3 */ /* accept() on fd; never bind()/listen() */ } ``` `sd_listen_fds()` verifies `LISTEN_PID`, returns the count, and with a non-zero argument clears the variables so children cannot misread them. `sd_listen_fds_with_names()` returns the names as well. Nothing forces you to link libsystemd — the variables are documented and trivially readable from any language. ## What the daemon has to change Exactly one thing: when descriptors are passed, do not create your own socket. A well-behaved daemon checks the count, uses the inherited fds when there are any, and falls back to binding normally when there are none — which is what lets the same binary run standalone during development and socket-activated in production. A daemon that ignores the passed descriptors and binds the port anyway fails immediately with `EADDRINUSE`, because systemd is already bound to it. ## Why it is worth the trouble Two things. First, on-demand start: a rarely used service costs nothing until someone connects. Second, restarts that do not refuse connections — the listening socket lives in PID 1, so stopping and starting the service leaves it open and the kernel queues arrivals in the meantime. To try a binary without writing units, `systemd-socket-activate -l 8080 ./myapp` sets up the socket and the environment exactly as systemd would, which is the quickest way to find out whether a program supports the protocol at all.

  • The socket unit passes two listeners instead of one. How does the daemon tell them apart?
    By name. Each `Listen*=` group can be labelled with `FileDescriptorName=` in the socket unit, and the names arrive in `LISTEN_FDNAMES` in the same order as the descriptors, starting at fd 3. `sd_listen_fds_with_names()` returns the array directly. Without names you are relying purely on the order the `Listen*=` directives appear in the unit file, which is fragile once someone edits it.
  • Can a daemon written in Go or Python use socket activation without linking libsystemd?
    Yes. The protocol is just environment variables plus inherited descriptors, so any language that can read `LISTEN_FDS` and `LISTEN_PID` and wrap a raw descriptor number in a socket object can implement it in a few lines. The only obligations are to compare `LISTEN_PID` against its own PID, and to start at descriptor 3. Most ecosystems already ship a small library that does this.
  • What happens if you start the service directly with systemctl start myapp.service while the socket unit is stopped?
    No descriptors are passed: `LISTEN_FDS` is unset and the count comes back as zero. A daemon written to fall back gracefully will bind the port itself and work normally, which is exactly what you want for debugging. A daemon that assumes it will always be handed a socket will exit with an error instead, so the fallback path is worth implementing.

systemd is the receptionist who keeps holding the phone line open while the specialist behind the desk is swapped out — the caller hears hold music rather than a disconnect tone.

saying these in an interview costs you the question

  • Thinks systemd proxies or forwards the traffic to the service
  • Says the daemon still binds the port and systemd just watches it
  • Believes the passed socket arrives on stdin as descriptor 0
  • Assumes any daemon can be socket-activated with no code support
  • Confuses LISTEN_FDS (a count) with the descriptor number itself

context