A web box is quiet, SSH still works, and nginx is “not running.” Killing a leftover worker will not help if systemd never started the unit, or if the unit is failed and waiting for you to notice. On systemd hosts you do not manage daemons by PID first — you ask the manager. systemctl is that interface: inspect a unit, start and stop it, persist it across boots, and read the file systemd actually loaded when something looks wrong.

The three answers you need are independent: loaded is not the same as active, and active is not the same as enabled. The sections below walk through the commands you will reach for most often — on a user-owned demo unit you can start and stop safely, plus a few illustrative system-unit names. Deep log filters live in journalctl. Raw signals belong with kill, pkill, and killall. The full crash/load playbook is the Linux Troubleshooting Toolkit.

Warm-up: list units and read status

Your playground is the live systemd instance on the machine in front of you. Start read-only: list what is loaded, then pick one unit and read status. Do not stop sshd, ssh.service, or PID 1 on a shared box.

List loaded units that are currently active:

systemctl list-units --type=service --no-pager

Typical rows look like this (names and counts will differ):

UNIT              LOAD   ACTIVE SUB     DESCRIPTION
cron.service      loaded active running Regular background program processing daemon
ssh.service       loaded active running OpenBSD Secure Shell server

list-units defaults to active units. A stopped or never-started service may be absent. Show inactive ones too, or switch to the on-disk inventory:

systemctl list-units --type=service --all --no-pager
systemctl list-unit-files --type=service --no-pager

list-unit-files answers enablement (enabled, disabled, static, masked) for every installed unit file, whether or not it is running. Filter by type when you only need services. Socket units (.socket) activate a service on first connection. Timer units (.timer) fire on a schedule — they exist on most hosts, but crontab and .timer authoring belong in the upcoming cron and systemd timers guide. Here you only need to recognize the suffix.

Read one unit without changing it. Short names work when they are unambiguous; the full name.service form is clearer in scripts:

systemctl status ssh.service --no-pager

Typical shape (your paths, PIDs, and timestamps will differ):

● ssh.service - OpenBSD Secure Shell server
     Loaded: loaded (/lib/systemd/system/ssh.service; enabled; preset: enabled)
     Active: active (running) since Sun 2026-09-06 09:12:04 IST; 2 days ago
   Main PID: 1234 (sshd)
      Tasks: 1 (limit: 12345)
     Memory: 8.2M
        CPU: 1.104s
     CGroup: /system.slice/ssh.service
             └─1234 sshd: /usr/sbin/sshd -D

Three fields in that dump are the whole mental model:

  • Loaded — systemd parsed a unit file (path shown). not-found means the name is wrong or the package is missing.
  • Active — runtime: active (running), inactive (dead), failed, and a few quieter states such as active (exited).
  • Enabled — the ; enabled / ; disabled bit on the Loaded line: will this unit start at boot?

A unit can be running but disabled (you started it by hand; a reboot drops it). It can be enabled but inactive (it will come back at boot; it is stopped now). status also prints a short journal tail. When you need filters, boots, or follow mode, switch to journalctl — this post stops at that tail.

Note: Unprivileged users often see user units with systemctl --user. System units typically need sudo systemctl …. Least-privilege sudoers rules for specific systemctl verbs already live in sudo and sudoers. If status looks empty or unauthorized, retry with the right bus (--user vs default system) before assuming the unit does not exist.

Start, stop, restart, reload

Practice lifecycle on a unit you own. A user service that sleeps does nothing useful except give you a safe start/stop target — unlike nginx or sshd on a box you share.

Create the demo, reload the user manager, and start it:

mkdir -p ~/.config/systemd/user
cat > ~/.config/systemd/user/gm-demo.service <<'EOF'
[Unit]
Description=GeekMonks systemctl demo (safe to start/stop)
[Service]
Type=simple
ExecStart=/bin/sleep 3600
[Install]
WantedBy=default.target
EOF
systemctl --user daemon-reload
systemctl --user start gm-demo.service
systemctl --user status gm-demo.service --no-pager

You want Active: active (running). Stop, start again, then restart (stop + start in one transaction):

systemctl --user stop gm-demo.service
systemctl --user start gm-demo.service
systemctl --user restart gm-demo.service
systemctl --user is-active gm-demo.service

