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-foundmeans the name is wrong or the package is missing. - Active — runtime:
active (running),inactive (dead),failed, and a few quieter states such asactive (exited). - Enabled — the
; enabled/; disabledbit 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:
| Goal | Command |
|---|---|
| List active services | systemctl list-units --type=service |
| Include inactive | systemctl list-units --type=service --all |
| On-disk enablement | systemctl list-unit-files --type=service |
| Inspect one unit | systemctl status name.service |
| Start / stop / restart | systemctl 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 start | systemctl enable --now name |
| Disable and stop | systemctl disable --now name |
| Failed units | systemctl --failed |
| Merged unit file | systemctl cat name |
| Drop-in editor | systemctl edit name |
| After unit-file edits | systemctl daemon-reload |
| Clear failed flag | systemctl reset-failed name |
| User bus | systemctl --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.
- List service unit files, then
statusa real system unit (for examplessh.serviceorcron.service) without changing it. Name whether it is active and whether it is enabled. - Create
gm-demo.serviceas in the start/stop section,daemon-reload,start, and confirmis-activeprintsactive. stopthenstartthe demo. Tryreloadand confirm it is not applicable.restartand confirm it is running again.disablethe demo, confirmis-enabledisdisabledwhile youstartit by hand (activebut not enabled). Thenenable --nowand confirm bothenabledandactive.- Add a breaking
ExecStart=/bin/falsedrop-in,daemon-reload,restart, list--failed,catthe merged unit, remove the drop-in,reset-failed, andstartuntilstatusis 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.