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=, andBefore=; - 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:
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.
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.
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:
[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.serviceunits, covered in depth in Creating a Custom systemd Service.[Install]— determines which target the unit attaches to when it is enabled withsystemctl 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:
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 |
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
This shows which target the system will try to reach the next time it boots.
Changing the Default 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
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
- Pick five different targets from
systemctl list-units --type=targetand describe each one's purpose in your own words. - Read the output of
systemctl cat ssh.service(or another service present on your system) and explain each line under[Unit],[Service], and[Install]. - In a disposable test VM, try
systemctl isolate rescue.target, then return withsystemctl isolate multi-user.target— observe how the SSH session is affected (make sure you have physical or console access first). - 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.