is-active prints active or inactive (and exits non-zero when not active) — handy in scripts. restart is the usual “make it pick up a new binary” hammer.

Reload is different: it asks the unit to reread config without a full stop. That only works when the unit defines ExecReload=. This sleep demo has none, so reload should fail:

systemctl --user reload gm-demo.service

Typical result:

Failed to reload gm-demo.service: Job type reload is not applicable for unit gm-demo.service.

That failure is information, not a broken manager. Use reload for daemons that document it (many reverse proxies). Use restart when the process must actually exit and come back. Prefer these verbs over a raw kill on the Main PID — signals race the supervisor; see kill, pkill, and killall.

Illustrative system unit names (do not run stop/restart on production SSH or on a host whose web server you need):

# illustrative — inspect only unless you mean to change the service
systemctl status nginx --no-pager
# systemctl reload nginx
# systemctl restart nginx

Note: Do not systemctl stop ssh/sshd, and never target PID 1 on a machine you cannot get back into. Practice on gm-demo or a throwaway VM. nginx in examples is a named stand-in — treat those commands as copy-paste only when you intend the outage.

Enable vs start (boot persistence)

start is this boot. enable is the next boot (and every boot after, until you disable). Mixing them up is why a “fixed” service dies again after a reboot.

Check, then persist the demo for your user session:

systemctl --user is-enabled gm-demo.service
systemctl --user enable gm-demo.service
systemctl --user is-enabled gm-demo.service

Typical sequence:

disabled
Created symlink .../default.target.wants/gm-demo.service → .../gm-demo.service.
enabled

enable writes a symlink under a .wants/ directory (default.target for user units; often multi-user.target for system services). It does not start the unit now. Combine both with --now:

systemctl --user disable gm-demo.service
systemctl --user enable --now gm-demo.service
systemctl --user is-enabled gm-demo.service
systemctl --user is-active gm-demo.service

enable —now is start plus persist. disable --now is the inverse: stop, and do not start at boot. For a system service the same flags apply with sudo — grant operators only the verbs they need via sudoers rather than a second root account.

static from is-enabled means the unit has no [Install] section. You cannot enable it; another unit or a timer pulls it in. masked means the unit file is linked to /dev/null — start will refuse until you unmask. Masking is a deliberate freeze, not a synonym for disable.

Note: Enabling a --user unit starts it when your systemd user instance starts (login, or linger). It does not replace a system unit under /etc/systemd/system. If enable “succeeds” but a reboot still misses the job, you likely enabled the user copy while a production service lives on the system bus — confirm with systemctl status (no --user) and the Loaded path.

Failed units, cat, and edit

When status says failed, do not guess the unit file. List failures, print what systemd loaded, then change a drop-in and reload the manager.

Break the demo on purpose with a drop-in that replaces ExecStart:

mkdir -p ~/.config/systemd/user/gm-demo.service.d
cat > ~/.config/systemd/user/gm-demo.service.d/break.conf <<'EOF'
[Service]
ExecStart=
ExecStart=/bin/false
EOF
systemctl --user daemon-reload
systemctl --user restart gm-demo.service || true
systemctl --user --failed --no-pager
systemctl --user is-failed gm-demo.service

Typical --failed row:

UNIT            LOAD   ACTIVE SUB    DESCRIPTION
gm-demo.service loaded failed failed GeekMonks systemctl demo (safe to start/stop)

systemctl cat prints the unit as merged — base file plus drop-ins — which is what the manager runs:

systemctl --user cat gm-demo.service

You should see the break.conf fragment after the main file. systemctl edit opens $EDITOR on a new drop-in and runs daemon-reload for you; writing files by hand (as above) always needs an explicit reload:

systemctl --user daemon-reload

Fix the demo: remove the breaking drop-in, reload, clear the failed flag, and start again.

rm -f ~/.config/systemd/user/gm-demo.service.d/break.conf
systemctl --user daemon-reload
systemctl --user reset-failed gm-demo.service
systemctl --user start gm-demo.service
systemctl --user status gm-demo.service --no-pager

reset-failed forgets the failed state so list-units --failed goes quiet. It does not fix the bug — you still have to change the unit and start it. After status shows the short log tail, go to journalctl for -u, -p, --since, and -b.

