systemctl

Control systemd services, and what starts at boot

Updated 2026-08-17

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
53 outputs, collapsed by default

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

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