With socket activation, systemd opens the listening socket at boot and only launches your service when the first connection arrives. The service can also exit when idle and be respawned on the next connection — useful for rarely-used daemons.
1. The socket
/etc/systemd/system/myapp.socket:
[Unit]
Description=Socket for myapp
[Socket]
ListenStream=8080
# ListenStream=/run/myapp.sock # a Unix socket instead of a TCP port
Accept=no
[Install]
WantedBy=sockets.target
ListenStream=is a TCP port (orIP:port, or a Unix socket path).Accept=no(the default, “single instance”) passes the listening socket to one long-lived service instance — what almost every modern app wants.Accept=yesspawns one instance per connection (inetd-style, via a@.servicetemplate) — rarely needed.
2. The service (same base name)
/etc/systemd/system/myapp.service:
[Unit]
Description=myapp (socket-activated)
Requires=myapp.socket
After=myapp.socket
[Service]
ExecStart=/usr/local/bin/myapp
# no [Install] — the socket starts it on demand
Your program must accept the inherited file descriptor instead of opening its own listener. systemd passes it as fd 3 and sets LISTEN_FDS/LISTEN_PID; libraries like sd-listen-fds (C), systemd.daemon (Python), or go-systemd/activation (Go) read it for you.
Enable the socket
sudo systemctl daemon-reload
sudo systemctl enable --now myapp.socket
systemctl status myapp.socket # listening, service not yet running
curl localhost:8080 # first hit launches myapp.service
Why socket activation: the port is reserved at boot so dependent services never race on startup, and the daemon can start lazily (and, with Type=notify + an idle exit, stop when unused). Gotchas: enable the .socket; the service must use the passed fd, not bind the port itself (otherwise you get “address already in use”). The .socket and .service must share a base name unless you set Service= in [Socket].