Controlling systemd Services and Writing Unit Files
Drive services with systemctl and author custom unit and timer files for scripted daemons.
Controlling systemd Services and Writing Unit Files is a free DevOps Bootcamp lesson on CoddyKit — lesson 2 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the DevOps Bootcamp learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
Introduction to systemd and systemctl
systemd is the init system and service manager used by most modern Linux distributions. It is responsible for starting, stopping, and managing system services, as well as handling boot sequences and system state.
The primary tool for interacting with systemd is systemctl. With it you can:
- Start and stop services
- Enable or disable services at boot
- Inspect service status and logs
- Reload configuration without restarting
All services managed by systemd are defined by unit files, which are declarative configuration files stored under /etc/systemd/system/ (system-wide) or ~/.config/systemd/user/ (per-user).
Essential systemctl Commands
These are the most commonly used systemctl commands for day-to-day service management. Each command operates on a unit name such as nginx.service or simply nginx.
systemctl start <unit>— start a service immediatelysystemctl stop <unit>— stop a running servicesystemctl restart <unit>— stop then start (full restart)systemctl reload <unit>— send SIGHUP to reload config without stoppingsystemctl enable <unit>— create symlinks so the unit starts at bootsystemctl disable <unit>— remove those symlinkssystemctl status <unit>— show state, PID, recent log lines
#!/usr/bin/env bash
# Quick reference: inspect and control an nginx service
# (Requires nginx to be installed; run as root or with sudo)
systemctl status nginx
systemctl start nginx
systemctl enable nginx
systemctl reload nginx
systemctl restart nginx
systemctl stop nginx
systemctl disable nginxChecking Service Status in Detail
systemctl status gives a rich snapshot of a unit. Understanding its output is essential for diagnosing problems quickly.
- Loaded: path to the unit file and whether it is enabled at boot
- Active: current state —
active (running),inactive (dead),failed - Main PID: the process identifier of the service's main process
- CGroup: all child processes belonging to the service
- Log lines: the most recent journal entries for this unit
You can also query just the active state with systemctl is-active and the enabled state with systemctl is-enabled, which are ideal for use in scripts.
#!/usr/bin/env bash
# Script-friendly service health check
SERVICE="sshd"
if systemctl is-active --quiet "$SERVICE"; then
echo "$SERVICE is running."
else
echo "$SERVICE is NOT running. Attempting restart..."
systemctl restart "$SERVICE"
fi
if systemctl is-enabled --quiet "$SERVICE"; then
echo "$SERVICE is enabled at boot."
else
echo "WARNING: $SERVICE is not enabled at boot."
fiListing and Filtering Units
When managing many services, you need ways to list and filter units efficiently. systemctl list-units shows all currently loaded units, while list-unit-files shows all installed unit files and their enabled state.
Useful filtering options:
--type=service— show only service units--state=failed— show only failed units--state=active— show only active units--all— include inactive units in the listing
Combining these filters with grep lets you build powerful inspection pipelines in maintenance scripts.
#!/usr/bin/env bash
# List all failed services and report them
echo "=== Failed Services ==="
systemctl list-units --type=service --state=failed --no-legend
echo ""
echo "=== Services Enabled at Boot ==="
systemctl list-unit-files --type=service --state=enabled --no-legend | awk '{print $1}'Anatomy of a systemd Service Unit File
A service unit file is a plain text INI-style file composed of sections. Each section controls a different aspect of the unit's lifecycle.
- [Unit] — metadata: description, ordering dependencies (
After=,Requires=,Wants=) - [Service] — how to start/stop the process:
ExecStart,ExecStop,Restart,User,WorkingDirectory - [Install] — when this unit should be enabled:
WantedBy=multi-user.targetmeans it activates during normal multi-user boot
After placing or editing a unit file under /etc/systemd/system/, you must run systemctl daemon-reload so systemd picks up the changes.
Writing Your First Service Unit File
Let's create a simple service unit file for a custom Python HTTP server script. The unit file tells systemd exactly how to manage this process as if it were any other system service.
Key [Service] directives used here:
Type=simple— systemd treats theExecStartprocess as the main processUser=www-data— run under a non-root account for securityWorkingDirectory— set the process's working directoryRestart=on-failure— automatically restart if the process exits with a non-zero codeRestartSec=5— wait 5 seconds between restart attempts
#!/usr/bin/env bash
# Create a custom service unit file for a simple HTTP server
# Run as root
cat > /etc/systemd/system/mywebserver.service << 'EOF'
[Unit]
Description=My Custom Python Web Server
After=network.target
[Service]
Type=simple
User=www-data
WorkingDirectory=/var/www/mysite
ExecStart=/usr/bin/python3 -m http.server 8080
Restart=on-failure
RestartSec=5
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl enable --now mywebserver.service
systemctl status mywebserver.serviceService Types and Process Lifecycle
The Type= directive in [Service] tells systemd how to track when a service has finished starting. Choosing the wrong type is a common source of ordering bugs.
Type=simple— default; systemd considers the service started as soon asExecStartlaunches. No readiness signal needed.Type=forking— theExecStartprocess forks and exits; the real daemon runs as a child. UsePIDFile=so systemd can track it.Type=notify— the process sendssd_notify(READY=1)when truly ready. Most reliable for complex daemons.Type=oneshot— runs once and exits; subsequent units wait for completion. Ideal for setup scripts.Type=idle— like simple but execution is delayed until all active jobs finish.
#!/usr/bin/env bash
# Example: oneshot service for a database migration script
# /etc/systemd/system/db-migrate.service
cat > /etc/systemd/system/db-migrate.service << 'EOF'
[Unit]
Description=Run Database Migrations
After=postgresql.service
Requires=postgresql.service
[Service]
Type=oneshot
User=appuser
WorkingDirectory=/opt/myapp
ExecStart=/opt/myapp/scripts/migrate.sh
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload
systemctl start db-migrate.serviceEnvironment Variables and Security Hardening
Service unit files support several directives for passing environment variables and applying security restrictions — both are important in production systems.
Environment directives:
Environment="KEY=value"— set a single variable inlineEnvironmentFile=/etc/myapp/env— load variables from a file (oneKEY=valueper line)
Common security hardening directives:
NoNewPrivileges=true— prevent the process from gaining additional privileges via setuidProtectSystem=strict— mount/usr,/boot,/etcread-onlyPrivateTmp=true— give the service its own isolated/tmpCapabilityBoundingSet=— drop all Linux capabilities
#!/usr/bin/env bash
# Hardened service unit with environment file
cat > /etc/systemd/system/secureapp.service << 'EOF'
[Unit]
Description=Secure Application Daemon
After=network.target
[Service]
Type=simple
User=secureapp
Group=secureapp
WorkingDirectory=/opt/secureapp
EnvironmentFile=/etc/secureapp/env
ExecStart=/opt/secureapp/bin/server
Restart=on-failure
RestartSec=10
# Security hardening
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
PrivateTmp=true
CapabilityBoundingSet=
ReadWritePaths=/var/lib/secureapp /var/log/secureapp
[Install]
WantedBy=multi-user.target
EOF
# Create the environment file separately so secrets stay out of the unit file
install -m 600 -o secureapp /dev/null /etc/secureapp/env
echo 'APP_PORT=9000' >> /etc/secureapp/env
echo 'DB_URL=postgresql://localhost/mydb' >> /etc/secureapp/env
systemctl daemon-reloadIntroduction to systemd Timer Units
A systemd timer is a unit that activates another unit (usually a .service) on a schedule. It replaces traditional cron jobs with a more integrated, loggable, and controllable mechanism.
Every timer unit (foo.timer) is paired with a service unit of the same base name (foo.service) that does the actual work. You enable and start the timer, not the service directly.
Timer types:
- Realtime (calendar) timers — fire at wall-clock times using
OnCalendar=expressions likedaily,weekly, orMon *-*-* 02:00:00 - Monotonic timers — fire relative to a system event using
OnBootSec=,OnActiveSec=, orOnUnitActiveSec=
Writing a Timer Unit File
Let's build a complete timer + service pair that runs a backup script every day at 02:00. Notice how the [Timer] section lives in its own .timer file while the work lives in the paired .service file.
Key [Timer] directives:
OnCalendar=— calendar expression for the schedulePersistent=true— if the system was off when the timer should have fired, run immediately on next bootRandomizedDelaySec=— add up to N seconds of random jitter to prevent thundering-herd when many hosts share the same scheduleUnit=— explicit target unit (optional if names match)
#!/usr/bin/env bash
# Create a daily backup timer pair
# Run as root
# 1. The service that does the work
cat > /etc/systemd/system/daily-backup.service << 'EOF'
[Unit]
Description=Daily Database Backup
After=network.target postgresql.service
[Service]
Type=oneshot
User=backup
ExecStart=/usr/local/bin/backup-db.sh
StandardOutput=journal
StandardError=journal
EOF
# 2. The timer that schedules it
cat > /etc/systemd/system/daily-backup.timer << 'EOF'
[Unit]
Description=Run Daily Database Backup at 02:00
[Timer]
OnCalendar=*-*-* 02:00:00
Persistent=true
RandomizedDelaySec=300
[Install]
WantedBy=timers.target
EOF
systemctl daemon-reload
systemctl enable --now daily-backup.timer
# Verify
systemctl list-timers daily-backup.timerInspecting Logs and Troubleshooting Units
systemd routes all service output to the journal, which you query with journalctl. This centralises logs from all units in one searchable store.
Essential journalctl flags for service troubleshooting:
-u <unit>— filter by unit name-f— follow log in real time (liketail -f)-n 50— show only the last 50 lines--since "1 hour ago"— limit by time-p err— show only error-priority and above-b— show logs from the current boot only
When a unit fails to start, the first thing to run is journalctl -u <unit> -n 30 --no-pager followed by systemctl status <unit> to capture the recent error context.
#!/usr/bin/env bash
# Troubleshooting helper: dump unit status and recent logs
# Usage: ./service-debug.sh <unit-name>
UNIT="${1:-nginx.service}"
echo "=============================="
echo "STATUS: $UNIT"
echo "=============================="
systemctl status "$UNIT" --no-pager -l
echo ""
echo "=============================="
echo "RECENT LOGS (last 50 lines): $UNIT"
echo "=============================="
journalctl -u "$UNIT" -n 50 --no-pager
echo ""
echo "=============================="
echo "ERRORS ONLY (current boot)"
echo "=============================="
journalctl -u "$UNIT" -b -p err --no-pagerKnowledge Check: systemd Unit Files
Test your understanding of systemd service and timer unit configuration.
Lesson Recap: Controlling systemd Services and Writing Unit Files
In this lesson you explored the core concepts of systemd service management and unit file authoring at a professional level. Here is a summary of what was covered:
- systemctl basics — start, stop, restart, reload, enable, disable, status, is-active, is-enabled for scripting-friendly checks
- Listing and filtering —
list-unitsandlist-unit-fileswith--typeand--stateflags to isolate failed or enabled services - Unit file anatomy — the three sections
[Unit],[Service], and[Install]and their key directives - Service types —
simple,forking,notify,oneshot, and when to use each - Environment and security —
EnvironmentFile,NoNewPrivileges,ProtectSystem,PrivateTmpfor hardened daemons - Timer units — pairing
.timerand.servicefiles,OnCalendarexpressions, andPersistentcatch-up behaviour - Journalctl — filtering by unit, time, boot, and priority to diagnose failures efficiently
With these skills you can manage any system service, schedule recurring tasks reliably, and author production-grade unit files for your own daemons.
Frequently asked questions
Is the “Controlling systemd Services and Writing Unit Files” lesson free?
Yes — the full text of “Controlling systemd Services and Writing Unit Files” is free to read here on the web, and the DevOps Bootcamp course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the DevOps Bootcamp course, upgrade to CoddyKit PRO.
What will I learn in “Controlling systemd Services and Writing Unit Files”?
Drive services with systemctl and author custom unit and timer files for scripted daemons. You practise DevOps Bootcamp with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start DevOps Bootcamp?
No prior experience is required. DevOps Bootcamp on CoddyKit is structured for beginners through advanced learners; this is — lesson 2 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Controlling systemd Services and Writing Unit Files” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this DevOps Bootcamp lesson?
Yes. Every DevOps Bootcamp lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- Automating User and Group Provisioning
- Controlling systemd Services and Writing Unit Files
- Disk, Filesystem, and Mount Automation
- Building System Health Check and Alert Scripts