Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

systemd: Manage Services on Linux

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.

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.

Service Lifecycle StatesLoadedUnit file parsedActiveProcess runningFailedExit code ≠ 0EnabledSymlinked to targetstartcrash / errorenablereset-failedEnable persists across reboots; Active is runtime-only
Understanding the distinction between runtime (Active) and persistent (Enabled) states prevents post-reboot outages when managing systemd services.

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 ExecStart path and execute permissions.
  • 200/CHDIR: WorkingDirectory doesn't exist or user lacks access.
  • 137/KILL: OOM killer terminated the process. Check dmesg | grep oom and 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.

Service Failure Diagnosis Flowsystemctl status <srv>Exit Code?Check Main PID line203/EXECBinary Not FoundCheck ExecStart path137/KILLOOM KilledIncrease MemoryMax1/FAILUREApplication Errorjournalctl -u <srv> -p err -n 50Fix → daemon-reload → restart
Follow this decision tree when systemctl status reports failure — exit codes point directly to root causes faster than reading logs blindly.

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.

FeaturesystemdDocker/ContainersSupervisor
ScopeFull system init + service managerApplication isolation + packagingProcess supervisor only
Boot IntegrationNative (PID 1)Requires container runtime startupMust be started by systemd/init
LoggingUnified journal with metadataContainer logs (json-file/journald driver)Separate log files per process
Resource Limitscgroups v2 nativecgroups via runtime flagsLimited (ulimit only)
Security IsolationNamespaces, seccomp, capabilitiesFull namespace + filesystem isolationNone (runs as invoking user)
ConfigurationDeclarative unit filesDockerfile + compose/kubernetes YAMLINI-style config
Best ForBare-metal/VPS system servicesMicroservices, portable deploymentsSimple 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.

Process Manager Architecture Comparisonsystemd (Bare Metal)Kernel / cgroups v2systemd (PID 1)PHP-FPM / NginxQueue WorkersDirect kernel integrationZero abstraction overheadDocker (Containerized)Host Kernelcontainerd / runcContainer RuntimeApp + DependenciesEntrypoint ScriptPortable, isolated environmentsHigher resource overheadSupervisor (Nested)Host systemd / initsupervisord daemonManaged Process GroupNo cgroup isolationLimited security featuresSimple multi-process mgmtLegacy / container-internal use
Choose systemd for bare-metal/VPS deployments, Docker for portable microservices, and Supervisor only for legacy systems or inside containers where systemd is unavailable.

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.

Frequently Asked Questions

Systemd is a system and service manager for Linux that initializes user space, manages processes, sockets, and mounts. It replaces SysVinit to provide parallel startup, dependency tracking, cgroup-based resource control, and unified logging via journald. On Ubuntu 22.04 and 24.04 servers I manage, systemd is the default init system, making legacy init scripts obsolete for new deployments.

Create a file at /etc/systemd/system/myservice.service with [Unit], [Service], and [Install] sections. Define Description, After, ExecStart, User, Group, Restart, and WantedBy directives. Run systemctl daemon-reload after saving, then enable and start the service. Always validate syntax with systemd-analyze verify before deploying to production to catch configuration errors early.

Restart stops and starts the service, causing downtime and dropping connections. Reload sends SIGHUP or runs ExecReload to apply config changes without stopping the process. For PHP-FPM or Nginx on production web servers, always prefer reload to maintain uptime. Only use restart when binary updates or structural config changes require a full process cycle.

Zero licensing cost; systemd ships free in all major Linux distributions. Operational cost depends on sysadmin time for configuration and debugging. In Nepal, hiring a Linux admin for systemd setup typically costs NPR 3,000–8,000 (~USD 22–60) per service depending on complexity, or included in monthly maintenance retainers ranging NPR 15,000–50,000.

Use Type=notify when your application supports sd_notify() to signal readiness explicitly. This prevents systemd from marking the service active before it can actually handle requests. Type=simple assumes readiness immediately after fork, which causes race conditions for slow-starting apps like Laravel queue workers or database-dependent services during boot sequences.

