systemctl Command: Tutorial & Examples

One verb for the whole machine — start it, stop it, run it at boot, and ask why it died.

What It Is

systemctl is the one command you use to control every background program on a modern Linux server. Your database, your web server, SSH, the clock-syncing thing you've never thought about — Linux calls each of these a service, and systemctl is the single remote control for all of them. Start one, stop one, restart one, tell one to come up automatically at boot, or ask a dead one what its last words were — it's all the same handful of verbs, applied to a unit name. Learn five of them and you can run a server.

If you've just been handed a box and told "keep the app running," this is the command that does it. It only acts when you tell it to; reading status changes nothing, so it's safe to poke around. We'll explain every verb, the one distinction that trips up everybody on day one, and — because systemctl is just the friendly face of a system called systemd — a fair bit about how a Linux machine actually boots and stays alive. By the end this is both your first lesson and the page you come back to for the flag you can never remember.

Your First Look

The most useful thing you'll type all week is status on a service. Let's look at the humble cron daemon:

systemctl status cron.service
● cron.service - Regular background program processing daemon
     Loaded: loaded (/usr/lib/systemd/system/cron.service; enabled; preset: enabled)
     Active: active (running) since Sat 2026-05-09 17:40:24 CEST; 3 weeks 4 days ago
 Invocation: d996ab896c644ecb82473f4f3dae9a9c
       Docs: man:cron(8)
   Main PID: 1394 (cron)
      Tasks: 1 (limit: 37810)
     Memory: 2.6M (peak: 6.1M, swap: 224K, swap peak: 788K)
        CPU: 1min 7.922s
     CGroup: /system.slice/cron.service
             └─1394 /usr/sbin/cron -f

Look at how much fell out of one command. The green dot and active (running) say it's alive. Loaded names the unit file that describes it and — crucially — that it's enabled, meaning it'll come back after a reboot. Active gives the uptime of this one service (three and a half weeks). Main PID is the process you'd find in top. Memory and CPU are this service's resource use, accounted for free. And that CGroup tree at the bottom is the genuinely clever part — we'll come back to it, because it's a small marvel.

That's the shape of it. Now the mental model, then every verb.

The One Thing That Bites Everybody: enable ≠ start

Stop here, because this single misunderstanding causes more "but I set it up!" head-scratching than anything else on this page. There are two completely separate questions you can ask about a service, and systemctl answers them with two different verbs:

  • start — run it right now, this second. Takes effect immediately, lasts until the next reboot (or until something stops it).
  • enable — run it automatically at every boot, forever. Takes effect at the next reboot, and does nothing to the currently-stopped service sitting in front of you.

They are orthogonal. A service can be enabled but not running (you enabled it, but haven't booted since). It can be running but not enabled (you started it by hand; next reboot it's gone). The classic day-one faceplant: you install nginx, run systemctl start nginx, see your site, declare victory — and three weeks later the server reboots for a kernel update and your site is gone, because you never enabled it. The fix you'll reach for nine times out of ten does both at once:

systemctl enable --now nginx

--now means "and also start/stop it this instant." enable --now = on at boot and on right now; disable --now = off at boot and off right now. Burn enable --now into muscle memory and you'll never ship that bug.

Note

Two read-only verbs answer the two questions directly: systemctl is-active nginx prints active or inactive (running now?), and systemctl is-enabled nginx prints enabled or disabled (running at boot?). They're built for scripts — they say one word and set an exit code — but they're also the fastest way to settle the "wait, is this thing actually on?" question in your own head.

How I Use It

Here's the actual order of moves in my head when I sit down at a server, because the verbs aren't equally important — status is most of the job.

First, always status. Before I touch anything I run systemctl status <service> and read three lines: the colored dot and Active: (is it up, and for how long?), Loaded: (is it enabled, so it survives a reboot?), and the last log lines that status prints for free (more on those in a second). That one screen usually tells me whether I have a problem at all.

