Stage 3 · Build
systemd Service Management & Resource Control
Writing Service Units
ExecStart, Type, Restart, EnvironmentFile, drop-ins, and validating units with systemd-analyze.
Unit File Anatomy
# /etc/systemd/system/myapp.service
[Unit]
Description=My Application Server
Documentation=https://docs.myapp.com
After=network.target postgresql.service
Requires=postgresql.service
Wants=redis.service
StartLimitIntervalSec=300
StartLimitBurst=5
[Service]
Type=simple
User=appuser
Group=appgroup
WorkingDirectory=/opt/myapp
Environment=NODE_ENV=production
EnvironmentFile=/etc/myapp/env
ExecStartPre=/usr/bin/test -f /opt/myapp/config.yaml
ExecStart=/opt/myapp/bin/server --config /opt/myapp/config.yaml
ExecReload=/bin/kill -HUP $MAINPID
ExecStop=/bin/kill -TERM $MAINPID
Restart=on-failure
RestartSec=5
TimeoutStartSec=30
TimeoutStopSec=30
KillMode=mixed
KillSignal=SIGTERM
StandardOutput=journal
StandardError=journal
SyslogIdentifier=myapp
[Install]
WantedBy=multi-user.targetA complete service unit has [Unit] (metadata), [Service] (configuration), and [Install] (enable behavior) sections.
Service Types
| Type | Behavior | Use Case |
|---|---|---|
| simple | Process stays in foreground | Most applications |
| forking | Process forks and parent exits | Traditional daemons |
| oneshot | Runs once, then exits | Setup scripts, timers |
| notify | Process notifies when ready | Applications with readiness |
| dbus | Activates on D-Bus name | D-Bus services |
| idle | Waits for jobs to finish | Console output services |
# Type=simple (default)
# Process stays in foreground, systemd considers it started immediately
[Service]
Type=simple
ExecStart=/usr/bin/myapp
# Type=forking
# Process forks, parent exits. systemd tracks the child.
[Service]
Type=forking
PIDFile=/run/myapp.pid
ExecStart=/usr/sbin/myapp --daemon
# Type=oneshot
# Runs once, exits. Used with RemainAfterExit=yes for state tracking.
[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/usr/bin/setup-script.sh
# Type=notify
# Process calls sd_notify() when ready. Best for applications that support it.
[Service]
Type=notify
ExecStart=/usr/bin/myappType=simple is the most common. Use Type=forking for traditional daemons that daemonize themselves. Type=notify gives the most accurate readiness detection.
Restart Policies
# Restart policies
# no — Never restart (default)
# always — Always restart
# on-success — Restart only on clean exit (code 0)
# on-failure — Restart on non-zero exit, signal, timeout, or watchdog
# on-abnormal — Restart on signal, timeout, or watchdog (not exit code)
# on-abort — Restart only on unclean signal
# on-watchdog — Restart only on watchdog timeout
# Common patterns
Restart=on-failure # Restart on errors
RestartSec=5 # Wait 5 seconds before restart
StartLimitBurst=5 # Max 5 restarts...
StartLimitIntervalSec=300 # ...within 300 seconds
# Rate limiting prevents restart loops
[Service]
Restart=on-failure
RestartSec=5
StartLimitBurst=5
StartLimitIntervalSec=300
# If restart limit is hit, the unit enters failed state
# Check with: systemctl show myapp -p NRestartsRestart=on-failure is the most common policy. StartLimitBurst prevents restart loops. Always set RestartSec to avoid rapid restart cycling.
Environment Files
# /etc/myapp/env
NODE_ENV=production
DATABASE_URL=postgresql://localhost/myapp
REDIS_URL=redis://localhost:6379
SECRET_KEY=abc123def456
PORT=8080
# In the service unit
[Service]
EnvironmentFile=/etc/myapp/env
Environment=NODE_ENV=production
EnvironmentFile=-/etc/myapp/env.local # Optional (ignore if missing)
# Reference in ExecStart
ExecStart=/opt/myapp/bin/server --port $PORT
# Multiple environment files (processed in order)
EnvironmentFile=/etc/myapp/env
EnvironmentFile=/etc/myapp/env.secrets
# Permissions
# chmod 600 /etc/myapp/env
# chown root:appuser /etc/myapp/envEnvironmentFile sets environment variables from a file. Use - prefix for optional files. Secrets should be in separate files with restricted permissions.
Drop-in Overrides
# Create drop-in directory
sudo mkdir -p /etc/systemd/system/myapp.service.d/
# Create override file
cat > /etc/systemd/system/myapp.service.d/override.conf << 'EOF'
[Service]
Environment=DEBUG=true
LimitNOFILE=65536
Nice=5
[Unit]
After=redis.service
EOF
# Reload systemd
sudo systemctl daemon-reload
# View effective configuration
systemctl cat myapp.service
# View all overrides
systemctl show myapp.service -p DropInPaths
# Reset to original
sudo rm /etc/systemd/system/myapp.service.d/override.conf
sudo systemctl daemon-reloadDrop-in overrides modify unit files without editing the original. This preserves package updates. Files in /etc/systemd/system/ override /lib/systemd/system/.
Validating Units
# Verify syntax
systemd-analyze verify /etc/systemd/system/myapp.service
# Check configuration
systemd-analyze cat-config systemd/system/myapp.service
# Test without enabling
systemctl start myapp.service
systemctl status myapp.service
systemctl stop myapp.service
# Analyze dependencies
systemd-analyze dot myapp.service | dot -Tpng > deps.png
# Check for errors
journalctl -u myapp.service -n 50
# Validate all unit files
systemd-analyze verify /etc/systemd/system/*.serviceAlways verify unit files before deploying. systemd-analyze verify catches syntax errors. Test by starting the service manually before enabling.
Never edit /lib/systemd/system/ unit files directly. Create drop-ins in /etc/systemd/system/ for local overrides. This preserves package updates.
When using Type=forking, you must specify PIDFile= so systemd can track the child process. Without it, systemd may kill the wrong process.
Mark this lesson complete to store local progress and unlock a cleaner resume path the next time you visit.