Skip to content

systemd Units and Targets

init and systemd showed why systemd replaced SysV init: parallel startup and declarative dependencies. Both of those rest on a single concept — the unit. Everything systemd manages, whether it's a service, a mount point, or a timer, is represented as a unit conforming to one common model. This article covers the unit types, the structure of their configuration files, and the target mechanism that replaced runlevels — the foundation the next two articles, Service Management and Creating a Custom systemd Service, build directly on.

What You Will Learn

By the end of this article you will be able to:

  • explain what a unit is and name the main unit types systemd supports;
  • locate unit files and understand the priority order between their possible locations;
  • explain what the [Unit], [Service], and [Install] sections of a unit file are for;
  • distinguish between Wants=, Requires=, After=, and Before=;
  • explain what a target is, how it maps to runlevels, and how to change the default target.

What a Unit Is

A unit is a named, configuration-file-described representation of any resource systemd manages. The file extension identifies its type:

Extension Unit type Purpose
.service Service Manages a single process or daemon (e.g. nginx.service)
.socket Socket Listens on a network or Unix socket, used for socket activation
.target Target Groups other units together as a synchronization point (replaces runlevels)
.mount Mount Mounts a filesystem
.timer Timer Triggers another unit on a schedule (a cron alternative)
.path Path Watches for filesystem changes to trigger another unit
.device Device Represents hardware detected by the kernel
.swap Swap Manages a swap area

The two types used most in this course are .service and .target; .timer is covered separately in another module, in systemd Timers.

Listing all units:

systemctl list-units --type=service --state=running
UNIT             LOAD   ACTIVE SUB     DESCRIPTION
cron.service     loaded active running Regular background program processing daemon
ssh.service      loaded active running OpenBSD Secure Shell server
systemd-journald.service loaded active running Journal Service
  • LOAD — whether the unit file was read successfully;
  • ACTIVE — the overall state (active, inactive, failed);
  • SUB — a more detailed state (e.g. running, exited, dead).

Where Unit Files Live

Unit files can live in three main locations, with a clear priority order between them:

Directory Purpose Priority
/etc/systemd/system/ Units created or overridden manually by an administrator Highest
/run/systemd/system/ Temporary units generated at boot time Middle
/lib/systemd/system/ (or /usr/lib/systemd/system/) Default units installed by packages Lowest

This priority structure mirrors the configuration-file precedence covered in Package Management Basics: when a package is upgraded, its file in /lib/systemd/system/ is overwritten, but an administrator's override in /etc/systemd/system/ survives.

systemctl cat ssh.service

This command shows the full content actually being applied for a given unit — merged across every priority level — which makes it a more reliable way to find out what to edit than searching with find/grep, which might turn up the wrong, lower-priority file.

Overriding a Unit Without Copying It: Drop-ins

Copying a whole package unit into /etc/systemd/system/ just to change one line is a maintenance trap — the copy stops tracking upstream fixes the next time the package is upgraded. The intended mechanism is a drop-in: a small .conf fragment in a <unit>.service.d/ directory that systemd merges on top of the vendor unit.

sudo systemctl edit ssh.service

This opens an editor on /etc/systemd/system/ssh.service.d/override.conf; only the directives set there change, everything else keeps coming from the package file. To edit a full copy instead (when you genuinely want to replace the whole unit) use systemctl edit --full, and to discard an override entirely, systemctl revert ssh.service. systemctl edit runs daemon-reload for you; a drop-in file created by hand still needs a manual daemon-reload. systemctl cat then shows the vendor unit followed by every drop-in, in the exact order they are applied.

Warning

Most directives in a drop-in are added to the vendor unit, but a few are lists — most notably ExecStart=. To change the command a service runs, the override must first clear the old value with an empty ExecStart= line and then set the new one, otherwise systemd rejects the unit for declaring two ExecStart= commands.

The Structure of a Unit File

Unit files are text files divided into sections, in an INI-like format. A typical .service file:

nginx.service (abridged example)
[Unit]
Description=A high performance web server and reverse proxy server
After=network.target

[Service]
Type=forking
ExecStart=/usr/sbin/nginx -g 'daemon on; master_process on;'
ExecReload=/usr/sbin/nginx -s reload
Restart=on-failure

[Install]
WantedBy=multi-user.target

The three sections always mean the same thing:

  • [Unit] — the unit's general description and its dependencies on other units. Present in every unit type.
  • [Service] (or the equivalent section for a .socket, .mount, and so on) — settings specific to the unit type: which command to run, the restart policy, and so on. Used for .service units, covered in depth in Creating a Custom systemd Service.
  • [Install] — determines which target the unit attaches to when it is enabled with systemctl enable.