If it's dead, I want to know why before I restart it. A restart often "fixes" things by hiding the evidence — the service comes back, the cause scrolls away, and it dies again at 3am. So I read the tail of the log that status shows me, or pull more with journalctl -u <service>, then restart.

To apply a config change, reload if I can, restart if I must. reload tells the service to re-read its config without dropping connections (nginx and SSH both support this) — no downtime. restart is the sledgehammer: stop, then start, killing every connection in between. Reach for reload first; fall back to restart only when the service can't reload.

The trap that gets everyone once: after editing a unit file, you must daemon-reload. systemd caches every unit file in memory. Edit nginx.service on disk and systemctl restart nginx will cheerfully restart it from the old cached version — your change silently ignored. systemctl daemon-reload tells systemd to re-read the files. (Note the asymmetry: reload re-reads the service's own config; daemon-reload re-reads systemd's description of the service. Different layers, different commands — and the day it confuses you, you'll remember this paragraph.)

So the spine is: status to look, read the why, reload/restart to act, enable --now to make it stick, daemon-reload after editing a unit. Now the reference.

The Status Block, Explained

Every line in that status output, so you never wonder again. We saw cron; here's what each field means on any service:

  • The colored dot. A tiny but real signal: green running, white inactive/stopped, red × failed. You can read a status from across the room.
  • Loaded: — the unit file path (/usr/lib/systemd/system/cron.service), then its install state: enabled (starts at boot), disabled (doesn't), static (can't be enabled — it has no boot hook, something else pulls it in), or masked (forcibly off; more below). preset: is the distro's recommended default for this unit — what a fresh install would have chosen.
  • Active: — the headline. active (running) for a long-lived daemon; active (exited) for a one-shot that did its job and is supposed to be gone (this confuses people — it's not broken, it's done); inactive (dead) for stopped; failed for crashed. The timestamp is this service's own uptime.
  • Invocation: — a unique ID minted for this run of the service. Restart it and the ID changes — handy for pinning journal logs to one specific run.
  • Docs: — the man page or URL the package author pointed at. Free documentation, right there.
  • Main PID: — the process ID of the service's main process. This is your bridge to top, ps, and kill.
  • Tasks: — how many threads and processes this service currently owns, and the cap (limit: 37810) before systemd refuses to let it spawn more — a built-in fork-bomb brake.
  • Memory: and CPU: — this service's resource use, totted up for you. 2.6M (peak: 6.1M, swap: 224K) is current, high-water-mark, and how much got pushed to swap. No need to hunt PIDs in top to answer "how fat is this service?" — systemd already knows, because of the next bit.
  • The CGroup: tree — the marvel. Every process the service spawned, drawn as a tree under a control group. This is why the memory and CPU numbers are exact, and it's the magic worth a section of its own.

The Magic: status Hands You the Whole Family Tree

Here's the thing that made me actually like systemd. On the old SysV init, a "service" was a wish. You ran a start script, it forked a process, that process forked children, the children forked grandchildren — and init lost track of all of them the instant they daemonized. Kill the service and orphans lingered. Ask "how much memory does Apache use?" and you got to go hunting through ps yourself, hoping you caught every worker.

systemd fixed this by leaning on a kernel feature called control groups (cgroups). When systemd starts a service, it drops it into its own cgroup — and the kernel guarantees that every process the service spawns, however many generations deep, stays inside that group. The service can't escape its own box. That's how status can draw you a perfect family tree:

     CGroup: /system.slice/cron.service
             └─1394 /usr/sbin/cron -f

One process here, but for a web server you'd see the master and every worker, neatly nested. And because the kernel is tracking the group, three things come free: accurate accounting (the Memory:/CPU: lines are the sum over the whole group, not a guess), clean shutdown (systemctl stop kills the entire group — no orphans, ever), and resource limits (you can cap the whole group's RAM in the unit file, and the kernel enforces it). The CGroup: line in status is the visible tip of all of that. The first time you realize that boring little tree means systemd literally cannot lose a process, the whole system clicks into place.