Check journalctl -u servicename --no-pager -n 50 for recent logs. Inspect exit codes with systemctl status. Verify file permissions, paths in ExecStart, and environment variables. Test the command manually as the specified User. Common issues include missing directories, wrong PHP binary paths after upgrades, or SELinux/AppArmor denials. On Ubuntu servers, also check /var/log/syslog for kernel-level blocks.

Yes. Systemd provides socket activation, automatic restarts on failure, resource limits via cgroups, and integrated logging. Instead of managing separate pool configs through init, define each pool as a templated unit like php-fpm@poolname.service. This allows independent scaling, monitoring, and restart policies per pool. I use this pattern on legal-tech portals running multiple PHP versions simultaneously.

Add MemoryMax, CPUQuota, LimitNOFILE, and TasksMax under the [Service] section. These map to cgroup v2 controllers on Ubuntu 22.04+. For example, MemoryMax=512M prevents runaway Laravel queue workers from exhausting server RAM. Always test limits in staging first; overly restrictive values cause silent OOM kills. Monitor actual usage via systemd-cgtop before setting production thresholds.

Use ProtectSystem=strict, ProtectHome=yes, PrivateTmp=yes, NoNewPrivileges=true, and RestrictSUIDSGID=yes to sandbox services. Drop capabilities with CapabilityBoundingSet and restrict syscalls via SystemCallFilter. For web-facing apps like WooCommerce or Laravel APIs, these prevent lateral movement if compromised. Combine with AppArmor profiles for defense-in-depth on Ubuntu production servers.

Systemd timers offer persistent scheduling across reboots, randomized delays to avoid thundering herds, and integration with journalctl logging. Unlike cron, they respect service dependencies and can trigger on boot completion rather than fixed wall-clock time. For Laravel scheduler alternatives or backup jobs on client servers, I prefer timers for reliability and observability, reserving cron only for legacy compatibility.

Usually missing After= or Requires= directives causing startup before dependencies like MySQL or network are ready. Also check if EnvironmentFile or WorkingDirectory paths are unavailable at boot. Enable Persistent=true for timers if missed runs matter. On Ubuntu 24.04, verify that required mounts are listed in After=. Manually testing as root masks permission issues that surface under the configured User directive.

Do not rely on automatic conversion tools; they produce suboptimal units. Read the original script to understand startup logic, environment setup, and shutdown hooks. Rewrite as a native unit with proper Type, ExecStartPre for setup steps, and ExecStop for cleanup. Preserve custom environment variables via EnvironmentFile. Test thoroughly in staging. Many older Magento or WordPress setups still ship init.d scripts that need this manual migration.

Yes. Set Restart=on-failure with RestartSec=5 for initial delay, then use StartLimitIntervalSec=60 and StartLimitBurst=3 to cap retries. For exponential backoff, combine with WatchdogSec and implement sd_watchdog_enabled() in your app, or wrap execution in a script with sleep multiplication. This prevents log flooding when third-party payment gateways like eSewa or Khalti experience intermittent outages.

Run systemctl status servicename for snapshot metrics including memory and CPU. Use systemd-cgtop for live cgroup resource monitoring sorted by usage. For historical data, query journalctl for OOM events or combine with Prometheus node_exporter exposing systemd collector metrics. On production eCommerce servers, I monitor PHP-FPM and Redis units this way to correlate traffic spikes with resource exhaustion before users notice degradation.

Avoid running systemd inside containers; use Docker’s native init or tini instead. Systemd expects PID 1 privileges and cgroup access that containers restrict. However, systemd on the host manages Docker daemon itself reliably. For Laravel or Node apps in containers, let Docker handle process supervision. Reserve systemd for bare-metal or VM deployments where you control the full OS stack, such as dedicated Ubuntu servers hosting client sites.

Share this article

Quick Contact Options
Choose how you want to connect me: