Systemd_units
Chapter 18: systemd Units and Services
Section titled “Chapter 18: systemd Units and Services”Creating and Managing Custom systemd Services
Section titled “Creating and Managing Custom systemd Services”18.1 Understanding Service Unit Files
Section titled “18.1 Understanding Service Unit Files”Unit File Syntax
Section titled “Unit File Syntax”Service unit files are the configuration files that tell systemd how to manage a service. They follow the INI format with sections for different aspects of the service.
Service Unit File Structure+------------------------------------------------------------------+| || Location: /etc/systemd/system/<name>.service || || Format: || +----------------------------------------------------------+ || | [Unit] | || | Description=Human readable description | || | Documentation=URL or man page | || | After=network.target syslog.target | || | Before=shutdown | ||.target | Wants=network.target | || | Requires=network.target | || | | || | [Service] | || | Type=simple | || | ExecStart=/usr/bin/myapp --config /etc/myapp.conf | || | ExecStop=/usr/bin/myapp --stop | || | ExecReload=/bin/kill -HUP $MAINPID | || | Restart=on-failure | || | RestartSec=5 | || | User=myappuser | || | Group=myappgroup | || | WorkingDirectory=/var/lib/myapp | || | Environment=NODE_ENV=production | || | | || | [Install] | || | WantedBy=multi-user.target | || +----------------------------------------------------------+ || |+------------------------------------------------------------------+Example: Custom Application Service
Section titled “Example: Custom Application Service”[Unit]Description=My Custom Application ServiceDocumentation=https://myapp.example.com/docsAfter=network.target postgresql.serviceWants=network.targetAfter=syslog.target
[Service]Type=simpleUser=myappGroup=myappWorkingDirectory=/var/lib/myappEnvironment="NODE_ENV=production"Environment="PORT=3000"EnvironmentFile=-/etc/myapp/env
ExecStart=/usr/bin/node /opt/myapp/server.jsExecStop=/bin/kill -TERM $MAINPIDExecReload=/bin/kill -HUP $MAINPID
Restart=on-failureRestartSec=10TimeoutStartSec=30TimeoutStopSec=30
StandardOutput=journalStandardError=journalSyslogIdentifier=myapp
PrivateTmp=yesProtectSystem=strictProtectHome=yesNoNewPrivileges=true
[Install]WantedBy=multi-user.target18.2 Service Type Deep Dive
Section titled “18.2 Service Type Deep Dive”Type: simple (Most Common)
Section titled “Type: simple (Most Common)”[Service]Type=simpleExecStart=/usr/bin/myapp
# Main process is the ExecStart process# If it exits, service is considered stopped# systemd considers it "active" while process runs
# Use when:# - Your application runs in the foreground# - No forking required# - Most common for modern applicationsType: forking (Traditional Daemons)
Section titled “Type: forking (Traditional Daemons)”[Service]Type=forkingExecStart=/usr/bin/mydemon --daemonPIDFile=/run/mydemon.pid
# Parent process forks child# systemd waits for parent to exit# Uses PIDFile to track main process# Service is "active" while child runs
# Use when:# - Traditional daemon that forks to background# - Parent exits after forking# - Legacy applicationsType: oneshot (One-Time Tasks)
Section titled “Type: oneshot (One-Time Tasks)”[Service]Type=oneshotExecStart=/usr/bin/setup-script.shRemainAfterExit=yes
# Runs once and exits# RemainAfterExit=yes keeps it "active" after completion# Useful for initialization tasks# Exit code determines success/failure
# Use when:# - One-time setup/configuration# - Initialization scripts# - Services that don't keep runningType: notify (Ready Notifications)
Section titled “Type: notify (Ready Notifications)”[Service]Type=notifyExecStart=/usr/bin/myapp
# systemd waits for READY notification# Service sends sd_notify("READY=1") when ready# More reliable than Type=simple for services needing init time# Requires application support for notifications
# Use when:# - Application needs time to initialize# - Application can send notifications# - Better than simple when startup time variesType: dbus (D-Bus Based)
Section titled “Type: dbus (D-Bus Based)”[Service]Type=dbusBusName=com.example.MyServiceExecStart=/usr/bin/myapp
# Waits for D-Bus name to appear# Service marked active when BusName is acquired# Common for system services using D-Bus
# Use when:# - Service registers with D-Bus# - Uses D-Bus for communication# - System services18.3 Essential Service Directives
Section titled “18.3 Essential Service Directives”Execution Control
Section titled “Execution Control”[Service]# Start command (required)ExecStart=/path/to/command arg1 arg2
# Pre-start commands (run before ExecStart)ExecStartPre=/path/to/script.shExecStartPre=/bin/mkdir -p /var/run/myapp
# Post-start commands (run after ExecStart starts)ExecStartPost=/path/to/post-start.sh
# Stop command (alternative to signal)ExecStop=/path/to/stop-script.sh
# Post-stop commands (run after ExecStop)ExecStopPost=/path/to/cleanup.sh
# Reload command (for config changes)ExecReload=/bin/kill -HUP $MAINPID
# Note: $MAINPID is the PID of the main processRestart Behavior
Section titled “Restart Behavior”[Service]# When to restart# on-failure (default) - non-zero exit, timeout, crashed# on-success - clean exit (for oneshot)# always - always restart# on-abnormal - timeout, crash, failed signalRestart=on-failure
# Time between stop and startRestartSec=5
# Maximum restart attemptsStartLimitIntervalSec=300 # Within this time windowStartLimitBurst=5 # This many restarts allowed
# What to do if limit reachedStartLimitAction=reboot# Options: none, reboot, reboot-force, reboot-immediateEnvironment and Working Directory
Section titled “Environment and Working Directory”[Service]# Single environment variableEnvironment="VAR1=value1"Environment="VAR2=value2"
# Multiple variablesEnvironment="VAR1=value1" "VAR2=value2"
# From file (lines like VAR=value)EnvironmentFile=/etc/myapp/env
# Working directoryWorkingDirectory=/var/lib/myapp
# User and group to run asUser=myappuserGroup=myappgroupOutput Handling
Section titled “Output Handling”[Service]# Where to send stdout/stderr# journal, syslog, kmsg, console, null
# Combined output to journalStandardOutput=journalStandardError=journal
# Separate outputsStandardOutput=file:/var/log/myapp/stdout.logStandardError=file:/var/log/myapp/stderr.log
# Syslog identificationSyslogIdentifier=myappSyslogFacility=userSyslogLevel=infoSyslogLevelPrefix=yes18.4 Security Hardening
Section titled “18.4 Security Hardening”Protected Execution
Section titled “Protected Execution”[Service]# Isolate from systemProtectSystem=strict # Read-only /usr, /boot, /etcProtectSystem=full # + /run, /var (except some dirs)ProtectSystem=no # Disabled (default before 247)
# Protect home directoryProtectHome=yes # Hide /home, /root, /run/userProtectHome=read-only # Read-only accessProtectHome=no # No protection
# Additional protectionsProtectKernelTunables=yes # Read-only /proc/sysProtectKernelModules=yes # Block module loadingProtectControlGroups=yes # Read-only cgroups
# New privilegesNoNewPrivileges=true # Prevent privilege escalation
# Private /tmpPrivateTmp=yes # Private /tmp
# Network accessPrivateNetwork=yes # No network accessProtectNetwork=yes # Virtual network onlySeccomp and Capabilities
Section titled “Seccomp and Capabilities”[Service]# System call filteringSystemCallFilter=@system-serviceSystemCallFilter=~@clock @debug @ipc @module @mount @obsolete @privileged @reboot
# System call error numberSystemCallErrno=EPERM
# Capability managementAmbientCapabilities=CAP_NET_BIND_SERVICECapabilityBoundingSet=CAP_NET_BIND_SERVICE CAP_CHOWN
# Memory protectionMemoryDenyWriteExecute=trueRestrictRealtime=trueRestrictNamespaces=yesFilesystem Restrictions
Section titled “Filesystem Restrictions”[Service]# Read-only filesystem (except specified paths)ReadWritePaths=/var/lib/myappReadWritePaths=/var/log/myappReadWritePaths=/run/myapp
# Temporary filesystemTemporaryFileSystem=/tmp:size=10M
# Bind mountsBindReadOnlyPaths=/etc/ssl/certs:/etc/ssl/certs:ro18.5 Resource Limits
Section titled “18.5 Resource Limits”Memory Limits
Section titled “Memory Limits”[Service]# Hard limit - process cannot exceedMemoryMax=1G
# Soft limit - process gets priority up to thisMemoryHigh=512M
# Swap limitMemorySwapMax=100MCPU Limits
Section titled “CPU Limits”[Service]# CPU quota (percentage of one CPU)CPUQuota=50% # Max 50% of one CPUCPUQuota=200% # Max 2 CPUs
# CPU affinity (specific cores)CPUAffinity=0 # Core 0 onlyCPUAffinity=0-3 # Cores 0-3CPUAffinity=0,2,4 # Cores 0, 2, 4I/O Limits
Section titled “I/O Limits”[Service]# Bandwidth limitsIOReadBandwidthMax=/var/lib/mysql 1GIOWriteBandwidthMax=/var/lib/mysql 1G
# IOPS limitsIOPSReadMax=10000IOPSWriteMax=5000Process Limits
Section titled “Process Limits”[Service]# Max processes/threadsTasksMax=100
# File descriptorsLimitNOFILE=1000
# Other limitsLimitNPROC=50LimitAS=2GLimitCORE=0 # Disable core dumps18.6 Socket Activation
Section titled “18.6 Socket Activation”Socket Unit
Section titled “Socket Unit”[Unit]Description=My Application SocketPartOf=myapp.service
[Socket]ListenStream=/run/myapp.sockSocketMode=0660SocketUser=myappSocketGroup=myapp
# Or TCP socketListenStream=8080BindIPv6Only=both
# Socket service activationService=myapp.serviceService for Socket Activation
Section titled “Service for Socket Activation”[Service]Type=notifyExecStart=/usr/bin/myappSocketBindToLowerLevel=true
# Note: Remove any Port= or similar directives# Service receives socket as fdCreating Socket-Activated Service
Section titled “Creating Socket-Activated Service”# Create socket unitsudo tee /etc/systemd/system/myapp.socket << 'EOF'[Unit]Description=My App SocketPartOf=myapp.service
[Socket]ListenStream=9000Accept=false
[Install]WantedBy=sockets.targetEOF
# Create service unit (without traditional port binding)sudo tee /etc/systemd/system/myapp.service << 'EOF'[Unit]Description=My ApplicationAfter=network.targetRequires=myapp.socket
[Service]Type=notifyExecStart=/usr/bin/myappUser=myapp
[Install]WantedBy=multi-user.targetEOF
# Reload and enablesudo systemctl daemon-reloadsudo systemctl enable --now myapp.socket18.7 Timer Units for Scheduling
Section titled “18.7 Timer Units for Scheduling”Timer Unit File
Section titled “Timer Unit File”[Unit]Description=Daily Database BackupRequires=backup.service
[Timer]# Calendar-based schedulingOnCalendar=dailyOnCalendar=Mon *-*-* 02:00:00 # Every Monday at 2 AMOnCalendar=*-*-* 12:00:00 # Every day at noonOnCalendar=*:0/15 # Every 15 minutesOnCalendar=hourly # Every hour
# Time from eventsOnActiveSec=1h # 1 hour after activationOnBootSec=30min # 30 minutes after bootOnUnitActiveSec=1h # 1 hour after last runOnUnitInactiveSec=1h # 1 hour after last ended
# OptionsPersistent=true # Run missed jobs on bootRandomizedDelaySec=1h # Random delay to prevent stormsWakeSystem=true # Wake from suspendAccuracySec=1us # Accuracy (default 1min)
[Install]WantedBy=timers.targetService for Timer
Section titled “Service for Timer”[Unit]Description=Daily Database Backup
[Service]Type=oneshotUser=backupExecStart=/usr/local/bin/backup.shManaging Timers
Section titled “Managing Timers”# Enable and startsudo systemctl enable --now backup.timer
# List timerssystemctl list-timers --allsystemctl list-timers --all | grep backup
# Manual trigger (for testing)sudo systemctl start backup.service
# View next run timesystemctl list-timers --all | grep backup
# Timer statussystemctl status backup.timer
# View timer detailssystemctl cat backup.timer
# View missed triggersjournalctl -u backup.timer -u backup.service18.8 Path Units for File Monitoring
Section titled “18.8 Path Units for File Monitoring”Path Unit File
Section titled “Path Unit File”[Unit]Description=Watch directory for new filesPartOf=processor.service
[Path]# Monitor file/directoryDirectoryNotEmpty=/var/spool/myappPathExists=/tmp/triggerPathChanged=/etc/myapp/config.conf
# OptionsUnit=processor.service
[Install]WantedBy=multi-user.targetService for Path Unit
Section titled “Service for Path Unit”[Unit]Description=Process new files
[Service]Type=oneshotExecStart=/usr/local/bin/process-files.shUsing Path Units
Section titled “Using Path Units”# Create path unitsudo tee /etc/systemd/system/watchdir.path << 'EOF'[Unit]Description=Watch /var/spool/incoming[Path]DirectoryNotEmpty=/var/spool/incomingUnit=process-files.service
[Install]WantedBy=multi-user.targetEOF
# Enable and startsudo systemctl enable --now watchdir.path
# Check statussystemctl status watchdir.path18.9 Drop-in Units
Section titled “18.9 Drop-in Units”Understanding Drop-ins
Section titled “Understanding Drop-ins”Drop-in files allow you to modify unit files without editing the original vendor files. They are stored in a subdirectory with .d suffix.
Drop-in Directory Structure+------------------------------------------------------------------+| || Main unit file: || /usr/lib/systemd/system/nginx.service || || Drop-in directory: || /etc/systemd/system/nginx.service.d/ || || Drop-in files (override specific directives): || /etc/systemd/system/nginx.service.d/override.conf || /etc/systemd/system/nginx.service.d/memory.conf || || Drop-in files can add new directives or override existing || |+------------------------------------------------------------------+Creating Drop-ins
Section titled “Creating Drop-ins”# Method 1: Using systemctl edit (recommended)sudo systemctl edit nginx
# This opens editor with blank file# Add overrides, save and exit
# Method 2: Manual drop-in filesudo mkdir -p /etc/systemd/system/nginx.service.dsudo tee /etc/systemd/system/nginx.service.d/override.conf << 'EOF'[Service]Restart=alwaysMemoryMax=512MEnvironment="NGINX_PORT=8080"EOF
# Method 3: Full editsudo systemctl edit --full nginx
# View effective unitsystemctl show nginx
# View drop-inssystemctl cat nginxCommon Override Examples
Section titled “Common Override Examples”# Override restart policysudo systemctl edit nginx[Service]Restart=always
# Override memory limitsudo systemctl edit nginx[Service]MemoryMax=1G
# Add environment variablessudo systemctl edit nginx[Service]Environment="NODE_ENV=production"Environment="PORT=3000"
# Add extra mountssudo systemctl edit nginx[Service]ReadWritePaths=/data
# Override usersudo systemctl edit nginx[Service]User=nginxGroup=nginx18.10 Troubleshooting Service Issues
Section titled “18.10 Troubleshooting Service Issues”Common Issues and Solutions
Section titled “Common Issues and Solutions”# Service won't start# 1. Check statussudo systemctl status myapp
# 2. Check logssudo journalctl -u myapp -n 50sudo journalctl -u myapp --since "10 minutes ago"sudo journalctl -u myapp -xe
# 3. Verify unit file syntaxsudo systemd-analyze verify myapp.service
# 4. Check dependenciessudo systemctl list-dependencies myappsudo systemd-analyze dot myapp.service | dot -Tsvg > deps.svg
# 5. Check for conflictssudo systemctl list-units --state=failedsudo systemctl failed
# 6. Debug startingsudo systemd-analyze service-ordersudo strace -f systemctl start myapp
# Permission issues# Check file permissionsls -la /etc/systemd/system/myapp.servicels -la /usr/bin/myapp
# Check user existsid myappuser
# Path issues# Verify ExecStart path existswhich myappls -la /path/to/myappViewing Detailed Service Information
Section titled “Viewing Detailed Service Information”# Full unit propertiessystemctl show nginx
# Specific propertiessystemctl show nginx -p MainPIDsystemctl show nginx -p MemoryCurrentsystemctl show nginx -p CPUUsageNSec
# Unit file with defaultssystemctl cat nginx
# Unit file dependenciessystemctl list-dependencies nginxsystemctl list-dependencies --reverse nginx
# Resource usagesystemctl status nginxsystemctl show nginx | grep -i memory
# Failed reasonsystemctl failure nginxTesting Services
Section titled “Testing Services”# Check if service can startsudo systemctl start myappecho $? # 0 = success
# Test start without systemdsudo -u myappuser /usr/bin/myapp
# Run in foreground for debuggingsudo systemctl stop myapp/usr/bin/myapp --debug
# Check environmentsudo systemd-run --scope -p User=myappuser env | grep -i myapp
# Temporary test servicesudo systemd-run --scope /bin/bash18.11 Best Practices for Service Units
Section titled “18.11 Best Practices for Service Units” Service Unit Best Practices+------------------------------------------------------------------+| || Always Use: || +----------------------------------------------------------+ || | ✓ Description= for all services | || | ✓ After= with Wants= for dependencies | || | ✓ Restart= with RestartSec= for critical services | || | ✓ User= and Group= (not running as root) | || | ✓ WorkingDirectory= for relative paths | || | ✓ StandardOutput=journal for logging | || | ✓ ProtectSystem=yes for security | || | ✓ PrivateTmp=yes | || | ✓ NoNewPrivileges=yes | || +----------------------------------------------------------+ || || Avoid: || +----------------------------------------------------------+ || | ✗ Running as root unless absolutely necessary | || | ✗ Complex shell commands in ExecStart | || | ✗ Ignoring exit codes | || | ✗ Missing dependencies | || | ✗ Hardcoded paths without verification | || | ✗ Using deprecated directives | || +----------------------------------------------------------+ || || Testing: || +----------------------------------------------------------+ || | ✓ Always test with systemctl start first | || | ✓ Check journalctl output | || | ✓ Verify with systemd-analyze verify | || | ✓ Test after reboot | || | ✓ Monitor with systemctl status in production | || +----------------------------------------------------------+ || |+------------------------------------------------------------------+18.12 Interview Questions
Section titled “18.12 Interview Questions”Basic Questions
Section titled “Basic Questions”-
What is a systemd unit file?
- Configuration file that defines a resource or service managed by systemd
-
What are the main sections in a service unit file?
- [Unit], [Service], [Install]
-
What does Type=simple mean?
- Main process is the ExecStart process; service is active while it runs
-
How do you make a service start at boot?
- systemctl enable servicename
- WantedBy= in [Install] section
-
What is the difference between ExecStart and ExecStartPre?
- ExecStart is the main command; ExecStartPre runs before it
Intermediate Questions
Section titled “Intermediate Questions”-
What does Restart=on-failure do?
- Automatically restarts the service if it exits with non-zero status
-
How do you override settings from a vendor unit file?
- Using drop-in files in /etc/systemd/system/
.service.d/
- Using drop-in files in /etc/systemd/system/
-
What is socket activation?
- Service starts on-demand when a connection is made to its socket
-
How do you set environment variables in a service?
- Environment= directive or EnvironmentFile=
-
What is the purpose of $MAINPID in systemd units?
- References the PID of the main process started by ExecStart
Advanced Questions
Section titled “Advanced Questions”-
Explain the difference between Requires and Wants
- Requires is a hard dependency (fails if dependency fails)
- Wants is a soft dependency (continues if dependency fails)
-
How do you debug a failing service?
- systemctl status, journalctl -u, systemd-analyze verify, check dependencies
-
What security measures should you implement in service units?
- ProtectSystem, PrivateTmp, NoNewPrivileges, User/Group, read-only paths
-
How do timer units work?
- Schedule service execution based on calendar or time intervals
- Alternative to cron for systemd-managed scheduling
-
What is the purpose of RemainAfterExit=yes?
- Keeps the service marked as “active” even after the process exits (for oneshot)
Summary
Section titled “Summary”Creating systemd service units requires understanding:
Quick Reference+------------------------------------------------------------------+| || Essential Directives: || +----------------------------------------------------------+ || | Type= | Service startup behavior | || | ExecStart= | Command to start service | || | Restart= | When to restart | || | User/Group= | Run as non-root | || | After= | Ordering dependencies | || | WantedBy= | Boot target | || +----------------------------------------------------------+ || || Service Types: || +----------------------------------------------------------+ || | simple | Default, main process is service | || | forking | Parent forks, uses PIDFile | || | oneshot | Runs once, use RemainAfterExit | || | notify | Waits for READY notification | || | dbus | Waits for D-Bus name | || +----------------------------------------------------------+ || || Key Commands: || +----------------------------------------------------------+ || | systemctl daemon-reload | Reload unit files | || | systemctl edit <service> | Create drop-in | || | systemctl cat <service> | View unit file | || | journalctl -u <service> | View logs | || | systemd-analyze verify | Check syntax | || +----------------------------------------------------------+ || |+------------------------------------------------------------------+