1 of 4

Services, Targets and BootLesson 1 of 4

Controlling services with systemctl

Start, stop, restart and reload services, decide what starts at boot, and find out why a service failed.

  • systemctl
  • systemctl status
  • systemctl enable --now
  • systemctl mask
  • systemctl --failed
  • systemctl edit
  • systemctl cat
  • ss -tlnp

45 min5 exercises

After this lesson you can

  • Distinguish running now from starting at boot, and control each independently or together.
  • Read systemctl status and journal output to diagnose a failing service.
  • Create a simple service unit, customise a packaged one with a drop-in, and mask a service so nothing can start it.

The idea

systemd is the first process (PID 1). It starts and supervises everything else, described as units. A service unit (name.service) is a daemon; there are also timers, mounts, sockets and targets. You control all of them with systemctl.

Two separate questions are always being answered about a service:

QuestionCommand to change itCommand to ask
Is it running now?start, stop, restart, reloadsystemctl is-active NAME
Will it start at boot?enable, disablesystemctl is-enabled NAME

They are independent. systemctl enable --now NAME does both in one go, and it is the form to reach for first when a task says “make sure it is running and starts at boot”. restart stops and starts (connections drop), reload asks the daemon to re-read its configuration without stopping (not every service supports it), and try-restart restarts only if it is already running.

mask goes further than disable: it links the unit to /dev/null, so nothing, not even another service that depends on it, can start it. Use it to forbid a service outright; unmask reverses it.

Unit files live in three places, from lowest to highest priority: /usr/lib/systemd/system/ (packages), /run/systemd/system/ (temporary), /etc/systemd/system/ (yours). Never edit the packaged copy; create an override instead. After creating or changing any unit file, run systemctl daemon-reload so systemd re-reads them.

Try it

systemctl status sshd --no-pager
● sshd.service - OpenSSH server daemon
     Loaded: loaded (/usr/lib/systemd/system/sshd.service; enabled; preset: enabled)
     Active: active (running) since Tue 2026-11-03 08:01:12 +03; 3h 40min ago
       Docs: man:sshd(8)
   Main PID: 842 (sshd)
      Tasks: 1 (limit: 22900)
     Memory: 4.2M
     CGroup: /system.slice/sshd.service
             └─842 "sshd: /usr/sbin/sshd -D [listener]"

Nov 03 08:01:12 rhel10 systemd[1]: Started sshd.service - OpenSSH server daemon.

Read it top to bottom: the Loaded line gives the file and whether it is enabled for boot; Active gives the live state and how long; the last lines are the most recent journal entries for the unit. The bullet is green for running, red for failed.

Run now, start at boot

sudo dnf install -y httpd
sudo systemctl enable --now httpd
systemctl is-active httpd
systemctl is-enabled httpd
Created symlink '/etc/systemd/system/multi-user.target.wants/httpd.service' → '/usr/lib/systemd/system/httpd.service'.
active
enabled

enable simply creates the symlink shown: that is all “starts at boot” means. A service that is enabled but not running will come up at the next boot; one that is running but disabled will not.

Check what is listening

sudo ss -tlnp | head -4
State  Recv-Q Send-Q Local Address:Port Peer Address:Port Process
LISTEN 0      128          0.0.0.0:22        0.0.0.0:*     users:(("sshd",pid=842,fd=3))
LISTEN 0      511                *:80              *:*     users:(("httpd",pid=3120,fd=4))

-t TCP, -l listening, -n numbers instead of names, -p show the process. Add -u for UDP. This is the quickest way to confirm “the network service is up and bound to the right port”.

Your own service

[Unit]
Description=Heartbeat logger

[Service]
ExecStart=/usr/local/bin/heartbeat.sh
Restart=on-failure

[Install]
WantedBy=multi-user.target

ExecStart must be a full path to an executable file. Restart=on-failure brings the process back if it crashes. WantedBy=multi-user.target is what enable links the unit into, so that it starts in the normal multi-user boot.

sudo systemctl daemon-reload
sudo systemctl enable --now heartbeat
journalctl -t heartbeat -n 2 --no-pager
Nov 03 11:50:10 rhel10 heartbeat[4410]: alive at 11:50:10
Nov 03 11:50:20 rhel10 heartbeat[4410]: alive at 11:50:20

Customise a packaged service without editing it

sudo systemctl edit httpd
[Service]
Restart=always
RestartSec=5

systemctl edit writes /etc/systemd/system/httpd.service.d/override.conf and reloads for you. systemctl cat httpd shows the original and the override together. The packaged file stays untouched, so updates do not overwrite your change.

When a service fails

systemctl --failed --no-pager
systemctl status heartbeat --no-pager | head -6
journalctl -u heartbeat -n 3 --no-pager
  UNIT              LOAD   ACTIVE SUB    DESCRIPTION
● heartbeat.service loaded failed failed Heartbeat logger
● heartbeat.service - Heartbeat logger
     Loaded: loaded (/etc/systemd/system/heartbeat.service; enabled; preset: disabled)
     Active: failed (Result: exit-code) since Tue 2026-11-03 11:52:03 +03; 4s ago
    Process: 4502 ExecStart=/usr/local/bin/heartbeat-missing.sh (code=exited, status=203/EXEC)
Nov 03 11:52:03 rhel10 systemd[4502]: heartbeat.service: Failed to locate executable /usr/local/bin/heartbeat-missing.sh: No such file or directory

