systemd Socket Activation: On-Demand Service Startup
Understanding and implementing systemd socket activation for efficient on-demand service startup, connection handling, and resource optimization.
Introduction
systemd socket activation allows services to start on demand when a connection arrives, rather than running permanently. This saves memory and resources while maintaining responsiveness. The concept is elegant: systemd opens a socket, and when a connection arrives, it starts the service and hands over the socket file descriptor. This post covers the concept, implementation, and practical benefits of socket activation.
Environment
The examples use systemd 252 on Debian 12, implementing socket activation for a custom TCP service and for standard services like Nginx.
systemctl --version
# systemd 252 (252.19-1~deb12u1)Problem
You have several services that run permanently but receive occasional traffic. For example:
- An internal API used only during business hours
- A development server that is accessed sporadically
- A database connection pooler that handles intermittent queries
These services consume memory even when idle:
ps aux --sort=-%mem | head -5
# USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
# appuser 1234 0.1 12.3 1234567 1024000 ? Sl Jan01 45:23 node /opt/api/server.jsThe service uses 1 GB of memory while mostly idle.
Analysis
Socket activation works in three steps:
- systemd creates the socket (file descriptor) and listens on it
- When a connection arrives, systemd starts the service
- The service inherits the socket file descriptor from systemd
- When the service stops, systemd keeps the socket open for future connections
The key insight: the service does not need to know about socket activation. It simply reads from file descriptor 3 (the standard SD_LISTEN_FDS starting point).
Benefits:
- Memory savings for idle services
- Faster perceived startup (connection is held while service starts)
- No port conflicts between restarts
- Clean dependency management
Solution
1. Basic TCP socket activation
Create the socket unit:
# /etc/systemd/system/myapi.socket
[Unit]
Description=My API Socket
[Socket]
ListenStream=8080
Accept=no
[Install]
WantedBy=sockets.targetCreate the service unit:
# /etc/systemd/system/myapi.service
[Unit]
Description=My API Service
[Service]
ExecStart=/opt/myapi/server
StandardInput=socket
User=appuser
Restart=on-failureEnable and start:
sudo systemctl enable myapi.socket
sudo systemctl start myapi.socket
# The service is NOT running yet
sudo systemctl status myapi.service
# ● myapi.service - My API Service
# Active: inactive (dead)Test with a connection:
curl http://localhost:8080/
# Connection accepted, service starts
# Now check the service
sudo systemctl status myapi.service
# ● myapi.service - My API Service
# Active: active (running) since ...2. Socket-activated Nginx
# /etc/systemd/system/nginx.socket
[Unit]
Description=Nginx Socket
[Socket]
ListenStream=80
ListenStream=443
ReusePort=true
[Install]
WantedBy=sockets.target# /etc/systemd/system/nginx.service
[Unit]
Description=A high performance web server
After=network.target
[Service]
Type=simple
ExecStart=/usr/sbin/nginx -g 'daemon off;'
ExecReload=/bin/kill -s HUP $MAINPID
Restart=on-failure
[Install]
WantedBy=multi-user.target3. UDP socket activation
# /etc/systemd/system/syslog.socket
[Unit]
Description=Syslog Socket
[Socket]
ListenDatagram=514
SocketProtocol=udp
[Install]
WantedBy=sockets.target4. Multiple sockets per service
# /etc/systemd/system/myapi.socket
[Unit]
Description=My API Sockets
[Socket]
ListenStream=8080
ListenStream=8443
Accept=no
[Install]
WantedBy=sockets.target5. Programmatic socket activation
The service reads file descriptors from the environment:
# Python example
import socket
import os
# Get the file descriptor from systemd
sd_listen_fds = int(os.environ.get('LISTEN_FDS', 0))
if sd_listen_fds > 0:
# FD 3 is the first socket
sock = socket.fromfd(3, socket.AF_INET, socket.SOCK_STREAM)
print(f"Received socket from systemd: {sock.getsockname()}")
else:
# No socket activation, create our own
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(('0.0.0.0', 8080))
sock.listen(5)// C example using sd-daemon.h
#include
int main() {
int n = sd_listen_fds(0);
if (n > 0) {
// Use SD_LISTEN_FDS_START (3) as the file descriptor
int fd = SD_LISTEN_FDS_START;
// fd is ready to use
}
return 0;
} 6. Check socket status
systemctl status myapi.socket
# ● myapi.socket - My API Socket
# Loaded: loaded (/etc/systemd/system/myapi.socket; enabled)
# Active: active (running) since ...
systemd-activate -l 8080 --unit=myapi.service
# Test socket activation manually7. Monitor socket usage
# See which sockets are listening
ss -tlnp | grep systemd
# LISTEN 0 128 0.0.0.0:8080 0.0.0.0:* users:(("systemd",pid=1,fd=6))
# Check socket file descriptors
ls -la /proc/$(pgrep systemd)/fd | grep socket8. Socket activation with Accept=yes
With Accept=yes, systemd accepts the connection and passes it as stdin/stdout:
# /etc/systemd/system/echo.socket
[Unit]
Description=Echo Socket
[Socket]
ListenStream=9000
Accept=yes
[Install]
WantedBy=sockets.target# /etc/systemd/system/echo@.service
[Unit]
Description=Echo Service
[Service]
ExecStart=/bin/cat
StandardInput=socket9. Transition existing services
# For services that support socket activation natively
# Just add the socket unit and enable it
# For services that do not support it
# Use a wrapper script
#!/bin/bash
# /opt/myapi/wrapper.sh
exec /opt/myapi/server --fd=310. Disable socket activation
# To revert to traditional always-running service
sudo systemctl stop myapi.socket
sudo systemctl disable myapi.socket
sudo systemctl start myapi.serviceLessons Learned
Socket activation is a powerful optimization for services with intermittent traffic. It reduces memory usage and improves resource efficiency without sacrificing responsiveness. The key challenge is ensuring the service can accept file descriptors from systemd. For new services, design them to support LISTEN_FDS. For existing services, check if they support socket activation natively. And always test with systemd-activate before deploying to production.
This blog does not accept any external sponsorships, affiliate marketing, or ad revenue.