Journal
Systemd for Web Developers: Services, Timers, and Journals
If you deploy to Linux, you’re using systemd whether you know it or not. It manages your Nginx process, your database, and probably your application server. Understanding it turns “it works on my machine” into “I know exactly why it works in production.”
Unit Files: The Building Block
Everything in systemd revolves around unit files. A service unit for a Node.js app looks like this:
# /etc/systemd/system/myapp.service
[Unit]
Description=My Node App
After=network.target
[Service]
User=deploy
WorkingDirectory=/var/www/myapp
ExecStart=/usr/bin/node server.js
Restart=on-failure
RestartSec=5
Environment=NODE_ENV=production
[Install]
WantedBy=multi-user.target
Enable and start it:
sudo systemctl daemon-reload
sudo systemctl enable --now myapp.service
The key directives:
- After — start order dependency (doesn’t imply requirement)
- Restart=on-failure — automatically restart on non-zero exit
- RestartSec — wait before restarting to avoid tight crash loops
- WantedBy=multi-user.target — start on normal boot
Timers: Cron’s Replacement
Systemd timers offer better logging, dependency management, and randomized delay to avoid thundering herds.
Create a timer unit alongside your service:
# /etc/systemd/system/backup.timer
[Unit]
Description=Nightly database backup
[Timer]
OnCalendar=*-*-* 03:00:00
RandomizedDelaySec=900
Persistent=true
[Install]
WantedBy=timers.target
# /etc/systemd/system/backup.service
[Unit]
Description=Database backup job
[Service]
Type=oneshot
User=deploy
ExecStart=/opt/scripts/backup.sh
Enable the timer (not the service):
sudo systemctl enable --now backup.timer
List all active timers:
systemctl list-timers --all
Persistent=true means if the server was off when the timer should have fired, it runs immediately on next boot.
Journalctl: Reading Logs
Forget tailing random files in /var/log. Journalctl centralizes everything.
# Logs for your service
journalctl -u myapp.service
# Follow live output
journalctl -u myapp.service -f
# Logs since last boot
journalctl -u myapp.service -b
# Logs from the last hour
journalctl -u myapp.service --since "1 hour ago"
# Kernel messages only
journalctl -k
Filter by priority:
# Errors and above
journalctl -u myapp.service -p err
Log Retention
Control disk usage in /etc/systemd/journald.conf:
SystemMaxUse=500M
MaxRetentionSec=30day
Then restart: sudo systemctl restart systemd-journald.
Practical Patterns
Running a PHP-FPM Pool
PHP-FPM already ships with a systemd unit. You can override specific directives without editing the vendor file:
sudo systemctl edit php8.3-fpm.service
This creates a drop-in override at /etc/systemd/system/php8.3-fpm.service.d/override.conf. Add limits:
[Service]
MemoryMax=512M
CPUQuota=80%
Checking Why Something Failed
systemctl status myapp.service
journalctl -u myapp.service -n 50 --no-pager
The status output shows the last few log lines, the PID, memory usage, and the active state. That’s usually enough to diagnose the issue.
Dependency Graphs
See what your service depends on:
systemctl list-dependencies myapp.service
The Mental Model
Think of systemd as a process supervisor with opinions. It wants to own the lifecycle of your application — starting, stopping, restarting, and logging. Fight it and you’ll have a bad time. Lean into it and you get process supervision, log aggregation, resource limits, and scheduling in one tool that’s already installed.