Pro Tip

systemctl status only prints the last ~10 log lines. When that's not enough, journalctl -u <service> gives you the entire history of that service, and journalctl -u <service> -f follows it live — because systemd captures everything a service writes to stdout/stderr into the journal, tagged by unit. No more spelunking through /var/log guessing which file a service logs to; the unit name is the filter.

The Verbs, Explained

The complete vocabulary, grouped by what you're trying to do.

Controlling one service, right now:

  • start — run it this instant.
  • stop — halt it this instant (kills the whole cgroup).
  • restart — stop then start; drops connections.
  • reload — re-read config without restarting; zero downtime, if the service supports it.
  • reload-or-restart — reload if it can, else restart. The safe "apply my change" verb.

Controlling boot behaviour:

  • enable — start it automatically at boot (does nothing right now).
  • disable — don't start it at boot (does nothing right now).
  • enable --now / disable --now — both at once. Your default.
  • mask — the nuclear off switch: links the unit to /dev/null so it cannot be started, by hand or by anything else. unmask reverses it. Use it when disable isn't enough because some other service keeps pulling a dependency back up. (mask is disable with attitude — it doesn't just decline to start the thing, it makes starting it impossible.)

Asking questions (all read-only, all safe):

  • status — the human-readable dashboard above.
  • is-active / is-enabled / is-failed — one-word, scriptable answers.
  • list-units — everything systemd currently knows about, with live state.
  • list-unit-files — every unit installed on disk and its enable state (covers things not currently loaded).
  • cat — print the unit file(s), including drop-in overrides, without hunting for paths.
  • show — every internal property as key=value (the machine-readable firehose; -p to pick one).
  • list-dependencies — the tree of what a unit needs and what needs it.

Editing and housekeeping:

  • edit — open a drop-in override in $EDITOR (--full to edit the whole unit); auto-runs daemon-reload on save.
  • daemon-reload — re-read all unit files after you've changed one. Not the same as reload.
  • reset-failed — clear the failed flag on a unit so it's eligible to start again (and tidies your --failed list).
  • kill — send a signal to the service's processes (--signal=HUP), respecting the cgroup.

Whole-machine verbs:

  • reboot / poweroff / halt / suspend — yes, systemctl reboot is the modern way to reboot.
  • get-default / set-default — the target the machine boots into (graphical.target for a desktop, multi-user.target for a headless server — the systemd descendants of the old "runlevels").
  • isolate — switch the running system to a target now (e.g. drop to rescue.target). Powerful and sharp; mostly for recovery.

Reading It by Example

Build instinct fast — real readouts and the verdict each one implies.

Active: active (running) since … ; 3 weeks 4 days ago plus Loaded: … enabled → the happy path. Up, and it'll stay up across reboots. Nothing to do.

Active: active (exited)not a bug. This is a one-shot unit (a mount, a firewall-load, a tmpfiles job) that ran, succeeded, and exited on purpose. systemd keeps it marked active to say "the thing it was supposed to make true is true." Beginners restart these in a panic; don't.

Active: failed (Result: exit-code) … Main PID: 2087 (code=exited, status=1/FAILURE) → it crashed, and the last log lines underneath tell you why. Read those before restarting. Status 203/EXEC specifically means systemd couldn't even execute the binary — wrong path or bad permissions in ExecStart.

Loaded: loaded (…; disabled; …) but Active: active (running) → someone started it by hand and forgot to enable it. Works today, vanishes at the next reboot. Run enable --now (or just enable) to make it permanent — this is a landmine quietly waiting for your next maintenance window.

Loaded: masked → it's been deliberately bolted shut, and start will refuse with Unit is masked. Either that was intentional (you, or the distro, killing a service for good) or someone forgot they did it. unmask to revive.

Active: activating (auto-restart) … Restart=on-failure caught between crashes → the unit has a restart policy and is in a crash loop, dying and respawning. The service looks like it's trying; really it's failing on a timer. journalctl -u <service> shows the same error repeating. Fix the cause; the loop won't fix itself.

