What a target is
A target is neither a process nor a service. It is a .target unit that
does nothing on its own, but through Wants= / Requires= it pulls in a set
of other units. Reaching a target means "every required service is up."
An analogy from SystemV: a target is like a runlevel, but it works through
dependencies instead of numbered scripts in /etc/rc3.d/.
Mapping to SystemV runlevels
| SystemV runlevel | systemd target | What it means |
|---|---|---|
| 0 | poweroff.target | Shutdown |
| 1 / S | rescue.target | Single-user, FS mounted, root shell |
| 1 (deeper) | emergency.target | Emergency shell only, FS not mounted |
| 2 | multi-user.target | Multi-user without NFS (historically) |
| 3 | multi-user.target | CLI server: everything runs, no GUI |
| 5 | graphical.target | Server plus display manager (GUI) |
| 6 | reboot.target | Reboot |
The aliases runlevel3.target → multi-user.target are kept for backward
compatibility with old scripts and the init 3 command.
default.target: where the system boots into
/etc/systemd/system/default.target is a symlink to the canonical target:
ls -l /etc/systemd/system/default.target
▸/usr/lib/systemd/system/graphical.target
This is what systemd picks up at startup when the kernel cmdline does not pass
systemd.unit=.... You do not need to change it by hand with ln -s. There
is a command for it:
systemctl get-default # current one
sudo systemctl set-default multi-user.target # for servers without a GUI
System targets: the full boot chain
They form a dependency tree, going from low-level to high-level:
sysinit.target ← mounts, swap, sysctl, journald
↓
basic.target ← timers.target, sockets.target, slices.target
↓
multi-user.target ← all the usual daemons (sshd, nginx, postgres)
↓
graphical.target ← + display-manager.service (gdm/lightdm)
↓
default.target ← symlink to one of the above
Alongside these there are "synchronization" targets:
network.targetis reached when all network units are configured (this does NOT mean the host is online)network-online.targetis reached when the network is actually ready and an IP is assigned (for services that are useless without connectivity)local-fs.targetis reached when all local filesystems from mount-and-fstab are mountedremote-fs.targetis reached when all NFS/SMB mounts are in placetime-sync.targetis reached when the clock is synchronized (see chrony-and-ntp)
How a target collects its units
Two mechanisms:
1) Through the [Install] section of a service. When you run systemctl enable nginx,
a symlink is created:
/etc/systemd/system/multi-user.target.wants/nginx.service
→ /usr/lib/systemd/system/nginx.service
That .wants/ directory (and .requires/) is what the target picks up at startup.
2) Through the WantedBy= directive in the target file itself, for system targets.
cat /usr/lib/systemd/system/graphical.target
# [Unit]
# Description=Graphical Interface
# Requires=multi-user.target
# Wants=display-manager.service
# After=multi-user.target display-manager.service
# AllowIsolate=yes
AllowIsolate=yes is critical. Without it a target cannot be made the default,
and you cannot switch to it through isolate.
Switching between targets on the fly
sudo systemctl isolate multi-user.target # drop to CLI without a reboot
sudo systemctl isolate graphical.target # bring the GUI back
sudo systemctl isolate rescue.target # repair without a reboot
sudo systemctl rescue # same as isolate rescue.target
sudo systemctl emergency # emergency shell
isolate stops everything that the new target does NOT need and starts what it
does. The name is odd, but that is the one.
Passing a target through GRUB at boot
If the system does not boot into default.target, you can override it once from
GRUB. At the menu screen, press e to edit, and on the linux ... line append:
systemd.unit=rescue.target
Then Ctrl+X to boot. This applies to this boot only; the default is not changed.
Debugging: what a target pulls in
systemctl list-units --type=target # active targets
systemctl list-dependencies multi-user.target # the tree of all units
systemctl list-dependencies --reverse nginx # which targets nginx belongs to
systemctl cat multi-user.target # the resulting file
Creating your own target rarely makes sense, for example for a group of
custom services that you switch on and off together. Most of the time
WantedBy=multi-user.target in ordinary units is enough.