Managing Services
Topic 42

Managing Services

Foundations

Managing services on a modern Debian or Ubuntu system means talking to systemctl, the single front-end to systemd's service manager. One command starts, stops, restarts, enables, disables, masks, and inspects every unit on the box. This replaced a scatter of mechanisms from the SysV era: the /etc/init.d/<name> start shell scripts, the service wrapper, and per-distribution tools like update-rc.d or chkconfig for boot-time enablement. Red Hat systems use the same systemctl today; only the very old /etc/rc.d/init.d layout differs historically.

The operational consequence is one distinction you must internalize: runtime state and boot-time state are separate. start and stop change what is running right now; enable and disable change whether a unit comes up at the next boot. Conflating the two is the single most common source of "it worked until we rebooted" incidents — a service started by hand that nobody enabled, gone the moment the machine cycles.

Runtime vs Boot State

start, stop, restart, and reload act on the running system immediately. They do not touch what happens at boot. enable and disable do the opposite: they create or remove a symlink under a .wants directory — typically /etc/systemd/system/multi-user.target.wants/ — that tells systemd to pull the unit in when its target activates. Enabling does not start the unit, and starting does not enable it. That is the classic trap.

The combined forms exist precisely to close that gap. systemctl enable --now nginx sets boot state and starts the service in one step; disable --now removes the symlink and stops the running instance. Use them by default so the two states never drift apart.

# Start now AND at every boot
systemctl enable --now nginx

# Stop now AND prevent it from starting at boot
systemctl disable --now nginx

# The trap: this starts it, but it will NOT come back after reboot
systemctl start nginx

Inspecting State

systemctl status nginx is the human-readable snapshot: a colored dot, the load and active states, the main PID, and the last ten journal lines. The dot encodes the answer at a glance — green for active (running), white for inactive, red for failed, and a spinner for activating. Underneath sit three orthogonal fields: LoadState (whether the unit file parsed), ActiveState (active, inactive, failed, activating), and SubState (the type-specific detail, like running, exited, or dead).

For scripts, never parse the status text. Use the purpose-built query commands, which return clean exit codes: systemctl is-active nginx, is-enabled nginx, and is-failed nginx. To survey the whole system, systemctl list-units --type=service shows what is loaded and active, while list-unit-files --type=service shows the enablement state of every installed unit, running or not.

# Snapshot, including the colored dot and recent journal lines
systemctl status nginx

# Machine-readable: exits 0 if active/enabled, non-zero otherwise
systemctl is-active nginx
systemctl is-enabled nginx

# Enablement of every installed service unit
systemctl list-unit-files --type=service

Reload vs Restart

restart stops the unit and starts it again — the process is replaced, and every open connection is dropped. reload tells the running process to re-read its own configuration without exiting, which keeps connections alive, but only works if the unit declares an ExecReload line (nginx and most well-behaved daemons do). When you are unsure, reload-or-restart reloads if possible and falls back to a restart otherwise, and try-restart restarts only if the unit is already running.

There is a second, easily confused operation: systemctl daemon-reload. This has nothing to do with a service's own config — it tells systemd itself to re-read the unit files after you have edited or added one. Forgetting it is a top mistake: you change a unit, run restart, and systemd quietly acts on the stale copy it already had in memory.

# Re-read the app's config, keep connections (needs ExecReload)
systemctl reload nginx

# Full stop/start; drops live connections
systemctl restart nginx

# After editing a UNIT FILE, tell systemd to re-read it
systemctl daemon-reload
systemctl restart nginx

Masking and Overrides

disable only removes the boot-time symlink — a disabled unit can still be started by hand or pulled in as a dependency of something else. mask is the hard stop: it symlinks the unit to /dev/null, so it cannot be started by any means — not manually, not as a dependency, not by a socket activation. unmask reverts it. Reach for masking when you need a guarantee, such as silencing a conflicting service before installing its replacement.

For changing what a unit does, never edit the packaged file under /lib/systemd/system/ (the path is /usr/lib/systemd/system/ on Red Hat) — a package upgrade will overwrite it. Use systemctl edit nginx, which writes a drop-in snippet under /etc/systemd/system/nginx.service.d/override.conf. The drop-in is merged on top of the shipped unit and survives upgrades cleanly.

# Fully block a unit; cannot start even as a dependency
systemctl mask apache2
systemctl unmask apache2

# Upgrade-safe override: writes a drop-in under /etc/systemd/system/
systemctl edit nginx

