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 nginxprintsactiveorinactive(running now?), andsystemctl is-enabled nginxprintsenabledordisabled(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 astatusfrom 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), ormasked(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;failedfor 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 totop,ps, andkill.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:andCPU:— 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 intopto 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 statusonly prints the last ~10 log lines. When that's not enough,journalctl -u <service>gives you the entire history of that service, andjournalctl -u <service> -ffollows it live — because systemd captures everything a service writes to stdout/stderr into the journal, tagged by unit. No more spelunking through/var/logguessing 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/nullso it cannot be started, by hand or by anything else.unmaskreverses it. Use it whendisableisn't enough because some other service keeps pulling a dependency back up. (maskisdisablewith 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 askey=value(the machine-readable firehose;-pto 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(--fullto edit the whole unit); auto-runsdaemon-reloadon save.daemon-reload— re-read all unit files after you've changed one. Not the same asreload.reset-failed— clear thefailedflag on a unit so it's eligible to start again (and tidies your--failedlist).kill— send a signal to the service's processes (--signal=HUP), respecting the cgroup.
Whole-machine verbs:
reboot/poweroff/halt/suspend— yes,systemctl rebootis the modern way to reboot.get-default/set-default— the target the machine boots into (graphical.targetfor a desktop,multi-user.targetfor a headless server — the systemd descendants of the old "runlevels").isolate— switch the running system to a target now (e.g. drop torescue.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 withenable/disableto also start/stop immediately.--no-pager— don't pipe throughless(essential in scripts and cron).-l/--full— don't truncate long lines instatus/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— filterlist-unitsto 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-failureis the auto-resurrection policy (always,on-failure,no).KillMode=controls how the cgroup gets torn down on stop.[Install]— whatenableactually wires up.WantedBy=multi-user.targetmeans "when I'm enabled, start me as part of normal multi-user boot."enableliterally just creates a symlink to satisfy that line;disableremoves 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. Runsystemctl edit nginxinstead. 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-runsdaemon-reloadwhen you save.systemctl catthen shows you the vendor unit and your override stacked together, so you always see the effective config.
Gotchas
enabledoesn't start,startdoesn't persist. The headline trap. Useenable --nowto do both.daemon-reload≠reload. 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). Skipdaemon-reloadand your unit edit is silently ignored.active (exited)is not an error. One-shot units are meant to finish. Don't "fix" them.- A
failedunit stays failed until reset. Fix the cause, thenrestart— orreset-failedto clear the flag without starting it. Until you do, it clutterssystemctl --failed. - The two name halves:
cronvscron.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 pokefoo.timerwhen you meantfoo.service. --useris a whole separate world. Plainsystemctlcontrols system services (run as root, started at boot).systemctl --usercontrols services tied to your login session. Forget--userand you'll wonder why your personal unit "doesn't exist" — you're asking the wrong systemd.reboot/stopmay need privileges. Read verbs are open to everyone; anything that changes state usually wantssudo. 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 servicetop— find theMain PIDfromstatusand watch it liveps— a one-shot snapshot of a service's processeskill— 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
enableit?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.