Lab 19: systemd Deep Dive

Time: 40 minutes | Level: Advanced | Docker: docker run -it --rm --privileged ubuntu:22.04 bash

systemd is the init system and service manager for virtually all modern Linux distributions. It manages the full service lifecycle, handles dependencies, activates services on-demand via socket activation, schedules tasks with timers, and provides structured logging via journald. In this lab you'll write unit files from scratch, use all major unit types, analyze boot performance, and apply runtime overrides with drop-ins.

⚠️ Container note: systemd is not running as PID 1 in a plain Docker container. We'll demonstrate unit file authoring, structure, and logic — and use systemd-analyze and journalctl where possible. For full systemd behavior, use a VM or systemd-nspawn.


Step 1: Unit File Types and Structure

apt-get update -qq && apt-get install -y -qq systemd procps

# See all unit types available
systemd --version | head -2

# List unit files by type
ls /lib/systemd/system/*.service | wc -l
ls /lib/systemd/system/*.timer  | wc -l
ls /lib/systemd/system/*.socket | wc -l
ls /lib/systemd/system/*.target | wc -l
ls /lib/systemd/system/*.mount  | wc -l
ls /lib/systemd/system/*.path   2>/dev/null | wc -l

echo ''
echo "=== Example unit files ==="
ls /lib/systemd/system/*.timer | head -3
ls /lib/systemd/system/*.socket | head -3

📸 Verified Output:

Unit Type
Extension
Purpose

Service

.service

Long-running daemon or one-shot task

Timer

.timer

Schedule a service (cron replacement)

Socket

.socket

Socket activation — start service on first connection

Target

.target

Group units, define boot stages (like runlevels)

Mount

.mount

Mount filesystem (replaces /etc/fstab entries)

Path

.path

Trigger service on filesystem path change

Scope

.scope

Externally-created processes (Docker containers)

Slice

.slice

cgroup hierarchy node for resource management

💡 Unit files are just INI-format text files. systemd reads them from /lib/systemd/system/ (package defaults) and /etc/systemd/system/ (admin overrides — takes precedence).


Step 2: Anatomy of a Service Unit

📸 Verified Output:

Now let's write a production-quality service unit:

📸 Verified Output:

💡 Restart=on-failure restarts only on non-zero exit codes or signal deaths. Use Restart=always to restart even on clean exit (e.g., for persistent daemons). StartLimitBurst=3 prevents restart loops from hammering the system.


Step 3: Dependency Ordering — After, Wants, Requires, BindsTo

📸 Verified Output:

💡 Wants= vs Requires=: Always prefer Wants= unless the dependency is truly critical. Requires= causes your service to fail if the dependency fails — often too strict for graceful degradation.


Step 4: systemd Timers — Replacing cron

Timers are .timer unit files that activate a paired .service at scheduled times:

📸 Verified Output:

📸 Verified Output:

💡 Unlike cron, systemd timers: (1) catch up on missed runs with Persistent=true, (2) support RandomizedDelaySec to avoid thundering herd, (3) log to journald, (4) have proper dependency management.


Step 5: Socket Activation

Socket activation allows systemd to hold open a socket and only start the service when the first connection arrives:

📸 Verified Output:

💡 Advantage over traditional daemons: With socket activation, you can stop/restart a service for updates with zero dropped connections. Connections to the socket queue while the service restarts. nginx, SSH, and DBus all support this.


Step 6: journald — Structured Logging

📸 Verified Output:

📸 Verified Output:

💡 Custom fields: Services can emit structured journal fields by writing echo "MYFIELD=value" > /dev/kmsg or using sd_journal_send(). These are then queryable with journalctl MYFIELD=value.


Step 7: Drop-ins — Overriding Units Without Editing Originals

Drop-ins let you extend or override any unit file safely (survives package upgrades):

📸 Verified Output:

💡 Why clear ExecStart first? ExecStart= in a drop-in appends to the list by default. Writing ExecStart= (empty) first clears the existing value. This is required for ExecStart but not for most other keys (which replace, not append).


Step 8: Capstone — systemd-analyze Boot Performance and Full Service Stack

Scenario: The ops team reports the server boots slowly and a critical service sometimes fails. You need to: (1) analyze boot time, (2) find the slow unit, (3) fix a failing service with a proper unit file, (4) add monitoring via a timer, and (5) verify with drop-in hardening.

📸 Verified Output:


Summary

Concept
Syntax / Command
Purpose

Service unit

[Service] ExecStart=

Define daemon entrypoint

Restart policy

Restart=on-failure

Auto-restart on crash

Dependency

After=, Requires=, Wants=

Boot ordering and activation

Conditions

ConditionPathExists=

Guard — skip, not fail

Timer (calendar)

OnCalendar=*-*-* 03:00:00

Schedule by date/time

Timer (relative)

OnBootSec=5min, OnUnitActiveSec=

Schedule from boot/last-run

Socket activation

ListenStream= in .socket

On-demand service start

journald query

journalctl -u NAME -p err

Query structured logs

JSON logs

journalctl -o json-pretty

Machine-readable log fields

Drop-in override

/etc/systemd/system/NAME.d/*.conf

Extend without forking

Boot analysis

systemd-analyze blame

Find slow boot services

Critical chain

systemd-analyze critical-chain

Longest boot dependency path

Security audit

systemd-analyze security NAME

Service hardening score

Key insight: systemd timers + socket activation + drop-ins replace cron, inetd, and manual config file patching — while adding dependency tracking, journald logging, and cgroup resource limits automatically.

Last updated