architecture2024-09-10·13 min·272/348

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.js

The service uses 1 GB of memory while mostly idle.

Analysis

Socket activation works in three steps:

  1. systemd creates the socket (file descriptor) and listens on it
  2. When a connection arrives, systemd starts the service
  3. The service inherits the socket file descriptor from systemd
  4. 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.target

Create the service unit:

# /etc/systemd/system/myapi.service
[Unit]
Description=My API Service

[Service]
ExecStart=/opt/myapi/server
StandardInput=socket
User=appuser
Restart=on-failure

Enable 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.target

3. UDP socket activation

# /etc/systemd/system/syslog.socket
[Unit]
Description=Syslog Socket

[Socket]
ListenDatagram=514
SocketProtocol=udp

[Install]
WantedBy=sockets.target

4. Multiple sockets per service

# /etc/systemd/system/myapi.socket
[Unit]
Description=My API Sockets

[Socket]
ListenStream=8080
ListenStream=8443
Accept=no

[Install]
WantedBy=sockets.target

5. 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 manually

7. 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 socket

8. 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=socket

9. 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=3

10. Disable socket activation

# To revert to traditional always-running service
sudo systemctl stop myapi.socket
sudo systemctl disable myapi.socket
sudo systemctl start myapi.service

Lessons 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.