status=203/EXEC means systemd could not execute the file: wrong path, no execute bit, or a bad shebang. Other common codes: status=1/FAILURE (the program itself exited with an error, so read the program’s own message in the journal) and status=217/USER (the User= does not exist). After fixing, daemon-reload, then systemctl reset-failed and restart.

Going further

systemctl list-units --type=service --state=running lists what is running; systemctl list-unit-files --state=enabled lists what starts at boot. systemctl list-dependencies httpd shows what a service needs, and systemctl show -p After httpd shows ordering. Requires= and Wants= declare dependencies; After= only orders. The SELinux and firewall layers can also stop a service from serving traffic even when systemctl says active, so a “running” service is the start of troubleshooting, not the end.

Practice

0 of 5 done

Do these on your own system. They get harder. Check each result, then tick it off.

  1. Warm-up

    Read a service's status

    Show the status of sshd and answer from the output alone: is it running, is it enabled at boot, what is its main PID and which file defines it?

    Check your result

    systemctl status sshd shows Loaded: loaded (/usr/lib/systemd/system/sshd.service; enabled; ...) and Active: active (running) with Main PID:.

    Hint

    Loaded: line holds the file and the boot setting. Active: holds the current state. Use --no-pager.

    Solution
    systemctl status sshd --no-pager
    systemctl is-active sshd
    systemctl is-enabled sshd
  2. Practice

    Now, boot, or both

    Install and configure httpd so that it is running now and starts at every boot, using one command. Verify both facts separately. Then disable it at boot without stopping it, and state what that means for the next reboot.

    Check your result

    systemctl is-active httpd prints active and systemctl is-enabled httpd prints enabled. After disable the first still prints active while the second prints disabled.

    Hint

    systemctl enable --now NAME enables and starts. systemctl disable NAME alone does not stop it.

    Solution
    sudo dnf install -y httpd
    sudo systemctl enable --now httpd
    systemctl is-active httpd; systemctl is-enabled httpd
    sudo systemctl disable httpd
    systemctl is-active httpd; systemctl is-enabled httpd
  3. Practice

    Make a service impossible to start

    Stop httpd, then mask it. Show that systemctl start httpd is refused, explain how masking differs from disabling, and then unmask it.

    Check your result

    systemctl is-enabled httpd prints masked; systemctl start httpd reports Unit httpd.service is masked. After unmask it starts again.

    Hint

    systemctl mask links the unit to /dev/null.

    Solution
    sudo systemctl stop httpd
    sudo systemctl mask httpd
    systemctl is-enabled httpd
    sudo systemctl start httpd
    sudo systemctl unmask httpd
    sudo systemctl start httpd
  4. Practice

    Write your own service

    Create a service heartbeat.service that runs /usr/local/bin/heartbeat.sh, a script that writes the date to the journal (via logger) every 10 seconds in an endless loop. The service must restart automatically if it fails and start at boot. Enable and start it and read its log.

    Check your result

    systemctl status heartbeat shows active (running). journalctl -u heartbeat -f or journalctl -t heartbeat shows a new line every 10 seconds. systemctl is-enabled heartbeat prints enabled.

    Hint

    A [Service] section with ExecStart=, Restart=on-failure, and an [Install] section with WantedBy=multi-user.target. Run systemctl daemon-reload after creating the file.

    Solution
    sudo tee /usr/local/bin/heartbeat.sh <<'EOT'
    #!/bin/bash
    while true; do
        logger -t heartbeat "alive at $(date +%T)"
        sleep 10
    done
    EOT
    sudo chmod 755 /usr/local/bin/heartbeat.sh
    sudo tee /etc/systemd/system/heartbeat.service <<'EOT'
    [Unit]
    Description=Heartbeat logger
    
    [Service]
    ExecStart=/usr/local/bin/heartbeat.sh
    Restart=on-failure
    
    [Install]
    WantedBy=multi-user.target
    EOT
    sudo systemctl daemon-reload
    sudo systemctl enable --now heartbeat
    journalctl -t heartbeat --no-pager | tail -3
  5. Challenge

    Find out why a service failed

    Break heartbeat.service by pointing ExecStart= at a script path that does not exist. Restart it, list the failed units, then use systemctl status and journalctl -u to find the exact error. Fix it, reset the failed state, and confirm it runs.

    Check your result

    systemctl --failed lists heartbeat.service. The status shows status=203/EXEC and the journal says Failed to execute ... No such file or directory. After the fix systemctl is-active heartbeat is active.

    Hint

    systemctl edit --full heartbeat or edit the file and run daemon-reload. systemctl reset-failed clears a failed mark.

    Solution
    sudo sed -i 's#heartbeat.sh#heartbeat-missing.sh#' /etc/systemd/system/heartbeat.service
    sudo systemctl daemon-reload
    sudo systemctl restart heartbeat
    systemctl --failed --no-pager
    systemctl status heartbeat --no-pager
    journalctl -u heartbeat -n 5 --no-pager
    sudo sed -i 's#heartbeat-missing.sh#heartbeat.sh#' /etc/systemd/system/heartbeat.service
    sudo systemctl daemon-reload
    sudo systemctl reset-failed heartbeat
    sudo systemctl restart heartbeat
    systemctl is-active heartbeat