Systemd is how modern Linux manages services. It starts your applications at boot, restarts them when they crash, and handles dependencies between services.

Understanding systemd transforms “it works when I run it manually” into “it runs reliably in production.”

Basic Service Unit

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# /etc/systemd/system/myapp.service
[Unit]
Description=My Application
After=network.target

[Service]
Type=simple
User=appuser
WorkingDirectory=/opt/myapp
ExecStart=/opt/myapp/bin/server
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target
1
2
3
4
# Enable and start
sudo systemctl daemon-reload
sudo systemctl enable myapp
sudo systemctl start myapp

Service Types

simple (default): Process started by ExecStart is the main process.

1
2
3
[Service]
Type=simple
ExecStart=/usr/bin/myapp

forking: Traditional daemon that forks and exits.

1
2
3
4
[Service]
Type=forking
PIDFile=/var/run/myapp.pid
ExecStart=/usr/bin/myapp --daemon

oneshot: Runs once and exits (scripts, setup tasks).

1
2
3
4
[Service]
Type=oneshot
ExecStart=/opt/myapp/setup.sh
RemainAfterExit=yes

notify: Process signals readiness to systemd.

1
2
3
4
[Service]
Type=notify
ExecStart=/usr/bin/myapp
# App must call sd_notify(0, "READY=1")

Restart Policies

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
[Service]
# Restart options
Restart=always          # Always restart
Restart=on-failure      # Only on non-zero exit
Restart=on-abnormal     # On signal, timeout, watchdog
Restart=no              # Never restart

RestartSec=5            # Wait 5 seconds before restart
StartLimitBurst=5       # Max 5 restarts...
StartLimitIntervalSec=60  # ...per 60 seconds

After hitting the limit, service enters failed state. Reset with:

1
sudo systemctl reset-failed myapp

Environment Variables

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
[Service]
# Direct assignment
Environment="NODE_ENV=production"
Environment="PORT=3000"

# From file
EnvironmentFile=/etc/myapp/env

# Multiple files
EnvironmentFile=-/etc/myapp/defaults  # - means optional
EnvironmentFile=/etc/myapp/overrides
1
2
3
# /etc/myapp/env
DATABASE_URL=postgres://localhost/myapp
API_KEY=secret123

Resource Limits

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
[Service]
# Memory limits
MemoryMax=512M
MemoryHigh=400M

# CPU limits
CPUQuota=50%

# File descriptors
LimitNOFILE=65535

# Process limits
LimitNPROC=4096

# Nice value
Nice=10

Security Hardening

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
[Service]
# Run as unprivileged user
User=appuser
Group=appgroup

# Filesystem restrictions
ProtectSystem=strict      # /usr, /boot read-only
ProtectHome=yes           # No access to /home
ReadWritePaths=/var/lib/myapp

# Namespace isolation
PrivateTmp=yes           # Private /tmp
PrivateDevices=yes       # No access to devices
PrivateNetwork=no        # Usually need network

# Capabilities
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_BIND_SERVICE

# System call filtering
SystemCallFilter=@system-service
SystemCallFilter=~@privileged @resources

# No new privileges
NoNewPrivileges=yes

Pre/Post Commands

1
2
3
4
5
6
7
8
9
[Service]
ExecStartPre=/opt/myapp/pre-start.sh
ExecStart=/opt/myapp/bin/server
ExecStartPost=/opt/myapp/post-start.sh
ExecStop=/opt/myapp/graceful-stop.sh
ExecStopPost=/opt/myapp/cleanup.sh

# Timeout for stop
TimeoutStopSec=30

Dependencies

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
[Unit]
Description=My Application
Documentation=https://docs.myapp.com

# Start after these
After=network.target postgresql.service redis.service

# Require these (fail if they fail)
Requires=postgresql.service

# Want these (don't fail if they fail)
Wants=redis.service

# If this starts, start me too
WantedBy=multi-user.target

# Part of a target
PartOf=myapp.target

Socket Activation

Start service only when connection arrives:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# /etc/systemd/system/myapp.socket
[Unit]
Description=MyApp Socket

[Socket]
ListenStream=8080
Accept=no

[Install]
WantedBy=sockets.target
1
2
3
4
5
6
7
8
9
# /etc/systemd/system/myapp.service
[Unit]
Description=MyApp Service
Requires=myapp.socket

[Service]
Type=simple
ExecStart=/opt/myapp/bin/server
# Receives socket from systemd

Timers (Cron Replacement)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# /etc/systemd/system/backup.timer
[Unit]
Description=Daily Backup Timer

[Timer]
OnCalendar=*-*-* 02:00:00
Persistent=true

[Install]
WantedBy=timers.target
1
2
3
4
5
6
7
# /etc/systemd/system/backup.service
[Unit]
Description=Backup Service

[Service]
Type=oneshot
ExecStart=/opt/backup/run.sh
1
2
3
sudo systemctl enable backup.timer
sudo systemctl start backup.timer
systemctl list-timers

Viewing Logs

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# All logs for a service
journalctl -u myapp

# Follow logs (like tail -f)
journalctl -u myapp -f

# Since last boot
journalctl -u myapp -b

# Last 100 lines
journalctl -u myapp -n 100

# With timestamps
journalctl -u myapp --output=short-precise

# Filter by time
journalctl -u myapp --since "1 hour ago"
journalctl -u myapp --since "2024-01-15" --until "2024-01-16"

Common Commands

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# Status
systemctl status myapp

# Start/stop/restart
systemctl start myapp
systemctl stop myapp
systemctl restart myapp
systemctl reload myapp  # If supported

# Enable/disable at boot
systemctl enable myapp
systemctl disable myapp

# Check if enabled
systemctl is-enabled myapp

# List all services
systemctl list-units --type=service

# List failed services
systemctl --failed

# Reload systemd after editing units
systemctl daemon-reload

Debugging

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
# Why won't it start?
systemctl status myapp
journalctl -u myapp -n 50

# Check unit file syntax
systemd-analyze verify /etc/systemd/system/myapp.service

# See resolved unit file
systemctl cat myapp

# See all settings (including defaults)
systemctl show myapp

# Boot analysis
systemd-analyze blame  # Slowest units
systemd-analyze critical-chain myapp  # Dependency chain

Template Units

Create multiple instances from one template:

1
2
3
4
5
6
# /etc/systemd/system/myapp@.service
[Unit]
Description=MyApp instance %i

[Service]
ExecStart=/opt/myapp/bin/server --port=%i
1
2
3
systemctl start myapp@8080
systemctl start myapp@8081
systemctl start myapp@8082

Systemd handles the tedious parts of service management: starting at boot, restarting on failure, managing dependencies, logging output. Learn its configuration options and your applications become self-healing.

Write a proper unit file. Set restart policies. Add security hardening. Use journalctl for logs. The service that runs reliably without babysitting is the service you can forget about — until you check on it and it’s still running.