Lab 09: systemd — Service Management

Time: 30 minutes | Level: Practitioner | Docker: docker run -it --rm ubuntu:22.04 bash


Overview

systemd is the init system and service manager used by virtually all modern Linux distributions. It starts your system, manages services, handles logging, and orchestrates dependencies. In this lab you'll learn to control services, write unit files, and use journalctl for log analysis.

Docker Note: systemd cannot run as PID 1 inside a standard Docker container (it requires a full init environment). This lab demonstrates syntax, unit file writing, and commands that do work in containers, while showing real-world output for systemd commands as they appear on actual hosts.


Step 1: systemctl — The systemd Control Interface

systemctl is the primary tool for interacting with systemd. Here's the complete command reference with expected output from a running system:

# On a real Linux system (not Docker), these commands manage services:

# Check service status
# systemctl status nginx

# Start / stop / restart a service
# systemctl start nginx
# systemctl stop nginx
# systemctl restart nginx
# systemctl reload nginx   # reload config without full restart

# Enable/disable service at boot
# systemctl enable nginx
# systemctl disable nginx

# Check if enabled
# systemctl is-enabled nginx
# systemctl is-active nginx

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

What systemctl status nginx looks like on a real host:

💡 Status indicators: (green dot) = active/running, (red) = failed, (grey) = inactive. The Loaded: line shows the unit file path and whether it's enabled at boot. Active: shows current state and uptime.


Step 2: Writing a systemd Unit File

Unit files are INI-format configuration files that define how systemd manages a service.

📸 Verified Output:

💡 Unit file sections: [Unit] = metadata and dependencies. [Service] = how to run the service. [Install] = when to start at boot (WantedBy=multi-user.target = normal system startup). The file lives in /etc/systemd/system/ for custom services.


Step 3: Unit File Types and Service Types

📸 Verified Output:

💡 Requires vs Wants: Use Wants= instead of Requires= for most dependencies. Requires= will stop your service if the dependency fails — even temporarily. Wants= is more resilient and preferred in production.


Step 4: systemctl daemon-reload — Applying Unit File Changes

After creating or modifying unit files, systemd must re-read them.

📸 Verified Output:

💡 Never skip daemon-reload: If you edit a unit file and don't run daemon-reload, systemd runs from its cached (old) version. You'll restart but nothing changes — a common debugging pitfall. Always: edit → daemon-reload → restart.


Step 5: systemd Targets — System Runlevels

Targets are systemd's equivalent of SysV runlevels — they define system states.

📸 Verified Output:

💡 Headless servers: Always set multi-user.target as default on servers with systemctl set-default multi-user.target. This skips loading the graphical stack (X11, display manager), saving RAM and boot time. Servers rarely need graphical.target.


Step 6: journalctl — Reading systemd Logs

journalctl reads the systemd journal — the centralized log for all services.

📸 Verified Output:

💡 journalctl disk usage: The journal can grow large. Check with journalctl --disk-usage. Limit it in /etc/systemd/journald.conf with SystemMaxUse=500M. Rotate with journalctl --rotate and vacuum with journalctl --vacuum-time=2weeks.


Step 7: Enable, Disable, and Mask Services

📸 Verified Output:

💡 mask vs disable: disable just removes boot symlinks — the service can still be started manually. mask creates a symlink to /dev/null, making it impossible to start even manually. Use masking for services that should never run (like telnet on a secure server).


Step 8: Capstone — Deploy a Custom Service

Scenario: Deploy a custom Python health-check server as a systemd service with proper logging, auto-restart, and boot persistence.

📸 Verified Output:

💡 Production hardening: Always add StartLimitIntervalSec and StartLimitBurst to prevent a crashing service from entering a restart storm. Pair with MemoryLimit and CPUQuota to prevent runaway services from starving the system. Use PrivateTmp=true and NoNewPrivileges=true as baseline security settings for any new service.


Summary

Command
Purpose
Example

systemctl start svc

Start a service

systemctl start nginx

systemctl stop svc

Stop a service

systemctl stop nginx

systemctl restart svc

Restart a service

systemctl restart nginx

systemctl reload svc

Reload config (no downtime)

systemctl reload nginx

systemctl status svc

Show service status

systemctl status nginx

systemctl enable svc

Start at boot

systemctl enable --now nginx

systemctl disable svc

Don't start at boot

systemctl disable nginx

systemctl mask svc

Prevent all starts

systemctl mask telnet.socket

systemctl daemon-reload

Re-read unit files

After editing .service files

journalctl -u svc

View service logs

journalctl -u nginx -f

journalctl -b

Current boot logs

journalctl -b -p err

systemctl list-units

List all units

systemctl list-units --failed

Unit file location

Custom services

/etc/systemd/system/*.service

[Unit] After=

Dependency ordering

After=network.target

[Service] Restart=

Auto-restart policy

Restart=on-failure

[Install] WantedBy=

Boot target

WantedBy=multi-user.target

Last updated