Dependency-Aware Operations

Services rarely stand alone. systemctl list-dependencies nginx draws the tree of units a service pulls in, and list-dependencies --reverse nginx shows what depends on it — essential before you stop something that other units require. Stopping a unit does not automatically stop its dependents, but it can leave them broken; reading the reverse tree first tells you the real blast radius.

systemctl isolate is the heavy hammer: it switches the whole system to a target, stopping everything not wanted by it. systemctl isolate rescue.target drops a running server to single-user rescue mode — useful in recovery, dangerous to run absent-mindedly on a production box over SSH, since it will tear down networking and your own session.

disable vs mask

disable — removes the unit's .wants symlink so it will not auto-start at boot. It says nothing about right now: a disabled unit still starts when you run systemctl start on it, and it can still be pulled in as a dependency of another unit. Reach for it when you simply do not want a service coming up automatically.

mask — symlinks the unit to /dev/null, making it unstartable by any path: not manually, not as a dependency, not through socket or timer activation. start returns an error instead of running it. Use it when you need a hard guarantee — most often to silence a conflicting service, masking apache2 before bringing up nginx on port 80, so nothing can start it behind your back. Run unmask before you expect it to run again.

Common Mistakes
  • Running start without enable. The service runs fine until the next reboot, then vanishes, because nothing created the boot-time symlink.
  • Editing a unit file and skipping daemon-reload. systemd keeps acting on the stale in-memory copy, so your restart silently ignores the change.
  • Expecting disable to stop a running service. It only removes the boot symlink; the live process keeps running until you add --now or call stop.
  • Treating mask and disable as equivalent. A masked unit cannot start at all — not manually, not as a dependency — which surprises people debugging why a service "won't come up."
  • Using restart where reload would do. A reloadable daemon gets every live connection dropped for no reason during a routine config change.
  • Editing the packaged unit under /lib/systemd/system/. The next package upgrade overwrites your changes; systemctl edit drop-ins survive.
  • Reading only status and missing journalctl -u. Status shows the last ten lines; the failure that actually caused the crash scrolled past and is only in the full journal.
Best Practices
  • Set both states at once. Use systemctl enable --now and disable --now so runtime and boot state never drift apart.
  • Run daemon-reload after every unit-file edit. Do it before the next start or restart, or the change is ignored.
  • Override with systemctl edit. It writes a drop-in under /etc/systemd/system/<unit>.d/ that outlives package upgrades.
  • Prefer reload or reload-or-restart for config changes. Keep live connections when the service supports it; restart only when it does not.
  • Query state with exit codes. Use is-active and is-enabled in scripts instead of grepping the status text, which can change format.
  • Use mask for hard guarantees. Mask a conflicting service before installing its replacement so nothing can start it accidentally.
  • Read the full history with journalctl -u <unit> --since. Diagnose failures from the complete log, not the status snapshot.
Comparable toolsSysV init (service/chkconfig)Windows Services (sc.exe)launchd (launchctl)

Knowledge Check

You run systemctl start nginx and it works. After a reboot, nginx is not running. Why?

  • start only changes runtime state; without enable there is no boot-time .wants symlink
  • The reboot corrupted the on-disk unit file, so systemd silently refused to load it on the way back up
  • start implies enable, so the service must have been explicitly masked again afterward
  • systemd automatically disables any service that was started manually rather than at boot time

You edit a service's unit file, run systemctl restart, and the change has no effect. What is the most likely cause?

  • You skipped systemctl daemon-reload, so systemd restarted from the stale in-memory unit
  • You needed to run reload instead of restart in order to pick up a unit-file change
  • The edited unit file must live under /etc, not under /lib, for the change to take effect at all
  • restart never re-reads any unit configuration; only a full machine reboot does

What does mask give you that disable does not?

  • It blocks the unit from starting by any means — manually, as a dependency, or via socket activation
  • It stops the currently running process immediately on its own, whereas plain disable never does
  • It deletes the unit file from disk entirely, effectively uninstalling that service from the whole system
  • It blocks the unit from ever being overridden later with a systemctl edit drop-in

A web service supports ExecReload. During a routine config change, why prefer reload over restart?

  • reload re-reads the app's config without dropping live connections; restart tears the process down
  • reload also re-reads the unit file itself, so using it lets you safely skip daemon-reload
  • restart always requires root privileges, but reload on the same unit can run unprivileged
  • reload additionally enables the unit on disk so that it always returns automatically at the very next boot

You got correct