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
- choose between disable and mask, and prove which one you have
- read
is-enabledanswers, including static - check and change the target the box boots into