systemd Socket Activation: Preserve the Listener Across an Embedded Service Restart
An embedded telemetry daemon can restart while its systemd-managed listening socket stays open. That only works if the daemon treats the inherited descriptor as a reference to an existing listener, rather than recreating or shutting down the endpoint during every process lifecycle.
This guide pins systemd v257 and an AF_UNIX stream socket using Accept=no. The hypothetical endpoint serves read-only device status. It excludes per-connection Accept=yes services, descriptor-store recovery and guarantees about unfinished application requests. No unit was installed or service restarted for this article.
Separate the socket lifetime from the process lifetime
With Accept=no, systemd passes listening sockets to one service. The tagged descriptor API explains that the manager retains references to those same sockets. A restarted daemon receives them again if the associated socket units were not restarted.
That distinction exposes a common cleanup defect. Closing the daemon’s own descriptor removes its reference. Calling shutdown() acts on the shared socket and can affect the manager’s retained reference too. The socket-unit reference also prohibits unlinking the inherited Unix socket’s filesystem entry on daemon exit.
For a socket-activated startup path, do not run the standalone server’s unlink, bind and listen sequence against the inherited endpoint. Keep the two initialization modes explicit. A program that silently falls back to creating another endpoint when activation parsing fails can conceal a packaging error.
Validate the inherited role once
At startup, call sd_listen_fds() through the supported library interface and handle its result. A negative result is an error; zero means no activation descriptors were supplied. Received descriptors start at SD_LISTEN_FDS_START, whose value is three, but the count and each descriptor’s role still need validation.
For this one-listener fixture, require exactly one suitable listening Unix stream socket before entering the accept loop. Use the relevant sd_is_socket_unix() or equivalent documented checks, including the properties the service actually requires. The number three is a starting descriptor number, not proof that the object is the intended endpoint.
If the product later adds another socket unit for the same service, do not preserve an accidental order assumption. Ordering across multiple socket units is unspecified. Optional descriptor names can help select roles, but they are not unique identities and do not replace type validation or client authorization.
Make the restart case observable
A minimal illustrative socket configuration is:
[Socket]
ListenStream=/run/device-status.sock
Accept=no
FlushPending=no
This fragment is not a complete installation recipe. The matching service, executable, endpoint access policy and activation relationships belong to the product configuration. It illustrates the particular listener and pending-queue behavior being reviewed, without changing them on a running device.

Original reference-lifetime diagram. Service generations change while the active socket unit retains the listener.
Suppose service generation A accepts one client and then exits. A new client attempts to connect while generation B is starting. With the socket unit still active and pending queues retained, the new attempt may wait for the replacement process, subject to the finite queue and ordinary error conditions. It is not evidence that the first client’s accepted connection or partly processed request survived.
FlushPending=yes changes that pending-queue policy after service exit: buffered data are cleared and pending incoming connections are rejected. Record the actual setting when a restart test behaves differently from an expected retained-listener model.
Inspect inherited state as well as endpoint availability
Socket options changed by generation A can remain on the same socket when generation B starts. A daemon should therefore establish its intended supported configuration deliberately rather than assuming restart creates default socket state. Review option changes for their shared-object effects before adding “cleanup” code.
There is a separate descriptor-inheritance boundary for child execution. sd_listen_fds() sets close-on-exec on received descriptors. Its nonzero unset_environment argument removes the activation environment, so a later call will not rediscover the same descriptors. Parse once, retain the validated result in the service’s ownership structure, and avoid using repeated discovery as a health check.
Qualify three different outcomes
In an approved development image, propose tests for an idle listener restart, a queued connection during restart, and an already accepted connection when the process exits. Keep their expected outcomes distinct. Log service generation, validated listener role and application request completion separately.
Audit the shutdown path for descriptor close, socket shutdown and pathname removal as three different operations. Confirm the socket unit remains active throughout the intended test. Success means the listener’s lifetime follows the socket unit and the daemon adopts it correctly; it does not establish lossless telemetry, unlimited queueing or exactly-once processing.