Note: When a drop-in overrides ExecStart=, clear the old command with an empty ExecStart= line first. systemd appends ExecStart entries otherwise, and you get two commands or a confusing merge. daemon-reload picks up unit-file edits; it is not a substitute for restart of an already-running process.

Leave the demo enabled or tear it down when you are done practicing:

systemctl --user disable --now gm-demo.service
rm -f ~/.config/systemd/user/gm-demo.service
rm -rf ~/.config/systemd/user/gm-demo.service.d
systemctl --user daemon-reload

Quick reference card

Keep this nearby until the verbs become muscle memory:

GoalCommand
List active servicessystemctl list-units --type=service
Include inactivesystemctl list-units --type=service --all
On-disk enablementsystemctl list-unit-files --type=service
Inspect one unitsystemctl status name.service
Start / stop / restartsystemctl start|stop|restart name
Reload config (if supported)systemctl reload name
Enabled at boot?systemctl is-enabled name
Running now?systemctl is-active name
Enable and startsystemctl enable --now name
Disable and stopsystemctl disable --now name
Failed unitssystemctl --failed
Merged unit filesystemctl cat name
Drop-in editorsystemctl edit name
After unit-file editssystemctl daemon-reload
Clear failed flagsystemctl reset-failed name
User bussystemctl --user …

Practice drills

Use gm-demo (or another unit you created). Do not practice stop on sshd or PID 1. System names such as nginx are for status / is-enabled unless you own the outage.

  1. List service unit files, then status a real system unit (for example ssh.service or cron.service) without changing it. Name whether it is active and whether it is enabled.
  2. Create gm-demo.service as in the start/stop section, daemon-reload, start, and confirm is-active prints active.
  3. stop then start the demo. Try reload and confirm it is not applicable. restart and confirm it is running again.
  4. disable the demo, confirm is-enabled is disabled while you start it by hand (active but not enabled). Then enable --now and confirm both enabled and active.
  5. Add a breaking ExecStart=/bin/false drop-in, daemon-reload, restart, list --failed, cat the merged unit, remove the drop-in, reset-failed, and start until status is healthy.

When you are ready to compare, here are solid answers — not the only ones, but clear and portable:

systemctl list-unit-files --type=service --no-pager
systemctl status ssh.service --no-pager
systemctl is-enabled ssh.service
systemctl is-active ssh.service

mkdir -p ~/.config/systemd/user
# write gm-demo.service as in the start/stop section, then:
systemctl --user daemon-reload
systemctl --user start gm-demo.service
systemctl --user is-active gm-demo.service

systemctl --user stop gm-demo.service
systemctl --user start gm-demo.service
systemctl --user reload gm-demo.service   # expect: job type reload is not applicable
systemctl --user restart gm-demo.service
systemctl --user is-active gm-demo.service

systemctl --user disable gm-demo.service
systemctl --user start gm-demo.service
systemctl --user is-enabled gm-demo.service   # disabled
systemctl --user is-active gm-demo.service    # active
systemctl --user enable --now gm-demo.service
systemctl --user is-enabled gm-demo.service
systemctl --user is-active gm-demo.service

mkdir -p ~/.config/systemd/user/gm-demo.service.d
printf '%s\n' '[Service]' 'ExecStart=' 'ExecStart=/bin/false' > ~/.config/systemd/user/gm-demo.service.d/break.conf
systemctl --user daemon-reload
systemctl --user restart gm-demo.service || true
systemctl --user --failed --no-pager
systemctl --user cat gm-demo.service
rm -f ~/.config/systemd/user/gm-demo.service.d/break.conf
systemctl --user daemon-reload
systemctl --user reset-failed gm-demo.service
systemctl --user start gm-demo.service
systemctl --user status gm-demo.service --no-pager

If you can work through those five comfortably, you already cover most real systemctl work: read loaded/active/enabled without guessing, change runtime with start/stop/restart/reload, persist with enable, and debug failures with --failed, cat, drop-ins, and daemon-reload. When the log tail on status is not enough, query journalctl. When the process is not a unit, signal it with kill. When the box is on fire, use the troubleshooting toolkit order — this post is the manager, not the whole playbook.