Front a Unix Socket with systemd-socket-proxyd

systemd-socket-proxyd bridges a TCP client to a service that only listens on a Unix socket, without you writing a line of proxy code. You will create a socket unit on port 9000 that forwards each connection to a backend at /run/example-backend.sock. Allow about 15 minutes, plus time to adapt the backend path and port to your own setup.

This guide was checked against systemd 255.4-1ubuntu8.17. The installed manual describes systemd-socket-proxyd as a socket-activated forwarder for IPv4, IPv6 and Unix stream sockets. The backend here is represented by an existing Unix stream socket, so the focus is the proxy boundary rather than any particular application. You need root to install units under /etc/systemd/system, bind the service port, and start the units.

1. Confirm the installed binary and the backend socket

Check the executable before writing a unit. On this install the binary lives in /usr/lib/systemd, and its version output identifies systemd 255.

$ /usr/lib/systemd/systemd-socket-proxyd --version
systemd 255 (255.4-1ubuntu8.17)

Now check the destination socket already exists and is actually a Unix socket, replacing the path below if your backend uses a different location.

$ sudo test -S /run/example-backend.sock && echo 'backend socket exists'
backend socket exists

A proxy does not create the backend service. If this check fails, stop here and fix or start the backend first. Do not create an empty file with touch: that would not make a usable listening socket.

2. Create the socket unit

Create /etc/systemd/system/example-proxy.socket with one listening stream.

[Unit]
Description=TCP listener for the example Unix-socket proxy

[Socket]
ListenStream=127.0.0.1:9000
Accept=no

[Install]
WantedBy=sockets.target

ListenStream selects a TCP stream socket. Binding to 127.0.0.1 keeps this example local to the machine; only choose a different address deliberately, once remote clients are part of the design. Accept=no is spelled out explicitly here, and it is also the documented default: it makes systemd pass the listening socket to one regular service, which then accepts clients and manages the forwarding itself. With Accept=yes, systemd would instead need a per-connection template service, which is not the arrangement below.

The matching service name is normally the same stem, so example-proxy.socket activates example-proxy.service. Saving this file only creates configuration: nothing is active yet.

3. Create the proxy service

Create /etc/systemd/system/example-proxy.service.

[Unit]
Description=Proxy TCP clients to the example Unix socket
Requires=example-proxy.socket
After=example-proxy.socket

[Service]
Type=notify
ExecStart=/usr/lib/systemd/systemd-socket-proxyd /run/example-backend.sock
PrivateTmp=yes

[Install]
WantedBy=multi-user.target

The final argument is the destination: a path selects a Unix-domain stream socket, while a value like 127.0.0.1:8080 would select a TCP destination instead. The proxy forwards data both ways and opens a fresh destination connection for each accepted client.

PrivateTmp=yes is the setting used by the installed manpage example. It gives this proxy a private temporary directory and does not hide /run/example-backend.sock. Leave it out only if your service specifically depends on a shared temporary directory: the manpage's own namespace example pairs JoinsNamespaceOf with a backend that has matching private namespaces, so do not assume changing namespace settings is harmless.

Warning: this service is already state-changing configuration. Before starting it, review the destination path, the listening address, and the account or sandbox settings you intend to use. The sample sets no User=, so the proxy runs with systemd's normal service credentials; only choose a restricted user after confirming it can reach the destination socket.

4. Load and verify the units

Ask systemd to reread unit files, then verify the service and socket without starting them.

$ sudo systemctl daemon-reload
$ sudo systemd-analyze verify example-proxy.socket example-proxy.service
$ systemctl cat example-proxy.socket example-proxy.service

A successful systemd-analyze verify normally prints nothing and returns status 0. The systemctl cat output should show exactly the files you wrote. If verification reports an unknown directive, a missing dependency or an invalid executable, fix that before activation; do not dismiss warnings by copying a directive from an unrelated systemd version.

Checkpoint: The listener address must be 127.0.0.1:9000, the service must use Type=notify, and ExecStart must name the installed proxy and the real backend socket.

5. Enable the listener and test it

Enable and start the socket. Starting the socket is the part that matters: it owns the listening port and activates the matching service when a client connects.

$ sudo systemctl enable --now example-proxy.socket
Created symlink .../sockets.target.wants/example-proxy.socket -> /etc/systemd/system/example-proxy.socket
$ systemctl is-active example-proxy.socket
active
$ systemctl status --no-pager example-proxy.socket

The exact symlink path and status decoration vary. Check the port without needing a successful application response first.

$ ss -ltn '( sport = :9000 )'
State  Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0      4096   127.0.0.1:9000 0.0.0.0:*

Use the protocol client appropriate for your backend. For an HTTP-speaking backend, for example:

$ curl --fail http://127.0.0.1:9000/

A successful client request proves the complete path: the socket unit accepted the TCP connection, systemd activated the proxy, and the proxy reached the Unix socket. If the backend speaks another protocol, use its own client instead of forcing an HTTP test.

6. Read failures at the right layer

Check the two units separately: a failed socket usually means the address is unavailable or the unit is invalid, while a failed service usually means the executable, destination socket or service environment is wrong.

$ systemctl is-active example-proxy.socket
active
$ systemctl is-active example-proxy.service
active
$ journalctl -u example-proxy.socket -u example-proxy.service -b --no-pager

The proxy service may not stay active until a client arrives, depending on how systemd reports the activated service and its current connections; treat the journal and a real protocol request as the useful evidence. --connections-max= defaults to 256 simultaneous connections, and further connections are refused once that limit is reached. Set an explicit limit when the backend or host needs a different boundary.

ExecStart=/usr/lib/systemd/systemd-socket-proxyd --connections-max=64 /run/example-backend.sock

--exit-idle-time= can make the proxy exit after a period with no connections; its default is infinity, and it accepts seconds or a time span such as 5min 20s. Use it only when the service lifecycle is deliberately designed around idle shutdown, since an unexpected exit can make diagnosis harder.

7. Stop or undo the example

To stop accepting new connections and remove the enabled startup link, run:

$ sudo systemctl disable --now example-proxy.socket
Removed .../sockets.target.wants/example-proxy.socket.

Disabling the socket does not delete either unit file or the backend socket. Remove the two files only once you are sure no other deployment refers to them, then reload systemd.

$ sudo rm /etc/systemd/system/example-proxy.socket /etc/systemd/system/example-proxy.service
$ sudo systemctl daemon-reload

Destructive action: the removal is irreversible through systemd, so keep a copy if you may need the configuration again. If you changed an existing unit rather than creating these names, restore its previous contents instead of running the removal command.

Done means