Cheat Sheet

The moves you'll actually use, in rough order of frequency:

systemctl status nginx            # the dashboard — start here, always
systemctl restart nginx           # stop + start (drops connections)
systemctl reload nginx            # re-read config, no downtime (if supported)
systemctl enable --now nginx      # on at boot AND on right now ← the one to memorize
systemctl disable --now nginx     # off at boot AND off right now
systemctl daemon-reload           # after you EDIT a unit file (not the same as reload)

Asking questions (all read-only):

systemctl is-active nginx         # running now?     → active / inactive
systemctl is-enabled nginx        # running at boot? → enabled / disabled
systemctl --failed                # everything that has crashed
systemctl list-units --type=service --state=running   # what's alive
systemctl list-unit-files --type=service              # what's installed + enable state
systemctl cat nginx               # print the unit file(s), drop-ins included
systemctl list-dependencies nginx # what it needs / what needs it

Handy flags:

  • --now — pair with enable/disable to also start/stop immediately.
  • --no-pager — don't pipe through less (essential in scripts and cron).
  • -l / --full — don't truncate long lines in status/list-units.
  • --user — operate on your personal services, not the system's (see Gotchas).
  • -H host — run against a remote machine over SSH (systemctl -H web01 status nginx).
  • --state=failed, --type=service — filter list-units to what you care about.

What's Actually Installed: list-unit-files

status looks at one service; list-unit-files shows you the whole inventory on disk and whether each one starts at boot:

systemctl list-unit-files --type=service
UNIT FILE                          STATE      PRESET
accounts-daemon.service            enabled    enabled
alsa-utils.service                 masked     enabled
apt-daily.service                  static     -
console-getty.service              disabled   disabled
...
240 unit files listed.

STATE is the answer to "does this start at boot?" — enabled, disabled, static (no boot hook; pulled in by something else), or masked (forcibly off). PRESET is the distro's recommendation, so a row where STATE differs from PRESET is something you (or a previous admin) deliberately changed — a quiet, useful audit trail of "what's been fiddled with on this box."

Where the Service Is Defined: the unit file

Every service is described by a small .ini-style text file called a unit file. systemctl cat prints it without you needing to know the path:

systemctl cat cron.service
[Unit]
Description=Regular background program processing daemon
Documentation=man:cron(8)
After=remote-fs.target nss-user-lookup.target

[Service]
ExecStart=/usr/sbin/cron -f $EXTRA_OPTS
Restart=on-failure
KillMode=process

[Install]
WantedBy=multi-user.target

This is the entire contract, and it's wonderfully readable:

  • [Unit] — metadata and ordering. After= says "don't start me until these are up" (here: remote filesystems and user lookups). That's how systemd untangles boot order while still starting things in parallel, instead of running scripts one-by-one like the old days.
  • [Service] — the meat. ExecStart= is the command systemd runs. Restart=on-failure is the auto-resurrection policy (always, on-failure, no). KillMode= controls how the cgroup gets torn down on stop.
  • [Install] — what enable actually wires up. WantedBy=multi-user.target means "when I'm enabled, start me as part of normal multi-user boot." enable literally just creates a symlink to satisfy that line; disable removes it. There's no magic — enabling a service is creating a symlink, and once you've seen that, the whole boot system stops feeling like sorcery.

Pro Tip

Never edit the vendor file in /usr/lib/systemd/system/ — a package update will overwrite it and your change vanishes. Run systemctl edit nginx instead. It creates a tiny drop-in override in /etc/systemd/system/nginx.service.d/ that layers on top of the vendor unit, survives upgrades, and auto-runs daemon-reload when you save. systemctl cat then shows you the vendor unit and your override stacked together, so you always see the effective config.

