
Table of Contents
By Khimananda Oli | Last reviewed: August 2026
Most teams still treat PHP-FPM as a standalone daemon managed by legacy init scripts, missing the reliability and observability gains of modern Linux. When you run PHP in production with systemd, you gain automatic restarts, precise resource control via cgroups v2, and unified logging through journald. This guide replaces outdated SysVinit habits with a production-grade systemd configuration that integrates directly with your existing server monitoring stack.
How do you configure php-fpm socket activation with systemd?
Socket activation is the single most impactful change when you run PHP in production with systemd. Instead of php-fpm binding its own socket at startup, systemd creates and owns the socket file, passing the open file descriptor to php-fpm only when needed. This eliminates race conditions during restarts and allows Nginx to queue requests briefly while workers spin up.
Create the socket unit file
Create /etc/systemd/system/php8.3-fpm.socket. The socket path must exactly match what Nginx expects in its fastcgi_pass directive. Set permissions explicitly so the www-data group can access it without world-readable bits.
[Unit]
Description=PHP 8.3 FPM Socket
[Socket]
ListenStream=/run/php/php8.3-fpm.sock
SocketUser=www-data
SocketGroup=www-data
SocketMode=0660
DirectoryMode=0755
[Install]
WantedBy=sockets.target Adjust php-fpm pool configuration
In /etc/php/8.3/fpm/pool.d/www.conf, comment out or remove any existing listen = directive. Systemd injects the socket automatically when the service starts. Keep listen.owner and listen.group commented as well; the socket unit now owns those attributes. This prevents conflicting ownership that causes "permission denied" errors during deployment.
Enable and verify socket activation
- Reload systemd:
sudo systemctl daemon-reload - Enable the socket:
sudo systemctl enable --now php8.3-fpm.socket - Verify ownership:
ls -la /run/php/php8.3-fpm.sockshould showwww-data:www-datawith modesrw-rw---- - Test activation:
sudo systemctl status php8.3-fpm.serviceshould show inactive until a request arrives
A common mistake is leaving the old php8.3-fpm.service enabled without the socket unit. Disable the standalone service first with sudo systemctl disable php8.3-fpm.service to prevent both units fighting over the same socket path.
What systemd unit settings harden php-fpm for production?
The default php-fpm service unit shipped by Ubuntu or Debian prioritizes compatibility over security. When you run PHP in production with systemd, override these defaults in a drop-in file at /etc/systemd/system/php8.3-fpm.service.d/override.conf to avoid losing changes during package upgrades.
[Service]
Type=notify
NotifyAccess=all
ExecStart=
ExecStart=/usr/sbin/php-fpm8.3 --nodaemonize --fpm-config /etc/php/8.3/fpm/php-fpm.conf
Restart=on-failure
RestartSec=3s
TimeoutStopSec=30s
# Resource isolation
MemoryMax=2G
CPUQuota=80%
TasksMax=512
# Security hardening
ProtectSystem=strict
ReadWritePaths=/var/log/php /tmp /run/php
ProtectHome=yes
NoNewPrivileges=yes
PrivateTmp=yes
RestrictSUIDSGID=yes
SystemCallFilter=@system-service
SystemCallFilter=~@privileged @resources Type=notify is critical here. PHP-FPM supports sd_notify natively since version 7.3, signaling readiness only after workers are forked and listening. Without this, systemd marks the service active before it can actually handle requests, causing health checks to pass prematurely during deploys. Pair this with application-level health endpoints for complete readiness verification.
The MemoryMax directive uses cgroups v2 to enforce a hard ceiling. Unlike memory_limit in php.ini, which applies per-request and can be bypassed by extensions or subprocesses, systemd's limit covers the entire process tree including OPcache shared memory. Set this based on your worker count: if you run 20 workers with 80MB each plus 200MB overhead, target ~1.8G. Monitor actual usage with systemd-cgtop before tightening.
How does systemd improve php-fpm logging and observability?
Legacy php-fpm setups scatter logs across /var/log/php*-fpm.log, pool-specific error logs, and slowlog files. When you run PHP in production with systemd, all output flows through journald with structured metadata. This unifies debugging with the rest of your infrastructure and enables log aggregation via tools like Graylog or Loki without custom parsers.
Redirect php-fpm logs to journald
In your pool configuration (www.conf), set:
error_log = syslog
syslog.facility = daemon
syslog.ident = php-fpm
catch_workers_output = yes
decorate_workers_output = no The decorate_workers_output = no setting is essential. By default, php-fpm prepends "[pool-name pid]" to every log line, which breaks structured parsing. Let journald handle process metadata instead. Query logs with:
- All php-fpm output:
journalctl -u php8.3-fpm.service --since "1 hour ago" - Errors only:
journalctl -u php8.3-fpm.service -p err..emerg - Specific worker crashes:
journalctl -u php8.3-fpm.service SYSLOG_IDENTIFIER=php-fpm | grep "segfault\|fatal"
Add structured context for production debugging
Journald supports custom fields that survive aggregation. Create a wrapper script or use EnvironmentFile to inject deployment metadata:
[Service]
Environment=DEPLOY_VERSION=2026.08.19-a3f2c1d
Environment=ENVIRONMENT=production
Environment=HOST_ROLE=web-tier These appear as DEPLOY_VERSION=, ENVIRONMENT= in journal entries, letting you correlate errors with specific releases without parsing log messages. Combine this with structured logging patterns in your application code for end-to-end traceability.
How does systemd compare to supervisord and Docker for PHP process management?
Teams often ask whether they should use Supervisord, Docker, or bare systemd when they run PHP in production with systemd. Each has trade-offs rooted in operational complexity, not just features.
| Criteria | systemd (native) | Supervisord | Docker / Container |
|---|---|---|---|
| Process isolation | cgroups v2, namespaces | None (shared PID namespace) | Full container isolation |
| Socket activation | Native .socket units | Not supported | Requires proxy or host networking |
| Log integration | journald (structured) | Flat files, custom tail | Container runtime logs + driver |
| Resource limits | MemoryMax, CPUQuota, TasksMax | Basic ulimit only | cgroups via runtime |
| Restart policy | on-failure, watchdog, rate-limit | autorestart=true (simple) | restart policy + orchestrator |
| Operational overhead | Zero extra packages | Extra daemon + config | Image build + registry + runtime |
| Best for | Bare-metal / VM production | Legacy apps, mixed processes | Microservices, CI parity |
For traditional LEMP stacks on Ubuntu VPS or EC2 instances, systemd wins on simplicity and integration. Supervisord made sense in 2015 when Upstart lacked dependency management; today it adds a failure domain without adding capability. Containers excel when you need environment parity between dev and prod, but introduce image pipeline complexity that many PHP monoliths don't justify. If you're already running Kubernetes, see Kubespray deployment guide; otherwise, systemd gives you 90% of the operational benefits with none of the container tax.
Run PHP in Production with systemd: Next Steps
Migrating to systemd-managed php-fpm takes under an hour but pays dividends in reliability and debuggability for years. Start with socket activation and Type=notify, then layer in resource limits and journal-based logging. Test thoroughly in staging with systemd-analyze verify before deploying. If your team needs help auditing existing PHP deployments or building compliant infrastructure for SOC 2 or ISO 27001, reach out for a consultation.