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:

SuffixWhat it is
.serviceA daemon or process — the common case
.socketA socket that starts its service on first connection
.timerA scheduled trigger — systemd’s answer to cron
.mountA filesystem mount point
.targetA group of units, roughly like an old runlevel
.pathWatches 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

CommandWhat it does
systemctl status UNITShow state, recent logs, process tree
systemctl start UNITStart it now
systemctl stop UNITStop it now
systemctl restart UNITStop then start
systemctl reload UNITRun 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 UNITReload 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 UNITStart automatically at boot
systemctl disable UNITDo not start at boot
systemctl enable --now UNITEnable and start in one step
systemctl is-active UNITIs it running? (script-friendly)
systemctl is-enabled UNITWill it start at boot? (script-friendly)
systemctl cat UNITPrint the unit file and any overrides
systemctl edit UNITCreate or edit a drop-in override
systemctl daemon-reloadRe-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:

  • start affects the running system right now. It does nothing about the next boot.
  • enable affects 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) or disabled.
  • 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:

StateMeaning
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
failedExited non-zero or was killed — check the logs
activatingStill 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 it

systemctl --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 messages

journalctl -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:

LocationPurposePriority
/lib/systemd/system/Shipped by packages — do not editLowest
/run/systemd/system/Runtime, disappears at rebootMiddle
/etc/systemd/system/Your local configurationHighest

Never edit files under /lib/systemd/system/. The next package update overwrites them. Instead use a drop-in override:

sudo systemctl edit nginx

That 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.target

Save 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.target

Save 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 user

Step 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:

  • ExecStart needs an absolute path. /usr/bin/python3, not python3.
  • If a service starts then immediately shows inactive (dead), its Type is probably wrong. A process that forks into the background needs Type=forking; one that stays in the foreground needs Type=simple.
  • systemd-analyze verify /etc/systemd/system/myapp.service catches syntax errors and bad directives.
  • systemd-analyze blame ranks units by startup time when boot is slow.

Masking

sudo systemctl mask apache2
sudo systemctl unmask apache2

Masking 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 -f

By 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 systemctl rather than killing it, and what to do when it restarts itself.
  • Disk space — the journal is frequently gigabytes; journalctl --vacuum-size is 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.