
August 20, 2026
10 min read
Table of Contents
By Kokil Thapa | Last reviewed: August 2026
When your Laravel queue worker silently dies at 3 AM or PHP-FPM fails to restart after a deploy, knowing systemd: Manage Services on Linux is the difference between minutes of downtime and hours of debugging. Modern Ubuntu 22.04 and 24.04 servers rely entirely on systemd as the init system, service manager, and logging backbone. Whether you are maintaining a legal-tech portal or a high-traffic WooCommerce store, mastering systemctl, unit files, and journalctl is non-negotiable for reliable production operations.
systemctl start|stop|restart|enable for lifecycle control, edit unit files in /etc/systemd/system/ for custom configurations, and diagnose issues with journalctl -u <service>. Always run systemctl daemon-reload after modifying unit files to apply changes without rebooting the server.For developers transitioning from shared hosting or older SysVinit systems, the shift to systemd can feel opaque. However, once understood, it provides deterministic control over application processes. If you are building infrastructure for Laravel applications in Nepal or managing global client servers, this guide covers the exact workflows I use daily. We will move beyond basic start/stop commands into resource limits, security sandboxing, and dependency management that keep production systems stable under load.
How do you control service lifecycle with systemctl commands?
The systemctl binary is your primary interface for interacting with systemd. While many tutorials list dozens of subcommands, in practice, you only need a focused subset for day-to-day web server administration. Understanding the distinction between runtime state and persistent state is critical here.
Essential Runtime Commands
Runtime commands affect the current session only. If the server reboots, these states reset unless explicitly persisted.
systemctl start nginx: Immediately starts the service.systemctl stop nginx: Gracefully stops the service (sends SIGTERM).systemctl restart nginx: Stops then starts; drops active connections.systemctl reload nginx: Reloads configuration without dropping connections (preferred for web servers).systemctl status nginx: Shows active state, PID, memory usage, and recent log lines.
A common mistake on production servers is using restart when reload suffices. For Nginx and Apache, reload spawns new workers with updated config while letting old workers finish serving requests. A full restart kills everything immediately, causing visible errors for users mid-request.
Persistent Enablement
To ensure a service survives reboots, you must enable it. This creates symlinks in /etc/systemd/system/multi-user.target.wants/.
# Enable service to start on boot
sudo systemctl enable php8.3-fpm
# Start immediately AND enable for future boots
sudo systemctl enable --now php8.3-fpm
# Disable auto-start (does not stop current instance)
sudo systemctl disable php8.3-fpm I always use enable --now during initial server setup. It prevents the embarrassing scenario where you configure a perfect queue worker, test it successfully, reboot the server for kernel updates, and discover nothing is running because you forgot the separate enable step.
How do you write custom systemd unit files for web apps?
Package managers provide unit files for standard software like Nginx or MySQL. But when deploying custom Laravel applications, Node.js APIs, or Python scripts, you must write your own. Custom units belong in /etc/systemd/system/, never in /lib/systemd/system/ which is reserved for vendor packages and gets overwritten during upgrades.
Anatomy of a Production Laravel Queue Worker
This unit file reflects patterns I use for legal-tech portals and e-commerce platforms where background processing reliability directly impacts revenue.
# /etc/systemd/system/laravel-queue.service
[Unit]
Description=Laravel Queue Worker (Production)
After=network.target mysql.service redis.service
Requires=mysql.service redis.service
[Service]
User=www-data
Group=www-data
WorkingDirectory=/var/www/myapp/current
Environment="APP_ENV=production"
EnvironmentFile=/var/www/myapp/shared/.env
ExecStart=/usr/bin/php8.3 artisan queue:work redis --sleep=3 --tries=3 --max-time=3600
Restart=always
RestartSec=5
StandardOutput=journal
StandardError=journal
SyslogIdentifier=laravel-queue
# Resource Limits
MemoryMax=512M
CPUQuota=80%
# Security Hardening
NoNewPrivileges=true
ProtectSystem=strict
ReadWritePaths=/var/www/myapp/shared/storage /var/www/myapp/shared/logs
PrivateTmp=true
[Install]
WantedBy=multi-user.target Critical Directives Explained
Dependency Management: The After= directive ensures ordering but does not enforce requirement. Adding Requires= means if MySQL fails to start, systemd won't even attempt to start your queue worker. This prevents cryptic "connection refused" errors filling your logs during boot races.
EnvironmentFile vs Environment: Never hardcode secrets in unit files. Use EnvironmentFile= pointing to your application's .env. The explicit Environment="APP_ENV=production" acts as a safety override, ensuring the worker never accidentally runs in debug mode even if the .env file is misconfigured.
Restart Policy: Restart=always with RestartSec=5 handles transient failures gracefully. Without the delay, a broken config can cause systemd to restart the service hundreds of times per minute, hammering your CPU and logs. For queue workers specifically, --max-time=3600 in the ExecStart command forces graceful worker recycling every hour to prevent memory leaks, complementing systemd's process supervision.
After creating or editing any unit file, you must reload the systemd manager configuration:
sudo systemctl daemon-reload
sudo systemctl enable --now laravel-queue.service Skipping daemon-reload is the most frequent reason edits appear to have no effect. Systemd caches parsed unit files aggressively; this command forces re-parsing without affecting running services.
How do you debug failing services using journalctl?
Systemd centralizes all service output into the journal. Traditional log files in /var/log/ often don't exist for custom services unless explicitly configured. Learning journalctl filters saves enormous time during incident response.
Targeted Log Queries
# Live tail (like tail -f)
journalctl -u laravel-queue -f
# Last 100 lines, no pager
journalctl -u laravel-queue -n 100 --no-pager
# Logs since last boot only
journalctl -u laravel-queue -b
# Time-range filtering
journalctl -u laravel-queue --since "2026-08-20 14:00:00" --until "2026-08-20 15:30:00"
# Filter by priority (error and worse)
journalctl -u laravel-queue -p err
# Combine with grep for specific patterns
journalctl -u laravel-queue --no-pager | grep "SQLSTATE" On a recent project involving payment gateway integration, webhook handlers were failing intermittently. Using journalctl -u webhook-handler -p warning --since today revealed timeout errors that weren't appearing in application logs because the process was being killed before it could flush buffers. Systemd captured the SIGTERM signal and final stderr output that the application itself never logged.
Understanding Exit Codes
When systemctl status shows failed, check the exit code. Systemd reports both the numeric code and a human-readable description:
- 1/FAILURE: Generic application error. Check application logs.
- 203/EXEC: Binary not found or permission denied. Verify
ExecStartpath and execute permissions. - 200/CHDIR: WorkingDirectory doesn't exist or user lacks access.
- 137/KILL: OOM killer terminated the process. Check
dmesg | grep oomand increase MemoryMax. - 143/TERM: Graceful shutdown via SIGTERM. Usually normal during restarts.
Code 203 is especially common after PHP version upgrades. If you upgrade from PHP 8.2 to 8.3 but forget to update ExecStart path from /usr/bin/php8.2 to /usr/bin/php8.3, systemd cannot find the binary. Always verify paths with which php8.3 before updating unit files.
How do you secure and resource-limit systemd services?
Default systemd units run with minimal isolation. On shared servers hosting multiple client sites, or any system handling sensitive data like legal documents or payment information, hardening is essential. Systemd provides cgroup-based resource limits and namespace isolation without requiring containers.
Resource Boundaries
Unchecked services can consume all available resources, starving other processes. Set explicit boundaries:
[Service]
# Prevent single service from consuming all RAM
MemoryMax=1G
MemoryHigh=768M
# CPU quota (percentage of one core)
CPUQuota=50%
# Limit concurrent tasks
TasksMax=64
# IO throttling for disk-heavy workers
IOWeight=50 MemoryHigh triggers reclaim pressure before hitting the hard MemoryMax limit, giving the application a chance to free memory gracefully. This is preferable to abrupt OOM kills for database-backed workers that might lose transaction state.
Security Sandboxing
These directives restrict what the service can access, limiting blast radius if compromised:
[Service]
# Prevent gaining new privileges via setuid/setgid
NoNewPrivileges=true
# Make filesystem read-only except whitelisted paths
ProtectSystem=strict
ReadWritePaths=/var/www/app/storage /tmp
# Isolate /tmp from other services
PrivateTmp=true
# Hide other users' processes
ProtectProc=invisible
# Restrict system calls (advanced)
SystemCallFilter=@system-service
SystemCallFilter=~@privileged @resources I apply NoNewPrivileges=true and PrivateTmp=true to every custom service by default. They cost nothing in compatibility and eliminate entire classes of privilege escalation attacks. ProtectSystem=strict requires careful testing — start with ProtectSystem=full which allows writes to /var and /etc, then tighten to strict once you've identified all required write paths through testing.
For teams managing DevOps automation in Nepal, these security settings should be templated. Create a drop-in directory /etc/systemd/system/laravel-app@.service.d/hardening.conf with shared security directives, then include it across all app instances. This ensures consistent hardening without duplicating configuration.
How does systemd compare to Docker and Supervisor for process management?
A frequent question from developers setting up new infrastructure: why use systemd when Docker or Supervisor exist? Each tool solves different problems, and understanding tradeoffs prevents architectural mistakes.
| Feature | systemd | Docker/Containers | Supervisor |
|---|---|---|---|
| Scope | Full system init + service manager | Application isolation + packaging | Process supervisor only |
| Boot Integration | Native (PID 1) | Requires container runtime startup | Must be started by systemd/init |
| Logging | Unified journal with metadata | Container logs (json-file/journald driver) | Separate log files per process |
| Resource Limits | cgroups v2 native | cgroups via runtime flags | Limited (ulimit only) |
| Security Isolation | Namespaces, seccomp, capabilities | Full namespace + filesystem isolation | None (runs as invoking user) |
| Configuration | Declarative unit files | Dockerfile + compose/kubernetes YAML | INI-style config |
| Best For | Bare-metal/VPS system services | Microservices, portable deployments | Simple process groups within containers |
In my experience, systemd is the right choice for traditional VPS deployments where you control the host OS directly. When deploying Symfony or Laravel on Ubuntu VPS, systemd integrates seamlessly with package-managed PHP-FPM, Nginx, and databases. Containers add overhead and complexity that isn't justified for single-application servers or small-scale deployments common in Nepal's SMB market.
However, if you're already running Kubernetes or Docker Compose, let the container orchestrator handle process management. Running systemd inside containers is an anti-pattern. Use Supervisor only when you need simple multi-process management inside a container where systemd isn't available.
Practical Takeaways for Production Reliability
Mastering systemd: Manage Services on Linux transforms how you operate web infrastructure. Start with correct lifecycle commands and proper unit file structure. Layer in journalctl proficiency for fast debugging. Apply resource limits and security hardening before problems force you to. Choose systemd over containers when simplicity and direct OS integration matter more than portability.
For teams operating in Nepal's infrastructure landscape — where VPS deployments dominate and operational budgets are tight — systemd expertise delivers outsized reliability gains without additional tooling costs. The patterns described here come from maintaining production legal-tech portals, e-commerce platforms, and API services handling real transactions daily.
If you need help designing resilient service architectures, troubleshooting persistent failures, or hardening existing deployments, reach out to discuss your infrastructure needs. Whether it's a single misbehaving queue worker or a complete service topology review, getting systemd fundamentals right prevents countless 3 AM incidents.