Gotchas

  • enable doesn't start, start doesn't persist. The headline trap. Use enable --now to do both.
  • daemon-reloadreload. After editing a unit file, daemon-reload (re-reads systemd's view). To re-read a service's own config, reload (re-reads e.g. nginx.conf). Skip daemon-reload and your unit edit is silently ignored.
  • active (exited) is not an error. One-shot units are meant to finish. Don't "fix" them.
  • A failed unit stays failed until reset. Fix the cause, then restart — or reset-failed to clear the flag without starting it. Until you do, it clutters systemctl --failed.
  • The two name halves: cron vs cron.service. You can usually drop .service (systemctl status cron). But systemd manages other unit types too — .socket, .timer, .mount, .target — and they can share a stem. When in doubt, spell out the suffix so you don't accidentally poke foo.timer when you meant foo.service.
  • --user is a whole separate world. Plain systemctl controls system services (run as root, started at boot). systemctl --user controls services tied to your login session. Forget --user and you'll wonder why your personal unit "doesn't exist" — you're asking the wrong systemd.
  • reboot/stop may need privileges. Read verbs are open to everyone; anything that changes state usually wants sudo. systemd checks via polkit, so the refusal is sometimes a polite "Interactive authentication required" rather than a flat denial.

History & Philosophy

For thirty years Linux booted with SysV init: a numbered pile of shell scripts in /etc/rc.d, run strictly in order, one after another, each one blocking the next. It was simple to read and miserable to use — boot was slow because nothing ran in parallel, a script that hung could freeze the whole startup, and init had no real idea whether the thing it launched was even still alive. "Is the service up?" was answered by a PID file that lied half the time.

systemd arrived in 2010 (Lennart Poettering at Red Hat) and rethought the lot: describe services declaratively in unit files instead of scripting them imperatively, start everything that can run in parallel by tracking dependencies, and — the master stroke — use the kernel's cgroups to never, ever lose track of a process again. It was bitterly controversial; few pieces of software have started more flame wars, because systemd grew well past "init" into logging, networking, and device management, and a lot of greybeards felt it swallowed too much of the system whole. But it won, comprehensively — Debian, Ubuntu, Fedora, Arch, RHEL, SUSE all ship it as default — and systemctl is the steering wheel they all share.

BTW, the name has a lovely bit of pedantry baked in: that lowercase d is on purpose. In Unix, a d suffix marks a daemon — a program that runs quietly in the background (sshd, crond, httpd). systemd is "the system daemon," so the authors insist it's always systemd, never SystemD or Systemd, even at the start of a sentence. (And where does "daemon" itself come from? Not the demonic kind — it's Maxwell's demon, the imaginary helper from 1860s physics that sorts molecules tirelessly in the background. MIT's early hackers borrowed the image for processes that toil unseen, and the name stuck across all of Unix. Your server is full of tiny, benevolent demons.)

And here's the thread worth pulling on some evening: under all of this, systemctl mostly just asks systemd nicely over a message bus called D-Bus — the same plumbing your desktop uses to know the battery is low. systemctl status is a D-Bus query; systemctl restart is a D-Bus method call. Which means everything you do by hand, a program can do too, through the same interface — which is exactly how a monitoring tool watches your services without scraping text off a terminal. The command line and the API are the same door, and once you see that, you stop thinking of the server as something you visit and start thinking of it as something you can talk to.

See Also

  • systemd — the init system behind it all: units, targets, timers, the journal
  • journalctl — read the logs systemd captured for each service
  • top — find the Main PID from status and watch it live
  • ps — a one-shot snapshot of a service's processes
  • kill — send a signal directly to a service's PID
  • daemon — what a background service actually is
  • cgroup — the kernel feature that lets systemd never lose a process
  • runaway process — when a service forks out of control

Did your service actually come back after that reboot — or did you forget to enable it?

CleverUptime detects every service running on your box and watches each one minute by minute, so a daemon that quietly dies (or never restarts) raises an alert with the plain-language reason — instead of you finding out when a customer does.

Want to see your own server's health right now? One command, no signup, no install.

Check your server →