OnCallReady

Lesson 2.18 · systemd · 15 min read

enable, disable, mask, static, targets

In plain words

Imagine a lamp with an evening timer, and a separate button to turn it on right now. Setting the evening timer does not light the lamp now; pressing the button now does not set the timer. And if you want the lamp to never light, removing the timer is not enough, because anyone could still press the button. You unplug it from the wall.

systemctl enable is the evening timer (a link used at boot), start is the button, and mask is unplugging it: the unit is linked to /dev/null (an empty file that swallows everything) and cannot start at all, not even when another unit asks for it. static means "this lamp has no timer socket; it only lights when something else needs it". Targets are the phases of the evening: multi-user.target is "the house is up".

Why this matters

"I disabled it, so it's off" is a sentence that has caused real outages: a "disabled" service got started anyway by something that needed it, in the middle of maintenance. systemd has several different on/off switches, and each answers a different question.

What you need to know already: 2.5 (enable vs start, the .wants symlink), 2.1 (targets), 2.14 (Wants=).

enable and start are independent

systemctl enable foo     create the symlink in multi-user.target.wants/ -
                         affects the NEXT boot. Changes nothing now.
systemctl start foo      run it NOW. Changes nothing about boot.
systemctl enable --now   both.

A disabled unit starts perfectly well by hand. An enabled unit can be dead right now. systemctl status reports the two independently, and confusing them is how "but I enabled it!" incidents happen.

enable literally just creates a symlink (the kind ln -s makes: a small file that points at another path), driven by the [Install] section. You can see it:

ls -l /etc/systemd/system/multi-user.target.wants/

mask is the big hammer

sudo systemctl mask cron

Puts a symlink in /etc pointing the unit at /dev/null - a special empty file that discards anything written to it. systemd sees an empty unit and refuses it. Now it cannot start at all - not by hand, not as a dependency of something else, not at boot:

Failed to start cron.service: Unit cron.service is masked.

That is what disable cannot give you: a disabled unit still starts if anything else wants it. Mask when you need a guarantee - for example during maintenance, where an automatic start would be dangerous. unmask to undo.

static

systemctl is-enabled NAME prints one word: the unit's boot setting.

$ systemctl is-enabled systemd-journald
static

(systemd-journald is the daemon that keeps the journal.)

"static" (a static unit) means the unit has no [Install] section, so there is nothing to enable or disable - it is pulled in by other units or by systemd itself. Trying to enable it is an error, not an oversight.

Other answers you will see: enabled, disabled, masked, alias (another name for a unit, like sshd.service for ssh.service), indirect (not enabled itself, but enabled through another unit), generated (written at boot by systemd from another file, e.g. mounts from /etc/fstab), transient (created on the fly with systemd-run, 2.26).

Targets

A target is a named point in the startup, and units attach themselves to it with WantedBy=. Older Linux called these runlevels (numbered modes, 1 = repair, 3 = normal server, 5 = desktop). The ones you meet:

multi-user.target   normal server: everything up, logins work, no GUI.
graphical.target    multi-user plus a graphical login screen.
rescue.target       repair mode: one root shell, minimal services.
emergency.target    barely anything - a root shell on the console only.

(The console is the machine's own screen and keyboard - for your VM, the UTM window - as opposed to logging in over SSH.)

Surprise on a fresh Ubuntu Server: systemctl get-default says graphical.target. That is the packaged default; with no graphical login installed it simply pulls in multi-user.target and nothing more. Setting it to multi-user.target is tidy, not required.

systemctl get-default          which target boots
sudo systemctl set-default multi-user.target
systemctl isolate rescue.target    switch NOW, stopping everything not wanted

isolate is a live switch: it starts what the target wants and stops everything else. Over SSH that includes sshd, so you are disconnected and cannot get back in - rescue.target only gives you a prompt on the console. It is a console-only operation. (The simulator refuses it and explains why rather than locking you out.)

What you can now do

Why it helps

"I enabled it but it is not running" and "it came back after I disabled it" are two incidents this lesson prevents. The second is the important one: during maintenance on a database or before swapping a disk, a disabled service can still be started by another unit, a socket or a timer, while a masked one cannot. That is a runbook step you will write.

Targets matter for recovery: knowing that isolate rescue.target over SSH stops your own connection is the difference between a controlled change and a trip to the console. get-default explains why a machine boots with or without a desktop. And recognising static stops you from "fixing" units that are meant to be started by others.

Commands in this lesson

systemctl

FAQ

What exactly does systemctl enable change on disk?

It creates the links described in the unit's [Install] section. For WantedBy=multi-user.target that is a link /etc/systemd/system/multi-user.target.wants/foo.service pointing at the unit file. It prints each link it creates. disable removes them. Nothing is started or stopped unless you add --now. You can look for yourself with ls -l /etc/systemd/system/multi-user.target.wants/.

What is the difference between disable and mask?

A disabled unit will not start by itself at boot, but it can still be started by hand, by another unit that wants or requires it, by a socket, or by a timer. A masked unit is linked to /dev/null in /etc/systemd/system, so nothing can start it: "Unit foo.service is masked." Use mask when you need a guarantee; unmask puts it back.

Why can I not enable some units?

Units without an [Install] section are static: there is nothing to enable, because they are designed to be started by other units, sockets or timers. systemd-journald and most timer-driven jobs are static. Trying to enable one prints a message saying the unit has no installation config. Enable the timer or socket that starts it instead.

What are runlevels, and what replaced them?

Before systemd, a machine booted into one numbered "runlevel": 1 for single-user repair mode, 3 for a normal text server, 5 with a graphical desktop. systemd replaced them with targets: rescue.target is roughly 1, multi-user.target 3, graphical.target 5. Several targets can be active at once. systemctl get-default shows which one boots; set-default changes it.

Why is isolate dangerous over SSH?

systemctl isolate X.target starts everything that target needs and stops every unit it does not need. rescue.target and emergency.target do not include the SSH server or networking, so isolating to them over SSH stops the very service you are connected through. You are cut off and can only continue on the machine's own console. Use it from the console, or only with targets that keep SSH.

In an interview Junior

What is the difference between disabling a service and masking it?

systemctl disable only removes the boot symlinks that enable created. The unit still starts by hand, or when something else wants it - so "I disabled it" does not mean "it is off".

systemctl mask points the unit at /dev/null with a symlink in /etc, so it cannot start at all: not by hand, not as a dependency, not at boot. Any attempt fails with "Unit cron.service is masked." unmask undoes it. I mask when I need a guarantee, like maintenance where an automatic start would do harm.

And the boot side: enable = start at the next boot, start = run now, enable --now = both. systemctl is-enabled prints one word: enabled, disabled, masked, or static (no [Install] section; other units pull it in - not "off").

Also asked: What does static mean in systemctl is-enabled? · What are targets, and how do you change the one a server boots into? · Why is systemctl isolate rescue.target dangerous over SSH?

Practise this lesson in the terminal Free, in your browser - a real Ubuntu terminal to try it in, with missions that check your work.