systemd Service Management¶
systemd is the init system and service manager for Ubuntu. Understanding systemd is essential for managing services, configuring system behavior, and implementing security controls.
systemd Fundamentals¶
What systemd Manages¶
┌─────────────────────────────────────────────────────────────┐
│ systemd │
├─────────────────────────────────────────────────────────────┤
│ Services │ Timers │ Mounts │ Targets │
│ (.service) │ (.timer) │ (.mount) │ (.target) │
├─────────────────────────────────────────────────────────────┤
│ Sockets │ Devices │ Paths │ Slices │
│ (.socket) │ (.device) │ (.path) │ (.slice) │
└─────────────────────────────────────────────────────────────┘
Unit Types¶
| Type | Purpose | Example |
|---|---|---|
| .service | Daemon or process | nginx.service |
| .socket | Socket activation | sshd.socket |
| .timer | Scheduled tasks (cron replacement) | apt-daily.timer |
| .mount | Filesystem mounts | home.mount |
| .target | Grouping of units | multi-user.target |
| .path | File/directory watching | myapp.path |
| .slice | Resource control groups | user.slice |
Service Management¶
Basic Commands¶
# Start/stop/restart
sudo systemctl start nginx
sudo systemctl stop nginx
sudo systemctl restart nginx
sudo systemctl reload nginx # Reload config without restart
# Enable/disable (auto-start at boot)
sudo systemctl enable nginx
sudo systemctl disable nginx
# Combined enable and start
sudo systemctl enable --now nginx
# Status and info
systemctl status nginx
systemctl is-active nginx
systemctl is-enabled nginx
systemctl is-failed nginx
# List services
systemctl list-units --type=service
systemctl list-units --type=service --state=running
systemctl list-units --type=service --state=failed
systemctl list-unit-files --type=service
Service Status Interpretation¶
$ systemctl status nginx
● nginx.service - A high performance web server
Loaded: loaded (/lib/systemd/system/nginx.service; enabled; vendor preset: enabled)
Active: active (running) since Mon 2024-01-15 10:30:00 UTC; 2h ago
Docs: man:nginx(8)
Process: 1234 ExecStart=/usr/sbin/nginx -g daemon on; (code=exited, status=0/SUCCESS)
Main PID: 1235 (nginx)
Tasks: 3 (limit: 4915)
Memory: 8.5M
CPU: 150ms
CGroup: /system.slice/nginx.service
├─1235 "nginx: master process /usr/sbin/nginx"
└─1236 "nginx: worker process"
| Field | Meaning |
|---|---|
| Loaded | Unit file location and enable status |
| Active | Current state and uptime |
| Process | Start command and exit status |
| Main PID | Primary process ID |
| Tasks | Number of tasks in cgroup |
| Memory | Memory usage |
| CGroup | Control group hierarchy |
Unit Files¶
File Locations¶
| Location | Purpose |
|---|---|
/lib/systemd/system/ | Package-provided units |
/etc/systemd/system/ | Administrator units (override) |
/run/systemd/system/ | Runtime units |
~/.config/systemd/user/ | User units |
Unit File Structure¶
[Unit]
Description=My Application
Documentation=https://example.com/docs
After=network.target
Wants=network-online.target
Requires=postgresql.service
[Service]
Type=simple
User=myapp
Group=myapp
WorkingDirectory=/opt/myapp
ExecStart=/opt/myapp/bin/myapp
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
Service Types¶
| Type | Behavior |
|---|---|
| simple | Process started is main service (default) |
| forking | Parent exits, child continues |
| oneshot | Completes then exits (for scripts) |
| notify | Sends notification when ready |
| idle | Waits for other jobs to finish |
Common Directives¶
[Unit] Section:
| Directive | Purpose |
|---|---|
| Description | Human-readable description |
| After | Start after these units |
| Before | Start before these units |
| Requires | Hard dependency (fails if dep fails) |
| Wants | Soft dependency (doesn't fail if dep fails) |
| Conflicts | Cannot run with these units |
[Service] Section:
| Directive | Purpose |
|---|---|
| ExecStart | Main command |
| ExecStartPre | Pre-start commands |
| ExecStartPost | Post-start commands |
| ExecStop | Stop command |
| ExecReload | Reload command |
| Restart | When to restart (no, on-failure, always) |
| RestartSec | Delay before restart |
| User/Group | Run as user/group |
| WorkingDirectory | Working directory |
| Environment | Environment variables |
| EnvironmentFile | File with environment variables |
[Install] Section:
| Directive | Purpose |
|---|---|
| WantedBy | Target that wants this unit |
| RequiredBy | Target that requires this unit |
| Alias | Alternative names |
Creating Custom Services¶
Example: Node.js Application¶
[Unit]
Description=My Node.js Application
Documentation=https://github.com/example/myapp
After=network.target
[Service]
Type=simple
User=myapp
Group=myapp
WorkingDirectory=/opt/myapp
# Environment
Environment=NODE_ENV=production
Environment=PORT=3000
EnvironmentFile=-/opt/myapp/.env
# Process management
ExecStart=/usr/bin/node /opt/myapp/server.js
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=10
StartLimitIntervalSec=60
StartLimitBurst=3
# Logging
StandardOutput=journal
StandardError=journal
SyslogIdentifier=myapp
# Security hardening
NoNewPrivileges=yes
ProtectSystem=strict
ProtectHome=yes
PrivateTmp=yes
ReadWritePaths=/opt/myapp/data
[Install]
WantedBy=multi-user.target
Enable and start:
Override Package Units¶
Don't modify files in /lib/systemd/system/. Use overrides:
This creates /etc/systemd/system/nginx.service.d/override.conf:
Apply changes:
Service Hardening¶
Security Directives¶
systemd provides extensive sandboxing:
[Service]
# User/Group isolation
User=myapp
Group=myapp
DynamicUser=yes # Create ephemeral user
# Filesystem protection
ProtectSystem=strict # /usr, /boot, /efi read-only
ProtectHome=yes # /home, /root, /run/user inaccessible
PrivateTmp=yes # Private /tmp and /var/tmp
ReadWritePaths=/var/lib/myapp # Exceptions
ReadOnlyPaths=/etc/myapp # Read-only access
InaccessiblePaths=/mnt # Completely hidden
# Privilege restrictions
NoNewPrivileges=yes # Can't gain privileges
CapabilityBoundingSet=CAP_NET_BIND_SERVICE # Limit capabilities
AmbientCapabilities=CAP_NET_BIND_SERVICE
# System call filtering
SystemCallFilter=@system-service # Only allowed syscalls
SystemCallFilter=~@privileged # Block privileged syscalls
SystemCallErrorNumber=EPERM # Return error instead of kill
# Network isolation
PrivateNetwork=yes # Own network namespace
RestrictAddressFamilies=AF_INET AF_INET6
# Kernel hardening
ProtectKernelModules=yes # Can't load modules
ProtectKernelTunables=yes # Can't modify /proc, /sys
ProtectKernelLogs=yes # No access to kernel logs
ProtectControlGroups=yes # Can't modify cgroups
# Misc restrictions
ProtectClock=yes # Can't set system clock
ProtectHostname=yes # Can't change hostname
RestrictRealtime=yes # Can't use realtime scheduling
RestrictSUIDSGID=yes # Can't create SUID/SGID files
LockPersonality=yes # Lock execution domain
MemoryDenyWriteExecute=yes # W^X enforcement
Analyze Security¶
# Check security score of a unit
systemd-analyze security nginx
# Security overview
systemd-analyze security
Output shows security score and recommendations:
NAME DESCRIPTION EXPOSURE
✗ PrivateNetwork= Service has access to the host's network 0.5
✓ PrivateTmp= Service has access to private tmp
✗ ProtectSystem= Service has full access to the file system 0.5
...
→ Overall exposure level for nginx.service: 7.2 MEDIUM
Example: Hardened Web Service¶
[Unit]
Description=Hardened Web Application
After=network.target
[Service]
Type=simple
User=webapp
Group=webapp
ExecStart=/opt/webapp/bin/server
# Strong isolation
PrivateTmp=yes
ProtectSystem=strict
ProtectHome=yes
NoNewPrivileges=yes
# Only needed filesystem access
ReadWritePaths=/var/lib/webapp
ReadOnlyPaths=/etc/webapp
# Network: only needed ports
RestrictAddressFamilies=AF_INET AF_INET6
# Capabilities: only bind to low ports
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_BIND_SERVICE
# System calls: service calls only
SystemCallFilter=@system-service
SystemCallFilter=~@privileged @resources
# Kernel protection
ProtectKernelTunables=yes
ProtectKernelModules=yes
ProtectKernelLogs=yes
ProtectControlGroups=yes
# Additional hardening
ProtectClock=yes
ProtectHostname=yes
LockPersonality=yes
RestrictRealtime=yes
RestrictSUIDSGID=yes
MemoryDenyWriteExecute=yes
[Install]
WantedBy=multi-user.target
Resource Management¶
Set Resource Limits¶
[Service]
# CPU
CPUQuota=50% # Max 50% of one CPU
CPUWeight=100 # Relative weight
# Memory
MemoryMax=512M # Hard limit
MemoryHigh=256M # Soft limit (triggers reclaim)
# I/O
IOWeight=100 # Relative I/O weight
IOReadBandwidthMax=/dev/sda 10M # Read bandwidth limit
# Process limits
TasksMax=100 # Max processes/threads
View Resource Usage¶
# Resource usage by unit
systemd-cgtop
# Detailed unit resources
systemctl show nginx --property=MemoryCurrent,TasksCurrent,CPUUsageNSec
# CGroup details
systemctl status nginx | grep CGroup
cat /sys/fs/cgroup/system.slice/nginx.service/memory.current
Timers (Cron Replacement)¶
Create a Timer¶
Service unit (/etc/systemd/system/backup.service):
[Unit]
Description=Backup Service
[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.sh
User=backup
Timer unit (/etc/systemd/system/backup.timer):
[Unit]
Description=Daily Backup Timer
[Timer]
OnCalendar=*-*-* 02:00:00
RandomizedDelaySec=1800
Persistent=true
[Install]
WantedBy=timers.target
Enable:
Timer Syntax¶
| Syntax | Meaning |
|---|---|
| OnCalendar=daily | Every day at 00:00 |
| OnCalendar=weekly | Every Monday at 00:00 |
| OnCalendar=--* 02:00:00 | Every day at 02:00 |
| OnCalendar=Mon --* 09:00:00 | Every Monday at 09:00 |
| OnBootSec=5min | 5 minutes after boot |
| OnUnitActiveSec=1h | 1 hour after unit last activated |
List Timers¶
Troubleshooting¶
View Logs¶
# Service logs
sudo journalctl -u nginx
# Recent logs
sudo journalctl -u nginx --since "1 hour ago"
# Follow logs
sudo journalctl -u nginx -f
# Boot messages
sudo journalctl -b
# Failed units
systemctl --failed
Debug Startup Issues¶
# Analyze boot time
systemd-analyze blame
# Critical chain
systemd-analyze critical-chain
# Plot boot graph
systemd-analyze plot > boot.svg
# Verify unit file
systemd-analyze verify myapp.service
# Check dependencies
systemctl list-dependencies nginx
Common Issues¶
Service fails to start:
# Check status and logs
systemctl status myapp -l
journalctl -u myapp --no-pager
# Check unit file syntax
systemd-analyze verify myapp.service
Service keeps restarting:
# Check restart limits
systemctl show myapp -p StartLimitBurst,StartLimitIntervalSec
# Reset failure counter
systemctl reset-failed myapp
Dependency issues:
# See what unit is waiting for
systemctl list-jobs
# Check dependency tree
systemctl list-dependencies --all myapp
Quick Reference¶
Essential Commands¶
# Lifecycle
systemctl start|stop|restart|reload service
systemctl enable|disable service
systemctl enable --now service
systemctl mask|unmask service
# Information
systemctl status service
systemctl show service
systemctl cat service
systemctl list-dependencies service
# System
systemctl daemon-reload
systemctl list-units
systemctl list-unit-files
systemctl --failed
systemctl isolate multi-user.target
systemctl get-default
# Logs
journalctl -u service
journalctl -f
journalctl -b
journalctl --since "1 hour ago"
Next Steps¶
Continue to Time Synchronization to configure accurate time keeping, which is essential for security (logs, certificates, authentication).