On a systemd host you ran `systemctl stop` and `systemctl disable` on a unit, yet after a reboot it is running again. What does `systemctl mask` do differently, and what does masking look like on disk?
answer
- disabling only edits the boot wiring
- something else can still pull it in
- a symlink pointing nowhere
- /dev/null in place of a unit file
- the running process is not touched
basics
~20 sDisabling only removes the unit's [Install] symlinks, so anything that pulls the unit in as a dependency, or a matching socket, path or timer unit, still activates it. Masking symlinks the unit name to /dev/null under /etc/systemd/system, which makes every activation path fail.
solid answer
~40 s`systemctl disable` removes the symlinks the unit's `[Install]` section created — typically under `multi-user.target.wants/`. That stops the unit being pulled in *by that route only*. If another unit lists it as a dependency, or a matching `.socket`, `.path` or `.timer` unit activates it, or something calls `systemctl start` on it, it comes right back. `systemctl mask` is the absolute veto: systemd creates a symlink from the unit name in `/etc/systemd/system` to `/dev/null`, which makes the unit unloadable, so every activation path — manual, dependency-driven, or activation-unit-driven — fails with "Unit foo.service is masked." `systemctl unmask` removes the link, and `--runtime` places it under `/run` so it disappears at the next boot.
code
bash · 5 linessystemctl list-unit-files --state=masked
systemctl mask --now foo.service
ls -l /etc/systemd/system/foo.service
systemctl start foo.service # Failed to start foo.service: Unit foo.service is masked.
systemctl unmask foo.servicego deeper
Know that disabling only affects what starts at boot and that masking is the stronger action which blocks a unit from starting at all.
Describe both mechanisms on disk — [Install] symlinks under a target's .wants directory versus a symlink to /dev/null — and name the activation routes that survive a plain disable.
Investigate what is actually pulling a unit in before reaching for mask, weigh the blast radius on units that require it, and prefer --runtime when you want the change to expire at reboot.
Treat masks as configuration that must be discoverable: record why a unit is masked on a host, avoid hand-masking as fleet policy, and prefer configuration management that makes the state auditable.
## Two different mechanisms, often confused Disabling and masking sound like degrees of the same thing. They are not: one edits the boot-time wiring, the other makes the unit impossible to load at all. ## What disable actually removes A unit's `[Install]` section is a recipe for symlinks: ```ini [Install] WantedBy=multi-user.target ``` Enabling creates `/etc/systemd/system/multi-user.target.wants/foo.service` pointing at the unit file. Disabling deletes it. That is the entire operation — it changes what a target pulls in at boot, and nothing else. systemd will happily start a disabled unit at any time. So a disabled unit still runs when: - another unit names it in a dependency directive and that unit starts; - a `foo.socket`, `foo.path` or `foo.timer` exists and is itself enabled — activation units start their paired service on demand, and disabling the `.service` does nothing to the activator; - a package upgrade re-runs its enable logic; - anyone, or any automation, runs `systemctl start foo.service`. ## What mask does ```bash systemctl mask foo.service ls -l /etc/systemd/system/foo.service # /etc/systemd/system/foo.service -> /dev/null ``` Because `/etc/systemd/system` takes precedence over `/usr/lib/systemd/system`, the unit that systemd finds for that name is an empty device node, and systemd treats a unit symlinked to `/dev/null` as explicitly masked. Loading it fails, which means *every* route fails: ``` $ systemctl start foo.service Failed to start foo.service: Unit foo.service is masked. ``` A dependency on a masked unit fails too. That is the point, and it is also the risk: mask something that other units genuinely require and you will break their startup instead of quietly not starting the one thing you meant to suppress. Before masking, it is worth knowing what pulls the unit in. ## The flags that matter - `systemctl unmask foo.service` removes the symlink and restores normal behaviour. - `systemctl mask --runtime foo.service` writes the link under `/run/systemd/system` instead, so the mask lasts until reboot. Good for an incident where you want a guaranteed rollback. - `systemctl mask --now foo.service` masks *and* stops a currently running instance. Without `--now`, masking does not touch the running process at all — it prevents future activation, and the existing process keeps serving until you stop it. ## When masking is the right tool The canonical cases are: a socket-activated or timer-activated service you do not want on this host; a distribution-shipped daemon that some other package keeps dragging back in; a service you must guarantee cannot start during a maintenance window even if automation asks for it. It is also the standard way to switch off something like a distribution's default resolver or firewall front-end when you are running a replacement. Masking is a strong statement, and it is invisible unless you look for it. `systemctl list-unit-files --state=masked` enumerates them, and it is worth checking during a "why will this not start" investigation — a masked unit is a very quiet failure mode for the next engineer who inherits the box. ## Putting the original symptom together A unit that survives stop-and-disable across a reboot is being activated by something you have not looked at yet. Find the activator first: is there an enabled `.socket` or `.timer` with the same name, or another unit that depends on it? If suppressing that activator is possible, that is the cleaner fix, because it leaves the dependency graph honest. If it is not — the activator is owned by a package, or the dependency is one you want to keep for other reasons — masking is the tool that ends the argument.
- What happens to a unit that Requires= a service you have just masked?It fails to start. Masking makes the unit unloadable, and a hard dependency on an unloadable unit is an error rather than something systemd skips. That is exactly why masking should follow a check of what depends on the unit — otherwise you suppress one daemon and break an unrelated one that needed it.
- You masked a service that is currently running. Is the process still there?Yes. Masking prevents future activation and does nothing to a process that is already running, so the service keeps serving until you stop it. `systemctl mask --now` does both in one step, and it is the form you want during an incident where you need the thing gone and guaranteed not to come back.
- How would you find masked units on a host you have just inherited?`systemctl list-unit-files --state=masked` lists them. It is worth running early in any "this service will not start" investigation, because a mask left behind by a previous engineer produces a confusing failure that mentions no configuration error at all — just that the unit is masked.
saying these in an interview costs you the question
- Thinks disable prevents a unit from ever starting
- Believes masking stops the running process
- Says masked units are simply skipped by dependencies
- Confuses the mask symlink with an empty override file
- Masks a unit without checking what depends on it