LFCS 60 ๐ง systemd โ Concepts and Architecture
systemd is the init system and service manager that replaced SysVinit and Upstart on most modern Linux distributions. It is not just a boot script runner. It is a dependency-aware, parallel-starting, socket-activated service manager that also handles device management, mount points, timers, and resource control. The core abstraction is the unit: a resource that systemd manages, described in a declarative configuration file with a type-specific suffix. A service is a .service unit, a mount point is a .mount unit, a timer is a .timer unit, and a boot target is a .target unit . The unit file is the contract; systemctl is the tool that reads it, starts it, stops it, enables it, and reports its state.
The architecture rests on two kinds of dependencies: requirement dependencies (Requires=, Wants=) and ordering dependencies (After=, Before=) . Requirement dependencies determine whether another unit must be started; ordering dependencies determine when. systemd resolves both simultaneously, which is what allows it to start services in parallel while still respecting the order that the services actually need. The LFCS exam expects you to understand this model well enough to diagnose a service that fails to start at boot, to enable a unit, to inspect its dependencies, and to read its logs through the journal.
Key point: A unit is a resource described by a unit file; the type is the suffix (.service, .socket, .timer, .target, .mount). Requires= pulls in a unit and fails if it fails; Wants= pulls it in but ignores failure. After= and Before= control the order but do not pull the unit in. systemctl enable creates the symlink that starts the unit at boot; systemctl start starts it now. The two are independent.
Why systemd is built on units and dependencies
The parallel-start problem. SysVinit started services sequentially in a fixed order defined by runlevel scripts. The boot time was the sum of every service’s startup time, and the order was often wrong for services that did not actually depend on each other. systemd replaces the fixed order with a dependency graph. Each unit declares what it requires and what it must run after. systemd computes the graph and starts every unit whose dependencies are satisfied at the same time . The boot is faster because the parallelism is real, and it is correct because the ordering constraints are explicit.
The socket-activation problem. A service that starts a network listener must bind the port before it can accept connections. If the service is slow to start, the port is unavailable during the startup. systemd’s socket activation solves this: a .socket unit binds the port, and systemd starts the corresponding .service only when the first connection arrives. The service inherits the listening socket, so no connection is lost . The socket unit and the service unit share the same name (minus the suffix), which is how systemd knows they are paired.
The timer problem. cron is a separate daemon with its own syntax and its own log. systemd timers replace cron for system services: a .timer unit defines when to run, and the corresponding .service unit defines what to run. The timer can be calendar-based (OnCalendar=) or monotonic (OnBootSec=, OnUnitActiveSec=). The advantage is that the timer and the service are both managed by systemd, and their output goes to the same journal .
The target problem. SysVinit runlevels are replaced by targets. A target is a unit that groups other units for a synchronization point. multi-user.target is the equivalent of runlevel 3 (text mode with networking), and graphical.target is the equivalent of runlevel 5 (GUI). The default target is what the system boots into, and it is set with systemctl set-default . The target does not do anything itself; it is a grouping mechanism that other units order themselves against.
The drop-in problem. A unit file should not be edited directly. The package manager owns it, and an upgrade overwrites it. The correct customization mechanism is the drop-in directory: foo.service.d/*.conf. The files in the drop-in directory are merged after the main unit file, and they can add or override any directive. systemctl edit foo.service creates the drop-in directory and opens an editor. systemctl edit --full foo.service opens the full unit file for editing, which copies it to /etc and overrides the package version . The drop-in precedence is /etc over /run over /lib, and the files within a directory are applied in lexicographic order .
a. The unit types and the unit file structure
The unit type is the suffix of the file. The type determines the sections and directives that are valid.
| Suffix | Type | Purpose |
|---|---|---|
.service | Service | A daemon or a one-shot process |
.socket | Socket | Socket activation |
.timer | Timer | Scheduled activation |
.target | Target | Grouping and synchronization |
.mount | Mount | A filesystem mount point |
.automount | Automount | On-demand mount |
.swap | Swap | Swap space |
.device | Device | A device managed by udev |
.slice | Slice | Resource control group |
A service unit has three main sections: [Unit], [Service], and [Install]. The [Unit] section contains the description, the dependencies, and the conditions. The [Service] section contains the execution details: the ExecStart command, the Restart policy, the User and Group, and the resource limits. The [Install] section contains the WantedBy directive that determines what target the unit is enabled into .
# /etc/systemd/system/myapp.service
[Unit]
# The human-readable description
Description=My Application
# Start after the network is online
After=network-online.target
# Pull in the network target
Wants=network-online.target
[Service]
# The command to run
ExecStart=/usr/local/bin/myapp
# Restart the service if it exits
Restart=on-failure
# Run as a dedicated user
User=myapp
Group=myapp
# The working directory
WorkingDirectory=/opt/myapp
# The environment file
EnvironmentFile=/etc/myapp/env
[Install]
# Enable into the multi-user target
WantedBy=multi-user.target
The After=network-online.target is the ordering dependency: the service starts after the network is online. The Wants=network-online.target is the requirement dependency: the network target is pulled in, but the service does not fail if the target fails. Without the Wants=, the After= has no effect if the network target is not already part of the boot transaction .
b. The systemctl lifecycle and the enable/start distinction
systemctl is the command that manages units. The most important distinction is between start and enable. start starts the unit now, in the current boot. enable creates the symlink in the target’s .wants directory that causes the unit to start at the next boot. They are independent: a unit can be started but not enabled, or enabled but not started.
# Start the service now
# This does not survive a reboot
sudo systemctl start myapp.service
# Enable the service to start at boot
# This does not start it now
sudo systemctl enable myapp.service
# Enable and start in one command
sudo systemctl enable --now myapp.service
# Stop the service now
sudo systemctl stop myapp.service
# Disable the boot start
sudo systemctl disable myapp.service
# Check the status
sudo systemctl status myapp.service
# Check whether it is enabled
systemctl is-enabled myapp.service
# Check whether it is active
systemctl is-active myapp.service
The status output shows the Loaded: line with the unit file path and whether it is enabled, and the Active: line with the current state and the last log lines. The Loaded: line is the verification that the unit file was found and parsed. A Loaded: not-found means the unit file is missing or has a syntax error that prevented the load . The Active: states are active (running), inactive (dead), activating, deactivating, and failed. A failed state means the service exited with an error, and the journalctl -u command shows the details.
The daemon-reload command is required after any unit file change. systemd parses the unit files at startup and caches them. An edit to a unit file is not seen until systemctl daemon-reload re-reads the cache. Without it, systemctl start uses the old definition .
c. The target and the journal
The target is the systemd replacement for the SysVinit runlevel. The default target is what the system boots into. The common targets are multi-user.target (text mode with networking) and graphical.target (GUI). The rescue.target is single-user mode, and emergency.target is the minimal shell .
# Show the current default target
systemctl get-default
# Output: graphical.target
# Set the default target to text mode
sudo systemctl set-default multi-user.target
# List the active targets
systemctl list-units --type=target
# Isolate a target immediately
# This stops the units that are not part of the target
sudo systemctl isolate multi-user.target
The isolate command is the equivalent of telinit in SysVinit: it stops the units that are not in the target’s dependency graph and starts the ones that are. The set-default command changes what the system boots into on the next boot. The two are different: isolate changes the current state, and set-default changes the next boot state.
The journal is systemd’s logging system. journalctl reads the journal, and the -u flag filters by unit.
# Show the logs for a specific unit
journalctl -u myapp.service
# Follow the logs
journalctl -u myapp.service -f
# Show the last 50 lines
journalctl -u myapp.service -n 50
# Show the logs since a time
journalctl -u myapp.service --since "1 hour ago"
# Show only error-priority logs
journalctl -p err
The journal is persistent by default on most distributions, and it stores the logs in /var/log/journal. If the journal is not persistent, the logs are lost on reboot. The journalctl --disk-usage command shows the current size, and the journalctl --vacuum-size=500M command trims it to a size limit.
Complete Example Session
The following session creates a service unit, starts it, enables it, checks its status, and reads its logs.
# ============================================
# PART 1: CREATE THE UNIT FILE
# ============================================
sudo tee /etc/systemd/system/myapp.service > /dev/null << 'EOF'
[Unit]
Description=My Application
After=network-online.target
Wants=network-online.target
[Service]
ExecStart=/usr/local/bin/myapp
Restart=on-failure
User=myapp
Group=myapp
[Install]
WantedBy=multi-user.target
EOF
# ============================================
# PART 2: RELOAD SYSTEMD
# ============================================
# systemd must re-read the unit files
sudo systemctl daemon-reload
# ============================================
# PART 3: START AND ENABLE
# ============================================
# Start the service now
sudo systemctl start myapp.service
# Enable it for the next boot
sudo systemctl enable myapp.service
# Or both in one command
sudo systemctl enable --now myapp.service
# ============================================
# PART 4: CHECK THE STATUS
# ============================================
systemctl status myapp.service
# Output includes:
# Loaded: loaded (/etc/systemd/system/myapp.service; enabled)
# Active: active (running) since ...
# Check the enabled state
systemctl is-enabled myapp.service
# Output: enabled
# ============================================
# PART 5: READ THE LOGS
# ============================================
journalctl -u myapp.service -n 20
# The last 20 log lines for the unit
# ============================================
# PART 6: STOP AND DISABLE
# ============================================
sudo systemctl stop myapp.service
sudo systemctl disable myapp.service
The six parts cover the unit file, the daemon reload, the start and enable, the status check, the log read, and the cleanup. What the session demonstrates is that the unit file is the definition, daemon-reload is required after the edit, start and enable are independent, and status and journalctl are the verification tools.
Quick Reference
Unit Types
| Suffix | Purpose |
|---|---|
.service | Daemon or process |
.socket | Socket activation |
.timer | Scheduled activation |
.target | Grouping and sync |
.mount | Filesystem mount |
Dependency Directives
| Directive | Meaning |
|---|---|
Requires= | Pull in; fail if it fails |
Wants= | Pull in; ignore failure |
After= | Order after; do not pull in |
Before= | Order before; do not pull in |
systemctl Lifecycle
| Command | Effect |
|---|---|
start | Start now |
stop | Stop now |
restart | Stop then start |
reload | Re-read config |
enable | Start at boot |
disable | Do not start at boot |
enable --now | Both |
status | State and logs |
daemon-reload | Re-read unit files |
Unit File Directories
| Path | Purpose |
|---|---|
/etc/systemd/system/ | Admin overrides (highest priority) |
/run/systemd/system/ | Runtime units |
/lib/systemd/system/ | Package units |
/etc/systemd/system/foo.service.d/ | Drop-ins |
Best Practices
โ Do This:
# Use After and Wants together
After=network-online.target
Wants=network-online.target
# Reload after editing
sudo systemctl daemon-reload
# Enable and start in one command
sudo systemctl enable --now myapp
# Check the logs after a failure
journalctl -u myapp -n 50
โ Don’t Do This:
# Edit the package unit file directly
sudo vim /lib/systemd/system/nginx.service # overwritten on upgrade
# Forget daemon-reload
sudo systemctl start myapp # uses the old definition
# Assume start enables
sudo systemctl start myapp # does not survive reboot
Common Pitfalls
| Pitfall | Why It Happens | Fix |
|---|---|---|
| Service not starting at boot | enable not run | systemctl enable |
| Unit not found | No daemon-reload after creation | systemctl daemon-reload |
After= has no effect | Target not pulled in | Add Wants= |
| Edits ignored | No daemon-reload | systemctl daemon-reload |
| Service fails silently | Checked only start | systemctl status, journalctl |
Real-World Examples
1. Create a service
[Unit]
Description=My App
[Service]
ExecStart=/usr/bin/myapp
[Install]
WantedBy=multi-user.target
2. Reload after edit
sudo systemctl daemon-reload
3. Enable and start
sudo systemctl enable --now myapp
4. Check status
systemctl status myapp
5. Check enabled
systemctl is-enabled myapp
6. Read logs
journalctl -u myapp -n 50
7. Follow logs
journalctl -u myapp -f
8. Set default target
sudo systemctl set-default multi-user.target
9. Isolate target
sudo systemctl isolate rescue.target
10. Override a unit
sudo systemctl edit myapp.service
Visual
The Unit and Dependency Model
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ myapp.service โ
โ Requires=db.service โ
โ After=network-online.target โ
โ โ โ
โ โผ โ
โ systemd resolves the graph โ
โ starts db, waits for network, then myapp โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
start vs enable
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ start โ
โ โ running now โ
โ โ lost on reboot โ
โ โ
โ enable โ
โ โ symlink in multi-user.target.wants โ
โ โ starts on next boot โ
โ โ does not start now โ
โ โ
โ enable --now = both โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Target and Journal
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ multi-user.target โ
โ โ myapp.service (WantedBy) โ
โ โ nginx.service (WantedBy) โ
โ โ
โ systemctl get-default โ multi-user.target โ
โ journalctl -u myapp โ unit logs โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Summary
| Item | Value |
|---|---|
| Init system | systemd |
| Unit | Resource described by a unit file |
| Unit suffix | .service, .socket, .timer, .target |
Requires= | Pull in, fail on failure |
Wants= | Pull in, ignore failure |
After= | Order only |
start | Start now |
enable | Start at boot |
daemon-reload | Re-read unit files |
journalctl -u | Unit logs |
| Target | Runlevel replacement |
Key takeaways:
- The unit is the core abstraction. Every resource systemd manages is a unit, and the suffix determines the type. A service is a
.serviceunit, a mount is a.mountunit, a timer is a.timerunit, and a boot target is a.targetunit . - Requirement and ordering dependencies are separate.
Requires=andWants=determine whether a unit is pulled in;After=andBefore=determine when it runs. TheAfter=without aWants=has no effect if the other unit is not already in the transaction . startandenableare independent.startruns the unit now;enablecreates the symlink that runs it at boot. The common mistake is to runstartand assume the service will survive a reboot .daemon-reloadis required after any unit file change. systemd caches the unit files at startup, and the cache is only refreshed bydaemon-reload.- The target replaces the runlevel.
multi-user.targetis text mode,graphical.targetis GUI.set-defaultchanges the next boot, andisolatechanges the current state . - The journal is the log destination.
journalctl -ureads the logs for a unit, andstatusincludes the last few lines. A failed service is diagnosed withstatusandjournalctl, not withstartalone .
Remember: systemd is not a boot script runner. It is a dependency-aware service manager. The unit file declares the resource and its dependencies; systemctl starts, stops, enables, and inspects it; the journal records what happened. The two distinctions that matter most for the LFCS exam are start versus enable and requirement versus ordering dependencies. A service that works when started manually but not at boot is a service that was not enabled. A service that starts in the wrong order is a service whose After= has no corresponding Wants=. The unit file is the contract, and systemctl status is the verification.
Stop using slow, ad-bloated tool sites! ๐คฎ
๐ Search “KandZ Tools” on Google to use many professional utilities for free.
KandZ.me is the ultimate minimalist hub for:
โ
Finance (Mortgage, Interest, Inflation)
โ
Tech (Base64, JSON, Dev Suite, IP)
โ
Health (BMI, BMR, TDEE)
โ
Productivity (Timer, Workspace, QR)
โก๏ธ Fast & Private
๐ No data leaves your device
๐ 100% Free
๐ Use it now: https://tools.kandz.me
๐ Bookmark itโyouโll need it later!