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.
systemctlsystemctl statussystemctl enable --nowsystemctl masksystemctl --failedsystemctl editsystemctl catss -tlnp
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:
| Question | Command to change it | Command to ask |
|---|---|---|
| Is it running now? | start, stop, restart, reload | systemctl is-active NAME |
| Will it start at boot? | enable, disable | systemctl 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.
- Warm-up
Read a service's status
Show the status of
sshdand 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 sshdshowsLoaded: loaded (/usr/lib/systemd/system/sshd.service; enabled; ...)andActive: active (running)withMain 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 - Practice
Now, boot, or both
Install and configure
httpdso 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 httpdprintsactiveandsystemctl is-enabled httpdprintsenabled. Afterdisablethe first still printsactivewhile the second printsdisabled.Hint
systemctl enable --now NAMEenables and starts.systemctl disable NAMEalone 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 - Practice
Make a service impossible to start
Stop
httpd, then mask it. Show thatsystemctl start httpdis refused, explain how masking differs from disabling, and then unmask it.Check your result
systemctl is-enabled httpdprintsmasked;systemctl start httpdreportsUnit httpd.service is masked. Afterunmaskit starts again.Hint
systemctl masklinks 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 - Practice
Write your own service
Create a service
heartbeat.servicethat runs/usr/local/bin/heartbeat.sh, a script that writes the date to the journal (vialogger) 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 heartbeatshowsactive (running).journalctl -u heartbeat -forjournalctl -t heartbeatshows a new line every 10 seconds.systemctl is-enabled heartbeatprintsenabled.Hint
A
[Service]section withExecStart=,Restart=on-failure, and an[Install]section withWantedBy=multi-user.target. Runsystemctl daemon-reloadafter 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 - Challenge
Find out why a service failed
Break
heartbeat.serviceby pointingExecStart=at a script path that does not exist. Restart it, list the failed units, then usesystemctl statusandjournalctl -uto find the exact error. Fix it, reset the failed state, and confirm it runs.Check your result
systemctl --failedlistsheartbeat.service. The status showsstatus=203/EXECand the journal saysFailed to execute ... No such file or directory. After the fixsystemctl is-active heartbeatisactive.Hint
systemctl edit --full heartbeator edit the file and rundaemon-reload.systemctl reset-failedclears 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