Systemd Services: Run and Manage Background Processes on Linux
If you’ve ever needed to run a script, web app, or service in the background on Linux — and have it start automatically on boot, restart on crash, and log output properly — systemd is how you do it. It’s the init system and service manager on virtually every modern Linux distribution.
This guide covers the practical essentials: managing services with systemctl, creating your own service files, viewing logs with journalctl, and handling timers as a modern alternative to cron.
What is Systemd?
Systemd is the first process that runs when Linux boots (PID 1). It manages:
- Services: Background processes (daemons) like nginx, sshd, Docker
- Targets: Groups of services that define system states (like runlevels)
- Timers: Scheduled tasks (modern replacement for cron)
- Mounts: Filesystem mount points
- Sockets: Network socket activation
Every service is defined by a unit file — a simple text configuration file.
Managing Services with systemctl
Basic Commands
# Start a service
sudo systemctl start nginx
# Stop a service
sudo systemctl stop nginx
# Restart a service
sudo systemctl restart nginx
# Reload configuration without full restart
sudo systemctl reload nginx
# Check status
systemctl status nginx
Enable/Disable Auto-Start on Boot
# Enable (start automatically on boot)
sudo systemctl enable nginx
# Disable (don't start on boot)
sudo systemctl disable nginx
# Enable AND start immediately
sudo systemctl enable --now nginx
# Disable AND stop immediately
sudo systemctl disable --now nginx
Check if a Service is Running
# Is it active?
systemctl is-active nginx
# Output: active
# Is it enabled on boot?
systemctl is-enabled nginx
# Output: enabled
# Is it failed?
systemctl is-failed nginx
List Services
# List all running services
systemctl list-units --type=service --state=running
# List all services (including inactive)
systemctl list-units --type=service --all
# List enabled services
systemctl list-unit-files --type=service --state=enabled
# List failed services
systemctl --failed
Creating a Custom Service
This is where systemd really shines. You can turn any script or application into a managed service.
Step 1: Create the Service File
Service files live in /etc/systemd/system/. Create one:
sudo nano /etc/systemd/system/myapp.service
Basic Service File
[Unit]
Description=My Application
After=network.target
[Service]
Type=simple
User=www-data
Group=www-data
WorkingDirectory=/opt/myapp
ExecStart=/usr/bin/node /opt/myapp/server.js
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
Step 2: Reload and Start
# Reload systemd to pick up the new file
sudo systemctl daemon-reload
# Start the service
sudo systemctl start myapp
# Enable on boot
sudo systemctl enable myapp
# Check status
systemctl status myapp
Service File Anatomy
[Unit] Section
[Unit]
Description=Human-readable description
Documentation=https://example.com/docs
After=network.target postgresql.service
Requires=postgresql.service
Wants=redis.service
- Description: What the service does
- After: Start this service after the listed units
- Requires: Hard dependency — if the required unit fails, this unit fails too
- Wants: Soft dependency — if the wanted unit fails, this unit still starts
[Service] Section
[Service]
Type=simple
User=appuser
Group=appgroup
WorkingDirectory=/opt/myapp
ExecStart=/opt/myapp/start.sh
ExecStop=/opt/myapp/stop.sh
ExecReload=/bin/kill -HUP $MAINPID
Restart=always
RestartSec=10
Environment=NODE_ENV=production
EnvironmentFile=/opt/myapp/.env
StandardOutput=journal
StandardError=journal
Type options:
| Type | Description |
|---|---|
simple | Default. The process started by ExecStart is the main process |
forking | The process forks and the parent exits (traditional daemons) |
oneshot | The process exits after doing its job (like a script) |
notify | Like simple, but the service sends a notification when ready |
Restart options:
| Value | Restarts on… |
|---|---|
no | Never restart |
on-failure | Non-zero exit code, signal, timeout |
on-abnormal | Signal, timeout |
always | Always restart, regardless of exit status |
[Install] Section
[Install]
WantedBy=multi-user.target
- WantedBy=multi-user.target: Start in normal multi-user mode (most common)
- WantedBy=graphical.target: Start in graphical/desktop mode
Real-World Examples
Node.js Application
[Unit]
Description=Node.js Express App
After=network.target
[Service]
Type=simple
User=node
WorkingDirectory=/opt/express-app
ExecStart=/usr/bin/node app.js
Restart=always
RestartSec=10
Environment=NODE_ENV=production
Environment=PORT=3000
[Install]
WantedBy=multi-user.target
Python Flask with Gunicorn
[Unit]
Description=Flask App with Gunicorn
After=network.target
[Service]
Type=simple
User=flask
Group=flask
WorkingDirectory=/opt/flask-app
ExecStart=/opt/flask-app/venv/bin/gunicorn --workers 4 --bind 0.0.0.0:8000 app:app
Restart=always
RestartSec=5
EnvironmentFile=/opt/flask-app/.env
[Install]
WantedBy=multi-user.target
Go Binary
[Unit]
Description=Go Web Service
After=network.target
[Service]
Type=simple
User=goapp
ExecStart=/opt/goapp/server
Restart=on-failure
RestartSec=5
LimitNOFILE=65535
[Install]
WantedBy=multi-user.target
Simple Bash Script
[Unit]
Description=Backup Script
After=network.target
[Service]
Type=oneshot
ExecStart=/opt/scripts/backup.sh
StandardOutput=journal
Viewing Logs with journalctl
Systemd captures all service output (stdout and stderr) in the journal. Use journalctl to view it.
# View logs for a specific service
journalctl -u nginx
# Follow logs in real-time (like tail -f)
journalctl -u myapp -f
# Show logs since last boot
journalctl -u myapp -b
# Show last 50 lines
journalctl -u myapp -n 50
# Show logs from the last hour
journalctl -u myapp --since "1 hour ago"
# Show logs between dates
journalctl -u myapp --since "2024-01-01" --until "2024-01-02"
# Show only errors
journalctl -u myapp -p err
# Output as JSON
journalctl -u myapp -o json-pretty
# Show disk usage of journal
journalctl --disk-usage
# Clean up old logs (keep last 1GB)
sudo journalctl --vacuum-size=1G
# Clean up logs older than 30 days
sudo journalctl --vacuum-time=30d
Systemd Timers (Modern Cron)
Systemd timers can replace cron jobs with better logging and dependency management.
Create a Timer
Service file (/etc/systemd/system/backup.service):
[Unit]
Description=Daily Backup
[Service]
Type=oneshot
ExecStart=/opt/scripts/backup.sh
Timer file (/etc/systemd/system/backup.timer):
[Unit]
Description=Run backup daily
[Timer]
OnCalendar=daily
Persistent=true
[Install]
WantedBy=timers.target
Enable the timer:
sudo systemctl enable --now backup.timer
# List active timers
systemctl list-timers
# Check when it last ran
systemctl status backup.timer
OnCalendar examples:
OnCalendar=hourly
OnCalendar=daily
OnCalendar=weekly
OnCalendar=*-*-* 03:00:00 # Every day at 3 AM
OnCalendar=Mon *-*-* 09:00:00 # Every Monday at 9 AM
OnCalendar=*-*-* *:*:00 # Every minute
OnCalendar=*-*-01 00:00:00 # First of every month
Security Hardening
Systemd offers built-in security features to restrict what a service can do:
[Service]
# Run as unprivileged user
User=myapp
Group=myapp
# Filesystem restrictions
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/opt/myapp/data
# Network restrictions
PrivateNetwork=false
# No new privileges
NoNewPrivileges=true
# Restrict system calls
SystemCallArchitectures=native
# Private /tmp
PrivateTmp=true
# Read-only /etc
ProtectEtc=true
Check the security score of a service:
systemd-analyze security myapp.service
Troubleshooting
# Check why a service failed
systemctl status myapp
journalctl -u myapp -n 30 --no-pager
# Verify a service file for syntax errors
systemd-analyze verify /etc/systemd/system/myapp.service
# Show boot time analysis
systemd-analyze blame
# Show service dependencies
systemctl list-dependencies myapp
# Reset a failed service
sudo systemctl reset-failed myapp
Quick Reference
| Command | Action |
|---|---|
systemctl start <svc> | Start service |
systemctl stop <svc> | Stop service |
systemctl restart <svc> | Restart service |
systemctl status <svc> | Check status |
systemctl enable <svc> | Auto-start on boot |
systemctl disable <svc> | Remove from boot |
systemctl daemon-reload | Reload unit files |
journalctl -u <svc> -f | Follow logs |
systemctl list-units | List all units |
systemctl --failed | Show failed units |
Summary
Systemd is the standard way to manage services on Linux. Once you know how to write a service file, you can turn any application into a reliable background process with automatic restarts, logging, and boot integration.
Key resources:
- systemd man page:
man systemd.service - Freedesktop docs: https://www.freedesktop.org/software/systemd/man/
- ArchWiki systemd: https://wiki.archlinux.org/title/Systemd
Create a service file, daemon-reload, enable --now, and your application runs forever.