Units and Targets
A unit is systemd’s basic building block — one configuration file, describing one thing systemd manages. Not every unit is a service, though, and understanding the different types, along with how systemd groups them into an overall system state called a target, is what makes the rest of this section make sense.
Unit Types
Units are distinguished by their file extension, and each type manages a different kind of resource:
| Extension | Manages |
|---|---|
.service | A program or daemon — the type you’ll work with most |
.mount | A filesystem mount point |
.socket | A network or IPC socket, often used to start a service on demand when something connects to it |
.timer | A scheduled, recurring trigger — systemd’s alternative to cron |
.target | A named grouping of other units, representing a system state |
.device | A piece of hardware the kernel has detected |
.path | A trigger based on a filesystem path changing |
You’ll spend most of your practical time with .service and .target units in this section — the others are worth recognizing on sight, but won’t come up hands-on here.
Where Unit Files Actually Live
Unit files aren’t scattered arbitrarily — they live in a small number of fixed locations, and where a given unit file sits determines how easily it can be overridden.
ls /etc/systemd/system
ls /lib/systemd/system/lib/systemd/system (sometimes /usr/lib/systemd/system, depending on the distribution) holds unit files installed by packages — this is where a package’s apt install puts the service definition it ships with. /etc/systemd/system holds units an administrator has created or customized directly, and it takes priority over the package-provided version when both exist. This mirrors a pattern already familiar from elsewhere: package-managed content stays in its own managed location, while local, administrator-owned customization goes in /etc, exactly the separation /etc has represented throughout this course.
Exploring What’s Actually Loaded
Two systemctl subcommands — covered fully in the next chapter — are worth previewing here just for exploration:
systemctl list-units --type=serviceShows every service unit currently loaded into systemd, running or not.
systemctl list-unit-files --type=serviceShows every service unit systemd knows about on disk, including ones that aren’t currently loaded or active, along with whether each is enabled to start automatically.
Targets: Grouping Units Into A System State
A target doesn’t run anything itself — it’s a named collection of other units, representing a coherent overall system state. Booting the system to a normal, usable, multi-user command-line environment means reaching multi-user.target, which in turn depends on a whole collection of individual service units all being active — networking, logging, and so on.
This directly replaces the older SysV concept of runlevels — numbered system states (0 through 6) that predate systemd entirely. You’ll still see runlevel numbers referenced in older documentation and habits, so the mapping is worth knowing:
| Old Runlevel | Modern systemd Target | Meaning |
|---|---|---|
| 0 | poweroff.target | Shut the system down |
| 1 | rescue.target | Single-user, minimal recovery mode |
| 3 | multi-user.target | Full multi-user, no graphical interface |
| 5 | graphical.target | Multi-user, with a graphical interface |
| 6 | reboot.target | Restart the system |
There’s also emergency.target, with no direct old-style runlevel equivalent — an even more minimal state than rescue.target, typically only reached automatically when something has gone wrong early enough in boot that even rescue.target’s dependencies can’t be satisfied.
Checking And Changing The Default Target
The default target is whichever one the system boots into normally — check it directly:
systemctl get-defaultOn a typical server with no graphical interface, this returns multi-user.target. Changing it is equally direct:
sudo systemctl set-default multi-user.targetThis is a genuinely common real task — converting a desktop-oriented installation into a headless server, for instance, means setting the default target to multi-user.target instead of graphical.target, so the system stops trying to start a graphical environment it may not even have hardware or drivers to support.
Switching Targets Without Rebooting: isolate
sudo systemctl isolate rescue.targetisolate switches the running system to a different target immediately, stopping any units that the new target doesn’t need and starting any it does — effectively transitioning system state live, without a reboot.
Warning
isolate can stop a large number of running services at once, including ones you didn’t intend to touch, if the target you’re switching to doesn’t include them. Switching to rescue.target or emergency.target on a remote system you’re connected to over SSH is a particularly easy way to lock yourself out — the networking and SSH service themselves may not survive the transition, since those minimal targets aren’t guaranteed to include them as dependencies. Confirm exactly what a target includes before isolating into it on any system you don’t have physical access to.
Dependency Directives: A Preview
Unit files express their relationships to each other through directives like Wants=, Requires=, After=, and Before= — Wants= and Requires= describe which other units should also be active, while After= and Before= control ordering without implying a dependency at all. Full unit file syntax, including these directives in context, is the subject of a later chapter — for now, just recognize that this is how a target like multi-user.target actually knows which individual services it depends on, rather than that grouping being some kind of hardcoded special case.
What’s Next
With units and targets understood conceptually, the next chapter makes it practical: systemctl, the single command responsible for starting, stopping, checking, and enabling nearly everything covered in this chapter.