systemctl is how you start, stop, inspect and configure services on any modern Linux distribution. systemd replaced the old SysV init scripts years ago and is now standard on Debian, Ubuntu, Fedora, RHEL, Rocky, Alma, Arch, openSUSE and most others — so if you are administering a Linux machine today, this is the tool.
Units: the thing systemd actually manages
systemd does not just manage services. It manages units, and a service is only one kind. The suffix tells you which:
| Suffix | What it is |
|---|---|
.service | A daemon or process — the common case |
.socket | A socket that starts its service on first connection |
.timer | A scheduled trigger — systemd’s answer to cron |
.mount | A filesystem mount point |
.target | A group of units, roughly like an old runlevel |
.path | Watches a file or directory and triggers on change |
If you leave the suffix off, systemctl assumes .service. So systemctl status nginx and systemctl status nginx.service are the same command.
The core commands
| Command | What it does |
|---|---|
systemctl status UNIT | Show state, recent logs, process tree |
systemctl start UNIT | Start it now |
systemctl stop UNIT | Stop it now |
systemctl restart UNIT | Stop then start |
systemctl reload UNIT | Run the unit’s ExecReload=, whatever the packager put there. Whether anything re-reads a configuration, and whether connections survive, is the program’s decision and differs per unit |
systemctl reload-or-restart UNIT | Reload if the unit has an ExecReload=, otherwise restart — and nothing in the output tells you which happened. On stock Ubuntu 24.04 it silently restarts rsyslog and chrony |
systemctl enable UNIT | Start automatically at boot |
systemctl disable UNIT | Do not start at boot |
systemctl enable --now UNIT | Enable and start in one step |
systemctl is-active UNIT | Is it running? (script-friendly) |
systemctl is-enabled UNIT | Will it start at boot? (script-friendly) |
systemctl cat UNIT | Print the unit file and any overrides |
systemctl edit UNIT | Create or edit a drop-in override |
systemctl daemon-reload | Re-read unit files after editing them |
Anything that changes state needs root, so prefix with sudo. Read-only commands like status and list-units do not.
enable is not start
This is the single most common misunderstanding, so it is worth stating plainly:
startaffects the running system right now. It does nothing about the next boot.enableaffects the next boot. It does nothing right now.
Install a package, run systemctl enable myservice, and then wonder why nothing is listening — that is this. Use systemctl enable --now myservice to do both, which is almost always what you meant.
The mirror image applies on the way out: disable --now stops it and prevents it starting at boot.
Reading status output
$ systemctl status nginx
● nginx.service - A high performance web server
Loaded: loaded (/lib/systemd/system/nginx.service; enabled; preset: enabled)
Active: active (running) since Fri 2026-08-21 09:12:04 UTC; 2 days ago
Main PID: 1247 (nginx)
Tasks: 5 (limit: 4915)
Memory: 12.4M
CPU: 3.221s
CGroup: /system.slice/nginx.service
├─1247 "nginx: master process /usr/sbin/nginx"
└─1248 "nginx: worker process"Two lines carry most of the information:
- Loaded — where the unit file came from, and whether it is
enabled(starts at boot) ordisabled. - Active — the current state and how long it has held it. A service that restarted five seconds ago when you expected days of uptime is a service that is crash-looping.
States you will see in the Active line:
| State | Meaning |
|---|---|
active (running) | Running normally |
active (exited) | Ran once, finished successfully — normal for one-shot setup units |
active (waiting) | Running but waiting on an event |
inactive (dead) | Not running, no error |
failed | Exited non-zero or was killed — check the logs |
activating | Still starting up |
Note that status exits non-zero when a unit is not running, which makes it awkward in scripts. Use is-active there instead.
Finding things
systemctl list-units --type=service # currently loaded services
systemctl list-units --type=service --all # including inactive ones
systemctl list-unit-files --type=service # everything installed, enabled or not
systemctl --failed # anything currently broken
systemctl list-timers # scheduled jobs and next run times
systemctl list-dependencies nginx # what it needs, and what needs itsystemctl --failed is the first thing to run on a machine that is misbehaving. It is short, and it is usually pointing straight at the problem.
Logs: journalctl
systemctl status shows only the last few log lines. For the rest, systemd has its own log system:
journalctl -u nginx # everything nginx has logged
journalctl -u nginx -f # follow live, like tail -f
journalctl -u nginx -n 100 # last 100 lines
journalctl -u nginx --since "1 hour ago"
journalctl -u nginx -p err # errors and worse only
journalctl -b # everything since this boot
journalctl -b -1 # the previous boot — for diagnosing crashes
journalctl -k # kernel messagesjournalctl -u SERVICE -n 50 --no-pager is the workhorse: the last fifty lines, printed straight out rather than into a pager. And -b -1 is invaluable — it lets you read the logs from before an unexpected reboot.
By default on some distributions the journal is kept in memory and lost at reboot. To make it persistent, create /var/log/journal and restart systemd-journald.
Unit files and overrides
Unit files live in three places, and the order matters:
| Location | Purpose | Priority |
|---|---|---|
/lib/systemd/system/ | Shipped by packages — do not edit | Lowest |
/run/systemd/system/ | Runtime, disappears at reboot | Middle |
/etc/systemd/system/ | Your local configuration | Highest |
Never edit files under /lib/systemd/system/. The next package update overwrites them. Instead use a drop-in override:
sudo systemctl edit nginxThat opens an editor for a snippet at /etc/systemd/system/nginx.service.d/override.conf containing only the settings you want to change. The original stays untouched and updates cleanly. Use systemctl edit --full if you really do need to replace the whole unit, and systemctl cat nginx to see the result of the original plus every override.
A minimal unit file looks like this:
[Unit]
Description=My application
After=network.target
[Service]
Type=simple
User=myapp
WorkingDirectory=/opt/myapp
ExecStart=/opt/myapp/bin/server
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.targetSave it as /etc/systemd/system/myapp.service, then sudo systemctl daemon-reload and sudo systemctl enable --now myapp.
Forgetting daemon-reload is a rite of passage. systemd caches unit files, so edits do nothing until you tell it to re-read them. If a change appears to have no effect, run it.
Timers instead of cron
A timer unit triggers a service unit on a schedule. Compared with cron you get proper logging through journalctl, dependency handling, and catch-up runs if the machine was off.
[Unit]
Description=Nightly backup
[Timer]
OnCalendar=daily
Persistent=true
[Install]
WantedBy=timers.targetSave as backup.timer alongside a matching backup.service, then enable the timer, not the service. Persistent=true means a missed run happens at next boot. Check your schedule syntax with systemd-analyze calendar "Mon *-*-* 03:00:00" before trusting it, and see everything scheduled with systemctl list-timers.
Troubleshooting a failing service
A repeatable sequence that resolves most cases:
systemctl status myapp # 1. what does it say happened
journalctl -u myapp -n 50 --no-pager # 2. what did it log
systemctl cat myapp # 3. what is it actually configured to run
sudo -u myapp /opt/myapp/bin/server # 4. run it by hand as its own userStep four catches most of it. A service that works when you run it as root but fails under systemd is usually hitting a permissions problem, a missing environment variable, or a relative path that resolved differently. systemd starts services with a deliberately minimal environment — your shell’s PATH and exported variables are not there.
Other things worth knowing when hunting a problem:
ExecStartneeds an absolute path./usr/bin/python3, notpython3.- If a service starts then immediately shows
inactive (dead), itsTypeis probably wrong. A process that forks into the background needsType=forking; one that stays in the foreground needsType=simple. systemd-analyze verify /etc/systemd/system/myapp.servicecatches syntax errors and bad directives.systemd-analyze blameranks units by startup time when boot is slow.
Masking
sudo systemctl mask apache2
sudo systemctl unmask apache2Masking is stronger than disabling: it links the unit to /dev/null so it cannot be started at all, not even as a dependency of something else. Use it when a package keeps resurrecting a service you do not want — the classic case being apache2 starting up and taking port 80 from nginx.
Remember to unmask before troubleshooting why something “will not start”. A masked unit fails with a distinctly unhelpful message.
User services
Add --user and systemd manages services for your login session instead of the whole machine. Unit files go in ~/.config/systemd/user/ and no root is needed.
systemctl --user enable --now syncthing
journalctl --user -u syncthing -fBy default these stop when you log out. sudo loginctl enable-linger yourusername keeps them running.
Quick reference
systemctl status UNIT # inspect
systemctl enable --now UNIT # start now and at boot
systemctl disable --now UNIT # stop now and at boot
systemctl restart UNIT # bounce it
systemctl reload UNIT # run the unit’s ExecReload=; not always either
systemctl --failed # what is broken
systemctl daemon-reload # after editing unit files
systemctl edit UNIT # safe override
systemctl cat UNIT # the unit file plus overrides, not the service’s own configuration
journalctl -u UNIT -n 50 --no-pager # recent logs
journalctl -u UNIT -f # follow live
systemctl list-timers # scheduled jobs
Related commands
- cron — the older way to schedule jobs, and an honest comparison with systemd timers.
- Processes — why you should stop a service with
systemctlrather than killing it, and what to do when it restarts itself. - Disk space — the journal is frequently gigabytes;
journalctl --vacuum-sizeis often the quickest win on a full disk. - ssh — reloading sshd safely after a configuration change.
- File permissions — the usual reason a unit runs by hand but fails as a service.