Dependency Directives: Wants=, Requires=, After=, Before=

These four directives express two independent dimensions — strength of requirement and ordering:

Directive Dimension Meaning
Wants= Requirement systemd tries to start the named unit, but the current unit keeps running even if it fails
Requires= Requirement If the named unit fails, the current unit is stopped too — a hard dependency
After= Ordering The current unit starts after the named unit
Before= Ordering The current unit starts before the named unit

Warning

After=/Before= only affect ordering, they do not create a dependency. If a unit needs to start after another one but isn't linked to it with Wants=/Requires=, the second unit might not start at all — the first one simply won't "wait" for something that was never going to run. In practice the two are usually combined: Wants=network-online.target together with After=network-online.target.

For example, a network-dependent service is typically written as:

[Unit]
Wants=network-online.target
After=network-online.target

This means "wait for the network to be ready if possible (ordering), but still try to start even if the network never comes up (a soft dependency)."

Targets: the Mechanism That Replaced Runlevels

A target is a special unit type that groups other units together to define a synchronization point representing a particular system state. It never starts a process directly — it only represents "the following units must be active to reach this target."

The most important targets and their approximate runlevel equivalents:

Target Approximate runlevel Meaning
poweroff.target 0 Shut the system down
rescue.target 1 Minimal, single-user recovery mode
multi-user.target 3 Full multi-user, networked, non-graphical mode — the usual state for a server
graphical.target 5 multi-user.target plus a graphical interface
reboot.target 6 Reboot the system
systemctl list-units --type=target
UNIT                   LOAD   ACTIVE SUB    DESCRIPTION
basic.target           loaded active active Basic System
cryptsetup.target      loaded active active Local Encrypted Volumes
multi-user.target      loaded active active Multi-User System
network.target         loaded active active Network
sysinit.target         loaded active active System Initialization

Viewing the Current and Default Target

systemctl get-default
multi-user.target

This shows which target the system will try to reach the next time it boots.

Changing the Default Target

sudo systemctl set-default multi-user.target

This command points the /etc/systemd/system/default.target symlink at the named target — the change takes effect starting from the next boot.

Switching the Target Immediately, in the Current Session

sudo systemctl isolate multi-user.target

isolate stops every unit not required by the named target, and activates only that target and its dependencies — this takes effect immediately, no reboot needed.

Danger

On a machine running a graphical session, systemctl isolate multi-user.target closes that graphical session immediately. On a server managed remotely over SSH, using isolate carelessly — for example, switching to rescue.target — can also cut the current SSH session, since sshd may not run under that target. Test this kind of action in a disposable test VM first.

Common Mistakes

Confusing Wants= and Requires=

Using Requires= for what is really an optional dependency means that when a loosely related unit fails to start, the entire primary service goes down with it — creating an unexpected "domino effect."

Searching for a Unit File Manually Instead of Using systemctl cat

A unit with the same name can exist in more than one directory (/etc/systemd/system/, /lib/systemd/system/). Editing whichever file find or grep happens to turn up first can mean editing the wrong, lower-priority file and seeing no effect at all. systemctl cat always shows the actual, merged configuration in effect.

Using isolate on a Production Server Without Testing First

As shown above, isolate-ing to the wrong target can cut off remote access (SSH). This kind of action should always be verified in a test environment first.

Exercises

  1. Pick five different targets from systemctl list-units --type=target and describe each one's purpose in your own words.
  2. Read the output of systemctl cat ssh.service (or another service present on your system) and explain each line under [Unit], [Service], and [Install].
  3. In a disposable test VM, try systemctl isolate rescue.target, then return with systemctl isolate multi-user.target — observe how the SSH session is affected (make sure you have physical or console access first).
  4. For a service of your choosing, decide which of Wants=, Requires=, After=, Before= you would use, and justify the choice.

Verification criterion: you should be able to explain, for a given pair of units, whether a dependency between them is a requirement, an ordering constraint, or both.

Summary

Everything systemd manages — a service, a mount, a socket, anything else — is represented as a named unit, whose file lives in /etc/systemd/system/ (highest priority) or /lib/systemd/system/. Wants=/Requires= define the strength of a dependency, while After=/Before= define startup order — two independent dimensions. A target is a special unit that groups other units and replaces the old runlevel concept; systemctl get-default/set-default change it permanently, while systemctl isolate switches immediately. The next article, Service Management, builds on this to show the day-to-day commands for managing .service units.

References