Starting services only when needed
Not every background service has to be running the instant a machine boots, some are only needed sporadically and spend most of their time just occupying memory for nothing. Socket activation flips the classic startup model on its head: systemd opens the network socket at boot, but only starts the actual service behind it once the first connection actually arrives. For custom PHP background services in a Magento infrastructure, that is a simple path to a faster boot and lower resource usage, without giving up availability.
Table of Contents
- 1. How socket activation works
- 2. .socket unit files: basic structure
- 3. Linking a socket unit to a service unit
- 4. Benefits: faster boot and lazy start
- 5. Accept=yes versus Accept=no
- 6. Practical example: a custom PHP background service with socket activation
- 7. Handling file descriptors: LISTEN_FDS and LISTEN_PID
- 8. Socket activation with Docker and containers
- 9. Debugging and troubleshooting socket activation
- 10. Summary
- 11. FAQ
1. How socket activation works
The basic principle of socket activation is not a new invention by systemd, it is a modern take on a concept that already existed in the classic inetd daemon of early Unix systems: instead of every service opening its own network socket and listening on it permanently, a central instance, in this case systemd itself, takes over opening and holding the socket open.
When an incoming connection arrives on that socket, systemd checks whether the associated service is already running. If it is not, systemd starts the service at that exact moment and hands it the already open socket as a prepared file descriptor, instead of the service having to create and bind a new socket itself. For the requesting client, this entire process is invisible, the connection is merely handled with a fraction of a second's delay.
2. .socket unit files: basic structure
A socket unit defines what systemd should listen on, through the ListenStream= directive for TCP or Unix domain sockets, or ListenDatagram= for UDP. The value can be a plain port number, a combination of address and port, or an absolute path for a local Unix socket, which is often the more performant choice over TCP on loopback for communication between processes on the same machine.
The [Install] section with WantedBy=sockets.target ensures the socket unit is actually enabled at boot. The distinction matters: it is the socket that gets activated, not the service itself, which stays deliberately inactive at this point until the first connection arrives.
# /etc/systemd/system/php-worker.socket
[Unit]
Description=Socket for the PHP background service
[Socket]
ListenStream=127.0.0.1:9501
Accept=no
[Install]
WantedBy=sockets.target
3. Linking a socket unit to a service unit
By default systemd automatically links a socket unit to a service unit of the same name, as long as the file names match apart from the extension, so php-worker.socket pairs with php-worker.service. This implicit naming convention already covers the vast majority of use cases without needing an explicit link.
For cases where a different service name is desired, or where several sockets should activate the same service, the Sockets= directive in the service unit allows an explicit mapping. This flexibility is especially useful when a service needs to be reachable both through a TCP socket for external requests and a Unix socket for internal communication.
# /etc/systemd/system/php-worker.service
[Unit]
Description=PHP background service for image conversion
Requires=php-worker.socket
[Service]
ExecStart=/usr/bin/php /opt/mironsoft/php-worker/worker.php
Sockets=php-worker.socket
StandardOutput=journal
4. Benefits: faster boot and lazy start
The most obvious benefit of socket activation is a faster boot: instead of waiting for every single service to fully initialize during system startup, systemd merely opens the associated sockets, a step that runs practically without delay. The actual, potentially slow startup of the application itself shifts to the moment of first real use.
Another, often underrated benefit is implicit parallelization: because multiple services only need to open their sockets at boot instead of fully initializing, significantly more services can start at the same time, without dependency chains artificially stretching out the boot. A service that talks to another through its socket no longer has to wait until that other service is fully up, only until its socket becomes available.
5. Accept=yes versus Accept=no
The Accept= directive in the socket unit governs a fundamentally different behavior. With Accept=no, the recommended mode for most modern applications, systemd hands the listening socket itself to a single, permanently running instance of the service, which then manages multiple simultaneous connections internally, typically through its own event loop.
With Accept=yes, systemd instead starts a brand new instance of the service for every single incoming connection, similar to the classic inetd behavior. That works for simple, stateless scripts, but causes considerable process startup overhead under a high connection rate, which is why this mode is usually unsuitable for applications with meaningful throughput and better reserved for rare, simple requests.
6. Practical example: a custom PHP background service with socket activation
A typical use case is a PHP background service that handles image conversion for product images outside the regular Magento request cycle, but is only called rarely and therefore does not need to sit permanently in memory. With socket activation, the service stays inactive until a conversion request actually arrives on the socket, so neither memory nor a PHP interpreter process is permanently tied up.
For PHP applications, the sockets extension together with a small helper function is needed to correctly take over the file descriptor handed over by systemd, instead of opening a new socket itself. Once the worker has successfully wrapped the passed in socket, it behaves like a normal, permanently running server for any subsequent connections.
#!/usr/bin/env php
<?php
declare(strict_types=1);
// worker.php: PHP worker started by systemd via socket activation
// The first file descriptor handed over by systemd starts at 3
$fd = 3;
$socket = socket_import_stream(fopen("php://fd/{$fd}", "r+"));
while (true) {
$client = socket_accept($socket);
if ($client === false) {
continue;
}
// Conversion logic for incoming requests
socket_write($client, "OK\n");
socket_close($client);
}
7. Handling file descriptors: LISTEN_FDS and LISTEN_PID
For a process started via socket activation to recognize that it should take over an already open socket instead of opening a new one itself, systemd sets two environment variables: LISTEN_FDS gives the number of passed file descriptors, LISTEN_PID holds the expected process ID, so that forked child processes do not accidentally misinterpret the variables as meant for themselves.
Languages with systemd bindings, for example through the libsystemd library in C or corresponding wrappers in Go and Python, usually wrap this logic in a function called sd_listen_fds, which validates the passed file descriptors and returns them as usable socket handles. No official library function exists for PHP, which is why taking over the socket, as shown in the example above, has to be done manually through the standard sockets extension.
8. Socket activation with Docker and containers
Inside a Docker container, classic socket activation is barely usable in a meaningful way, because containers usually do not run their own systemd as PID 1, and the container runtime model assumes permanently running processes anyway, not on demand startup. A container that gets terminated once no active process remains would directly contradict the lazy start idea behind classic socket activation.
Socket activation still makes sense on the host system itself, outside containers, for example for custom helper services running alongside a Docker based Magento infrastructure. Anyone wanting to replicate the lazy start idea inside a container environment usually ends up with Kubernetes native mechanisms such as scale to zero instead, which are conceptually similar but technically independent of systemd.
9. Debugging and troubleshooting socket activation
The command systemctl list-sockets shows every active socket unit together with its associated service and current activation state, a good first stop to check whether an expected socket is even open. In addition, systemctl status php-worker.socket shows details about the socket itself, while systemctl status php-worker.service reveals the actual state of the service behind it.
If the service does not start as expected on the first connection, journalctl -u php-worker.service provides the relevant error messages. A common stumbling block is a mismatched naming convention between the socket and service files, or a forgotten Sockets= directive when file names differ, which leads systemd to open the socket correctly but find no matching service once a connection arrives.
# Show every socket unit and its activation state
systemctl list-sockets
# Check the logs of the underlying service after startup problems
journalctl -u php-worker.service -e
| Mode | Instances per Connection | Typical Use | Overhead |
|---|---|---|---|
| Accept=no | One permanent instance for all connections | Applications with their own event loop | Low, a single process start overall |
| Accept=yes | A new instance per connection | Simple, stateless scripts | High under many connections |
| ListenStream (TCP) | Depends on the accept mode | Network wide reachability | Low to medium |
| ListenStream (Unix socket) | Depends on the accept mode | Local inter process communication | Very low |
Mironsoft
Server administration, Docker hosts, and performance tuning
Linux servers nobody on the team really understands anymore?
We handle setup, hardening, and performance tuning of Linux servers and Docker hosts for Magento deployments, documented and traceable instead of grown and unclear.
Server Audit
Review the existing server configuration for security gaps and performance bottlenecks.
Docker Host Setup
Set up and secure production-ready Docker environments for Magento cleanly.
Monitoring & Tuning
Measure resource usage and tune systemd, kernel, and services with purpose.
10. Summary
Socket Activation
Basic principle
systemd opens the socket, the service only starts on demand
Core command
systemctl list-sockets for an overview of active sockets
Recommended mode
Accept=no for applications with their own event loop
Biggest benefit
Faster boot through parallel socket opening without full startup