Home / Alt manpages / systemd.socket(5)

  • systemd.socket(5)
  • File format
  • linux

Build a Socket-Activated Service with systemd.socket

A systemd.socket unit lets systemd own a listening socket and only start your service once a connection actually arrives. This guide builds a small TCP echo service to show exactly how, leaving you with a verifier-tested pair of unit files and a clear way to remove them. Allow about 20 minutes.

  • You need: systemd 255 or a compatible release (255.4-1ubuntu8.17 here), a shell, and systemd-analyze.
  • What this touches: a user service on 127.0.0.1:8765, so no root and nothing exposed on your network. A real daemon must understand systemd socket activation, either through the native file-descriptor protocol or the traditional socket-on-standard-input method used here.

Warning

Binding a service to a public address, changing a system service, or accepting untrusted input is a separate deployment decision from anything in this guide. Start with loopback and a high port; the example here is deliberately disposable.

1. Understand the two unit files

A .socket unit opens and supervises the listening socket. A matching .service unit handles traffic when systemd hands it over. By default, echo.socket activates echo.service when Accept=no, or instances of [email protected] when Accept=yes. This guide picks the latter so each incoming TCP connection gets its own service process.

Save these as echo.socket and [email protected] in a temporary directory first.

[Unit]
Description=Example socket activated echo service

[Socket]
ListenStream=127.0.0.1:8765
Accept=yes

[Install]
WantedBy=sockets.target
[Unit]
Description=Example echo connection

[Service]
ExecStart=/usr/bin/cat
StandardInput=socket
StandardOutput=socket

cat is only a test handler: it reads the accepted connection from standard input and writes the same bytes to standard output. In a production service, replace it with a program designed for activation and check its documentation for how it receives passed file descriptors.

2. Check the syntax before installing anything

Run this ordinary, read-only command from the directory containing the two files.

$ systemd-analyze verify echo.socket [email protected]

No output and exit status 0 mean systemd could parse the units and found no verification error. This checks syntax and many unit relationships, but it does not bind port 8765 or prove your user manager is running.

Checkpoint

If verification fails, fix the first reported error before continuing. Common distractions are a typo in ListenStream=, a missing [Socket] section, or writing echo.service when Accept=yes actually requires the [email protected] template.

3. Install the pair as user units

Create the user unit directory and copy the files into it. This changes your user systemd configuration but needs no elevated privileges.

$ mkdir -p "$HOME/.config/systemd/user"
$ cp echo.socket [email protected] "$HOME/.config/systemd/user/"

Reload the user manager so it notices the files.

$ systemctl --user daemon-reload
$ systemctl --user cat echo.socket
[Socket]
ListenStream=127.0.0.1:8765
Accept=yes

If systemctl --user reports that it cannot connect to the bus, your session does not have a running user manager. Do not switch to sudo systemctl as a reflex: that controls the system manager and is a different scope entirely. Use a normal desktop or login session with user systemd support, or stick to the syntax check above.

4. Start the socket, not the service

Start the socket unit. This is the point where systemd actually attempts to bind the address.

$ systemctl --user start echo.socket
$ systemctl --user is-active echo.socket
active
$ systemctl --user status echo.socket --no-pager

There should be no long-running echo@ service yet; socket activation only starts the handler when a connection arrives. If the start fails, inspect the journal.

$ journalctl --user -u echo.socket -n 30 --no-pager

The usual causes are another process already using port 8765, a stale socket unit from an earlier test, or a user manager that cannot access the unit file. Change the port to another high, unused value if it is occupied. Do not kill an unknown process merely to free a port.

5. Make one connection and verify activation

Use netcat from the same machine, an ordinary client command.

$ printf '%s\n' 'socket activation works' | nc 127.0.0.1 8765
socket activation works

The response shows the accepted connection reached cat. Check the instantiated service and its recent log.

$ systemctl --user list-units 'echo@*' --all --no-legend
$ journalctl --user -u 'echo@*.service' -n 30 --no-pager

The instance name contains an escaped connection identifier, so do not script against one exact instance name. The service may already show inactive when you inspect it, since cat exits as soon as the client closes the connection: that is expected for this test.

6. Know the settings that matter

  • ListenStream= selects a stream socket. A bare port is interpreted as IPv6, while 127.0.0.1:8765 is explicitly IPv4. An address such as /run/example.sock instead creates an AF_UNIX socket.
  • FreeBind=yes is documented for a specific IP address that may not exist yet when the unit starts; use it only when that early binding behaviour is intentional.
  • Accept=no is the recommended shape for new daemons: one service receives the listening sockets and manages connections itself.
  • Accept=yes is mainly useful for inetd-style programs and needs a template service. It also spawns one service instance per connection, so consider MaxConnections=, MaxConnectionsPerSource= and cleanup behaviour before using it on an untrusted interface.

The defaults can be surprising: a file-system socket node defaults to mode 0666, while its automatically created parent directories default to 0755. Set SocketMode=, SocketUser= and SocketGroup= explicitly for a Unix socket carrying sensitive data. For network sockets, the default BindIPv6Only=default follows the kernel setting, which can make an IPv6 bind reachable over IPv4 too.

7. Stop and remove the test safely

Stop the socket first. This prevents new activations but does not necessarily terminate an already running service instance.

$ systemctl --user stop echo.socket
$ systemctl --user is-active echo.socket
inactive

Then stop any remaining test instances and remove only the files created for this guide.

$ systemctl --user stop 'echo@*.service' 2>/dev/null || true
$ rm "$HOME/.config/systemd/user/echo.socket" "$HOME/.config/systemd/user/[email protected]"
$ systemctl --user daemon-reload

Destructive action

The final command is destructive to these two unit files, but it does not remove application data. If you need the test again, restore the two files from your temporary copies, run daemon-reload, and start echo.socket.

Done means

  • Verified clean: systemd-analyze verify accepted the socket and matching service template.
  • Loopback only: the socket bound only to loopback and used a high test port.
  • Activation confirmed: a client connection caused an echo@ service instance to run.
  • Accept mode understood: you know why Accept=yes needs a template and when Accept=no is preferable.
  • Diagnostics work: you can inspect failures with systemctl --user status and journalctl --user.
  • Cleaned up: the test socket and user unit files are stopped and removed now the experiment is over.