systemctl
Control systemd services, and what starts at boot
systemctl is how you talk to systemd, which has been Debian's init system since jessie. It
starts and stops services, decides what comes back after a reboot, and reports why something
didn't. Almost every "is it running?" question on a Debian box is answered here.
Enabled and active are different things
Every unit has two independent states, and conflating them is where most systemd confusion starts:
- active: is it running right now?
- enabled: will it start at the next boot?
Neither implies the other. A service you started by hand is active but not enabled, and vanishes on reboot. A service you enabled but never started is enabled but not active, and does nothing until you reboot. Stopping a service does not disable it, which is why a "fixed" server so often breaks again the next morning.
systemctl is-active deploy-agent # active | inactive | failed
systemctl is-enabled deploy-agent # enabled | disabled | static | masked
--now bridges the two: enable --now enables and starts, disable --now disables and
stops. When you mean both, say both.
Reading a status block
systemctl status is the first thing to run and the densest thing to read:
● deploy-agent.service - Deploy agent
Loaded: loaded (/etc/systemd/system/deploy-agent.service; disabled; preset: enabled)
Active: active (running) since Mon 2026-08-17 10:32:36 UTC; 9ms ago
Main PID: 1329 (deploy-agent)
CGroup: /system.slice/deploy-agent.service
The leading glyph is a quick health check: ● for active or failed, ○ for stopped. Loaded:
gives the unit file's path and whether it is enabled, so that one line answers both state
questions at once. Active: gives the current state and how long it has held it. Below that come
the main PID, resource use, the process tree, and the last few journal lines for the unit;
journalctl -u is how you read the rest of them.
Because the timestamps, PIDs and memory figures differ on every machine and every run, the
examples on this page show the parts that don't, and use is-active, is-enabled and
systemctl show where an exact answer matters. Those are also the forms to use in a script:
they print one word and set a meaningful exit code.
systemd caches unit files
systemd parses a unit file once and works from its own copy. Editing the file on disk changes nothing until you tell systemd to re-read it:
sudo systemctl daemon-reload
Forget it and you get the most confusing symptom in systemd: a unit file that says one thing
while the running service does another. systemctl status warns about it, and the warning
is worth reading rather than scrolling past. A unit that isn't loaded yet is read fresh when it is
first used, which is why the mistake sometimes appears to work.
daemon-reload re-reads unit files. It does not restart anything, so a running service keeps its
old settings until you restart it too.
Where unit files live
Three locations, in increasing order of authority:
/usr/lib/systemd/system/: units shipped by Debian packages. Don't edit these; an upgrade overwrites them./etc/systemd/system/: units you write, and overrides. Wins over the package copy./etc/systemd/system/<unit>.d/*.conf: drop-ins, which change individual settings while leaving the rest of the package's unit intact. Usually what you want.
systemctl cat shows the file and every drop-in applying to it, in the order systemd reads them,
which beats guessing which of the three is winning.
Managing services with systemd covers what Debian's packaging does
with these directories, and why a drop-in survives an upgrade that an edited package unit does
not.
Root, and reading versus writing
Querying state needs no privileges: status, is-active, list-units and cat all work as any
user. Changing it needs root, so start, stop, enable, mask and daemon-reload want
sudo. The examples below show sudo where it is genuinely required.
Sample files used on this page
Every example below was run against these files. Recreate them to follow along.
/etc/systemd/system/deploy-agent.service a unit that exists only for this page, standing in for whatever service you are working on. Type=notify means the agent tells systemd when it is ready, so systemctl start returns once it genuinely is - with the default Type=simple, start returns as soon as systemd has forked the process, which is why a command run straight afterwards can find a service that isn't up yet
[Unit]
Description=Deploy agent
After=network.target
[Service]
Type=notify
ExecStart=/usr/local/bin/deploy-agent
Restart=on-failure
[Install]
WantedBy=multi-user.target
/etc/systemd/system/backup-sync.service fails on purpose, so the page can show what a failed unit looks like
[Unit]
Description=Nightly backup sync
[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup-sync
Is it running?
Every one of these is safe to run as any user, and none of them change anything.
Check whether a service is running
systemctl is-active cron
Prints one word - active, inactive or failed - and exits 0 only when the service is active. The form to use in a script.
Show output
active
Check whether a service starts at boot
systemctl is-enabled cron
Starting at boot is a separate question from running now, and the answers are independent: a service can be enabled and stopped, or running and disabled.
Show output
enabled
See a service's unit file and enabled state in one line
systemctl status cron --no-pager | head -2
The Loaded: line names the unit file systemd is using and whether it is enabled, which answers 'where is this configured?' and 'will it come back after a reboot?' at once.
Show output
● cron.service - Regular background program processing daemon
Loaded: loaded (/usr/lib/systemd/system/cron.service; enabled; preset: enabled)
Read a service's full status block
systemctl status cron --no-pager | head -9
The command everyone runs first. head -9 stops before the control-group listing, which on this machine names the container it ran in; on yours it names the slice.
Show output
Your output will differ: the date, uptime, invocation id, PID, task count and memory figures are all specific to your machine and the moment you run it
● cron.service - Regular background program processing daemon
Loaded: loaded (/usr/lib/systemd/system/cron.service; enabled; preset: enabled)
Active: active (running) since Mon 2026-08-17 11:01:48 UTC; 261ms ago
Invocation: 59fc699d36de401395207715ea765ce8
Docs: man:cron(8)
Main PID: 87 (cron)
Tasks: 1 (limit: 4699)
Memory: 288K (peak: 1.7M)
CPU: 4ms
See the state of a stopped service
systemctl status deploy-agent --no-pager | head -3
○ rather than ● marks a unit that isn't running, and Active: inactive (dead) confirms it stopped cleanly rather than failing.
Show output
○ deploy-agent.service - Deploy agent
Loaded: loaded (/etc/systemd/system/deploy-agent.service; disabled; preset: enabled)
Active: inactive (dead)
Ask for one property exactly
systemctl show cron -p ActiveState --value
show reads systemd's own state, and --value prints the value alone. Unlike status it is stable enough to compare against in a script.
Show output
active
Ask for several properties at once
systemctl show cron -p ActiveState,SubState,UnitFileState
Without --value each line is Property=value. SubState is the fine-grained state (running, exited, dead) behind the coarse ActiveState.
Show output
ActiveState=active
SubState=running
UnitFileState=enabled
Find out where a unit file actually lives
systemctl show cron -p FragmentPath --value
Answers which of the three unit directories won, without guessing from the file system.
Show output
/usr/lib/systemd/system/cron.service
Check whether a service has failed
systemctl is-failed deploy-agent
Exits 0 when the unit is failed, which reads backwards until you think of it as a test rather than a report.
Show output
inactive
Starting and stopping
All of these change system state, so they need root.
Start a service now
sudo systemctl start deploy-agent
Starts it immediately and says nothing on success. This does not make it start at boot - see the next section.
Confirm a service started
sudo systemctl start deploy-agent
systemctl is-active deploy-agent
start is silent, so pair it with is-active when you want confirmation rather than trust.
Show output
active
Stop a service now
sudo systemctl start deploy-agent
sudo systemctl stop deploy-agent
systemctl is-active deploy-agent
Stops it immediately. It will still start at the next boot if it is enabled, which is the single most common systemd surprise.
Show output
inactive
Restart a service
sudo systemctl start deploy-agent
sudo systemctl restart deploy-agent
systemctl is-active deploy-agent
Stops then starts, in one command. On a stopped service restart simply starts it.
Show output
active
Restart a service only if it is already running
sudo systemctl try-restart deploy-agent
systemctl is-active deploy-agent
try-restart does nothing to a stopped service, where restart would start it. What a package's post-upgrade script wants: refresh whatever is running, start nothing that isn't.
Show output
inactive
Reload a service's configuration without dropping connections
sudo systemctl reload ssh
Asks the service to re-read its own config while staying up. Only works where the unit defines ExecReload, which long-running network daemons usually do.
Find out that a service can't be reloaded
sudo systemctl reload cron
echo "exit=$?"
A unit with no ExecReload refuses rather than silently restarting, and exit 3 distinguishes that from a service that failed to reload.
Show output
Failed to reload cron.service: Job type reload is not applicable for unit cron.service.
exit=3
Reload if possible, restart if not
sudo systemctl reload-or-restart deploy-agent
systemctl is-active deploy-agent
Picks whichever the unit supports. Useful in a script that handles services it doesn't know the details of.
Show output
active
Send a signal to a service
sudo systemctl start deploy-agent
sudo systemctl kill -s SIGHUP deploy-agent
systemctl is-active deploy-agent
kill signals the service's processes without systemd treating it as a stop request. -s picks the signal; the default is SIGTERM.
Show output
active
Running at boot
enable and disable write and remove symlinks under /etc/systemd/system; neither touches whether the service is running now.
Make a service start at boot
sudo systemctl enable deploy-agent
Creates the symlink that the unit's WantedBy= target looks for. It does not start the service now.
Show output
Created symlink '/etc/systemd/system/multi-user.target.wants/deploy-agent.service' → '/etc/systemd/system/deploy-agent.service'.
Enable a service without starting it, and prove it
sudo systemctl enable deploy-agent 2>/dev/null
systemctl is-enabled deploy-agent
systemctl is-active deploy-agent
The pairing that shows the two states are independent: enabled for next boot, not running now.
Show output
enabled
inactive
Enable and start in one command
sudo systemctl enable --now deploy-agent 2>/dev/null
systemctl is-enabled deploy-agent
systemctl is-active deploy-agent
--now is what most people mean by 'turn this service on': running immediately and again after a reboot.
Show output
enabled
active
Stop a service from starting at boot
sudo systemctl enable deploy-agent 2>/dev/null
sudo systemctl disable deploy-agent
Removes the symlink enable created. A service that is currently running keeps running until you stop it or reboot.
Show output
Removed '/etc/systemd/system/multi-user.target.wants/deploy-agent.service'.
Disable and stop in one command
sudo systemctl enable --now deploy-agent >/dev/null 2>&1
sudo systemctl disable --now deploy-agent
systemctl is-active deploy-agent
The mirror of enable --now, and what you want when retiring a service rather than pausing it.
Show output
Removed '/etc/systemd/system/multi-user.target.wants/deploy-agent.service'.
inactive
See that stopping a service leaves it enabled
sudo systemctl enable --now deploy-agent >/dev/null 2>&1
sudo systemctl stop deploy-agent
systemctl is-active deploy-agent
systemctl is-enabled deploy-agent
The failure mode behind 'we fixed it yesterday and it came back': stopping is not disabling, and a reboot undoes it.
Show output
inactive
enabled
List everything that starts at boot
systemctl list-unit-files --type=service --state=enabled --no-pager --no-legend
The inventory to review on a server you have inherited. --no-legend drops the trailing explanation, leaving one line per unit.
Show output
cron.service enabled enabled
e2scrub_reap.service enabled enabled
getty@.service enabled enabled
ssh.service enabled enabled
sshd-keygen.service enabled enabled
systemd-pstore.service enabled enabled
Prevent a service from being started at all
sudo systemctl mask apt-daily.service
systemctl is-enabled apt-daily.service
Masking points the unit at /dev/null, so nothing can start it - not start, not another unit depending on it. Stronger than disable, and the right tool when something keeps starting a service you don't want.
Show output
Created symlink '/etc/systemd/system/apt-daily.service' → '/dev/null'.
Masking 'apt-daily.service', but its triggering units are still active:
apt-daily.timer
masked
See a masked service refuse to start
sudo systemctl mask apt-daily.service >/dev/null 2>&1
sudo systemctl start apt-daily.service
echo "exit=$?"
The refusal is explicit rather than a silent no-op, which is what makes masking safe to rely on.
Show output
Failed to start apt-daily.service: Unit apt-daily.service is masked.
exit=1
Undo a mask
sudo systemctl mask apt-daily.service >/dev/null 2>&1
sudo systemctl unmask apt-daily.service
Removes the symlink to /dev/null and restores whatever the unit's enabled state was before.
Show output
Removed '/etc/systemd/system/apt-daily.service'.
When a service won't start
A failed unit stays marked failed until it starts cleanly or you clear it, which is what makes failures visible hours later.
Watch a service fail to start
sudo systemctl start backup-sync
echo "exit=$?"
start reports the failure and exits non-zero, pointing at the two commands that explain it.
Show output
Job for backup-sync.service failed because the control process exited with error code.
See "systemctl status backup-sync.service" and "journalctl -xeu backup-sync.service" for details.
exit=1
Confirm a service is in the failed state
sudo systemctl start backup-sync 2>/dev/null
systemctl is-failed backup-sync
failed means it tried and didn't make it, as distinct from inactive, which means nobody asked it to run.
Show output
failed
List everything that has failed
sudo systemctl start backup-sync 2>/dev/null
systemctl --failed --no-pager --no-legend
The first command to run on a machine that is misbehaving for no obvious reason.
Show output
● backup-sync.service loaded failed failed Nightly backup sync
See the exit code a service died with
sudo systemctl start backup-sync 2>/dev/null
systemctl show backup-sync -p Result,ExecMainStatus
Result says how systemd classified the failure and ExecMainStatus is the process's own exit code - the number the program chose, which its documentation will explain.
Show output
Result=exit-code
ExecMainStatus=1
Clear a failed state once it is fixed
sudo systemctl start backup-sync 2>/dev/null
sudo systemctl reset-failed backup-sync
systemctl is-failed backup-sync
Forgets the failure so the unit stops showing in --failed. It does not start anything or fix anything, and a service that fails again comes straight back.
Show output
inactive
Clear every failed unit at once
sudo systemctl start backup-sync 2>/dev/null
sudo systemctl reset-failed
systemctl --failed --no-pager --no-legend
echo "(nothing above)"
With no unit named, reset-failed clears them all. Worth doing deliberately rather than habitually: it erases the evidence that something went wrong.
Show output
(nothing above)
Check a unit file for syntax problems
systemd-analyze verify /etc/systemd/system/deploy-agent.service
Parses the unit and reports anything systemd would complain about, before you find out by restarting a service at three in the morning. Silence means it is valid.
Unit files and changing them
systemd works from its own parsed copy of a unit file, so editing one on disk does not change what is running.
Read a unit file, wherever it lives
systemctl cat deploy-agent
Prints the unit file with its path as a comment on the first line, plus every drop-in applying to it, in the order systemd reads them. Beats guessing which of the three unit directories is winning.
Show output
# /etc/systemd/system/deploy-agent.service
[Unit]
Description=Deploy agent
After=network.target
[Service]
Type=notify
ExecStart=/usr/local/bin/deploy-agent
Restart=on-failure
[Install]
WantedBy=multi-user.target
See that an edited unit file has not taken effect yet
sudo systemctl start deploy-agent
sudo sed -i 's/Description=Deploy agent/Description=Deploy agent v2/' /etc/systemd/system/deploy-agent.service
systemctl show deploy-agent -p Description --value
systemd is still using the copy it parsed when the service started. The file on disk and the running configuration now disagree.
Show output
Deploy agent
Load a changed unit file
sudo systemctl start deploy-agent
sudo sed -i 's/Description=Deploy agent/Description=Deploy agent v2/' /etc/systemd/system/deploy-agent.service
sudo systemctl daemon-reload
systemctl show deploy-agent -p Description --value
daemon-reload re-reads every unit file. It changes no running process, so a service also needs a restart before new ExecStart or environment settings apply.
Show output
Deploy agent v2
Notice the warning about a stale unit file
sudo systemctl start deploy-agent
sudo sed -i 's/Description=Deploy agent/Description=Deploy agent v2/' /etc/systemd/system/deploy-agent.service
systemctl status deploy-agent --no-pager 2>&1 | head -1
systemd notices the file changed and says so at the top of status. This line is the answer to 'why is it ignoring my config?' most of the time it is asked.
Show output
Warning: The unit file, source configuration file or drop-ins of deploy-agent.service changed on disk. Run 'systemctl daemon-reload' to reload units.
Override one setting without touching the package's unit file
sudo mkdir -p /etc/systemd/system/deploy-agent.service.d
printf '[Service]\nRestart=always\n' | sudo tee /etc/systemd/system/deploy-agent.service.d/restart.conf >/dev/null
sudo systemctl daemon-reload
systemctl show deploy-agent -p Restart --value
A drop-in changes the settings it names and leaves the rest of the unit alone, so a package upgrade can replace the original without discarding your change. systemctl edit deploy-agent creates the same file interactively. The tee is what lets an unprivileged shell write into /etc.
Show output
always
See which drop-ins are applying
sudo mkdir -p /etc/systemd/system/deploy-agent.service.d
printf '[Service]\nRestart=always\n' | sudo tee /etc/systemd/system/deploy-agent.service.d/restart.conf >/dev/null
sudo systemctl daemon-reload
systemctl cat deploy-agent | tail -4
cat appends each drop-in after the main unit, with its path, so the file that is overriding a setting names itself.
Show output
# /etc/systemd/system/deploy-agent.service.d/restart.conf
[Service]
Restart=always
Check the resulting configuration rather than the files
systemctl show deploy-agent -p Restart,ExecStart --value
show reports what systemd will actually do, after the unit file, every drop-in and every default have been combined. The last word on 'what is this service configured to do?'
Show output
on-failure
{ path=/usr/local/bin/deploy-agent ; argv[]=/usr/local/bin/deploy-agent ; ignore_errors=no ; start_time=[n/a] ; stop_time=[n/a] ; pid=0 ; code=(null) ; status=0/0 }
Reload systemd's own configuration after writing a new unit
printf '[Unit]\nDescription=Report disk usage\n\n[Service]\nType=oneshot\nExecStart=/usr/bin/df -h /\n' | sudo tee /etc/systemd/system/disk-report.service >/dev/null
sudo systemctl daemon-reload
systemctl is-enabled disk-report.service
A brand-new unit file is invisible to systemd until daemon-reload. static means the unit has no [Install] section, so it cannot be enabled - only started, or pulled in by something else.
Show output
static
Looking at the whole system
The commands for orienting yourself on a machine you did not set up.
Check whether the system booted cleanly
systemctl is-system-running
running means everything systemd was asked to start did start. degraded means at least one unit failed and is worth investigating with --failed.
Show output
running
List the services that are running
systemctl list-units --type=service --state=running --no-pager --no-legend 'cron*' 'ssh*'
list-units reports what systemd has loaded, filtered here to services that are up. Drop the patterns to list every running service, and add --all to include units that are loaded but inactive.
Show output
cron.service loaded active running Regular background program processing daemon
ssh.service loaded active running OpenBSD Secure Shell server
Find a service when you only know part of its name
systemctl list-unit-files --type=service --no-pager --no-legend 'ssh*'
A glob pattern narrows the list, which beats scrolling when you can't remember whether the unit is ssh, sshd or openssh-server. On Debian it is ssh.
Show output
ssh.service enabled enabled
sshd-keygen.service enabled enabled
sshd-unix-local@.service alias -
sshd.service alias -
sshd@.service indirect enabled
See what a unit depends on
systemctl list-dependencies deploy-agent --no-pager | head -3
Prints the tree of units that must be active first. ● and ○ mark which of them currently are.
Show output
deploy-agent.service
● ├─system.slice
● └─sysinit.target
See what depends on a unit
systemctl list-dependencies cron --reverse --no-pager | head -2
--reverse answers the question that matters before stopping something: what else breaks if this goes away.
Show output
cron.service
● └─multi-user.target
List the timers that will fire
systemctl list-unit-files --type=timer --state=enabled --no-pager --no-legend
systemd timers are the modern alternative to cron entries, and a package may install one without saying so. This lists them without the volatile next-run times of list-timers.
Show output
apt-daily-upgrade.timer enabled enabled
apt-daily.timer enabled enabled
dpkg-db-backup.timer enabled enabled
e2scrub_all.timer enabled enabled
fstrim.timer enabled enabled
man-db.timer enabled enabled
See which target the system boots into
systemctl get-default
graphical.target pulls in a display manager; multi-user.target is the text-console target a server install usually settles on. sudo systemctl set-default multi-user.target changes it.
Show output
graphical.target
In scripts
systemctl sets exit codes precisely, which is what makes it usable in a script rather than something to parse.
Act on whether a service is running
if systemctl is-active --quiet cron; then
echo "cron is up"
fi
--quiet suppresses the printed word and leaves only the exit code, which is what a conditional needs.
Show output
cron is up
Act on whether a service is enabled
if systemctl is-enabled --quiet deploy-agent; then
echo "starts at boot"
else
echo "will not survive a reboot"
fi
The check worth putting in a provisioning script. Whether a service is running now is easy to get right; whether it comes back after a reboot is what people forget.
Show output
will not survive a reboot
Fail a health check when a service is down
systemctl is-active --quiet deploy-agent || echo "deploy-agent is not running"
echo "exit=$?"
The one-liner form for a monitoring check or a cron job: no output while everything is fine.
Show output
deploy-agent is not running
exit=0
Check several services in one loop
for unit in cron ssh deploy-agent; do
printf '%-14s %s\n' "$unit" "$(systemctl is-active "$unit")"
done
is-active prints exactly one word per unit, so it lines up into a report without any parsing.
Show output
cron active
ssh active
deploy-agent inactive
Wait for a service to come up
sudo systemctl start deploy-agent
for _ in $(seq 1 10); do
systemctl is-active --quiet deploy-agent && break
sleep 0.2
done
systemctl is-active deploy-agent
start returns once systemd has finished its part, which is not always the moment the service is ready to serve. A bounded wait beats a fixed sleep.
Show output
active
Restart a service only when its config changed
sudo systemctl start deploy-agent
if ! systemctl show deploy-agent -p Description --value | grep -q v2; then
sudo systemctl restart deploy-agent
echo "restarted"
fi
Comparing systemd's loaded configuration against what you expect avoids the blanket restart that a configuration-management run otherwise performs on every pass.
Show output
restarted