Before this:First boot & SSHUsers, permissions & updates
Services with systemd
Key takeaways
The difference between “a program I run” and “an appliance” is a service:
something the OS itself starts at boot, supervises, and restarts when it dies. On
Linux that supervisor is systemd. You describe the program once in a unit
file (its ExecStart command, the user it runs as, Restart=on-failure),
then systemctl enable --now it — from that moment it survives every reboot and
power cut, unattended. Its output lands in the journal, read with
journalctl -u name. This one file is the load-bearing pattern of the whole
appliance.
Everything so far — headless boot, SSH, updates — has been preparing the stage for this lesson. An appliance’s defining behaviour is that its job starts itself: power arrives, and a minute later the work is running, no login, no human. systemd is how Linux delivers that promise.
What is a service, and what does systemd do?
A service (traditionally a daemon) is a long-running background program with
no terminal attached. systemd is the init system — the first process the kernel
starts — and the manager of everything after it: it starts services in the right
order at boot, tracks their state, restarts them when they crash, and collects
their logs. You talk to it with systemctl:
$ systemctl status ssh # is it running? since when? recent log lines
$ sudo systemctl restart ssh # stop + start
$ sudo systemctl enable ssh # start at every boot
The Linux CLI module introduces these commands broadly; here we go one level deeper and write a service, because your appliance’s daemon needs one.
What’s inside a unit file?
A unit file is a short INI-style description of how to run a program. Here is the realistic shape of one for a decoder daemon — the same skeleton Unit 6 will use for GopherTrunk:
# /etc/systemd/system/gophertrunk.service
[Unit]
Description=GopherTrunk trunking scanner daemon
After=network-online.target
Wants=network-online.target
[Service]
User=gophertrunk
ExecStart=/usr/local/bin/gophertrunk daemon -config /etc/gophertrunk/config.yaml
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
Reading it line by line:
| Line | What it does |
|---|---|
After= / Wants=network-online.target |
Wait for the network — a daemon serving a web console shouldn’t race it |
User= |
Run as a dedicated unprivileged user, not root — smallest possible blast radius |
ExecStart= |
The one command to run — absolute paths, no shell tricks |
Restart=on-failure + RestartSec=5 |
The appliance magic: crash → wait 5 s → start again, forever |
WantedBy=multi-user.target |
Which boot stage enable hooks it into — normal non-graphical boot |
Create the service user (sudo adduser --system --group gophertrunk) so User=
has someone to be, then install the unit:
$ sudo systemctl daemon-reload # re-read unit files
$ sudo systemctl enable --now gophertrunk # boot-start + start now
$ systemctl status gophertrunk
● gophertrunk.service - GopherTrunk trunking scanner daemon
Active: active (running) since Tue 2026-08-18 09:14:02 GMT; 2min ago
Where did the program’s output go?
Into the journal — systemd captures stdout and stderr automatically:
$ journalctl -u gophertrunk -e # jump to the newest entries
$ journalctl -u gophertrunk -f # follow live, like tail -f
$ journalctl -u gophertrunk --since "1 hour ago"
This is your window into a headless daemon’s mind, and you’ll live in it during
Unit 6. One SBC-specific caveat: the journal is disk writes, and unbounded logging
is exactly the load that wears SD cards — SD-card wear
shows how to cap it (SystemMaxUse= in journald.conf).
What does restart-on-failure buy — and not buy?
Restart=on-failure converts a crash from an outage into a blip: the daemon dies at
3am, systemd relaunches it five seconds later, and the appliance never noticed you
were asleep. What it does not buy: recovery from a hung process (running but
doing nothing — watchdogs handle that, in
Watchdogs & recovery), or a fix for a
program that crashes instantly every time (systemd will notice the crash-loop and
give up; the journal tells you why it’s crashing). Restart is the safety net, not a
substitute for reading the logs.
Rule of thumb: anything your appliance needs to be doing at 3am belongs in a unit file. If starting it requires you to log in and type something, it isn’t an appliance yet.
Quick check: what does sudo systemctl enable --now gophertrunk do?
Recap
- A service is a program the OS starts, supervises, and restarts —
systemd is the supervisor,
systemctlthe steering wheel. - A unit file describes the program once:
ExecStart, a dedicated User=,Restart=on-failure, and network ordering. systemctl enable --now= start at every boot and start now — the appliance’s core guarantee across reboots and power cuts.- Logs land in the journal (
journalctl -u name -f) — and on an SBC the journal’s size should be bounded. - Restart handles crashes; hung processes need watchdogs (Unit 5), and crash-loops need you to read the journal.
Next up: Networking your board.
Frequently asked questions
How do I make a program start automatically when a Raspberry Pi boots?
Write a small systemd unit file in /etc/systemd/system describing the program (its ExecStart command, the user to run as, and Restart=on-failure), then run sudo systemctl enable –now yourservice. enable registers it to start at every boot; –now also starts it immediately. From then on the program starts itself after every power cut and reboot with no login required.
What is the difference between systemctl enable and systemctl start?
start runs the service right now, in this boot, and does nothing about the future. enable registers it to start automatically at boot but doesn’t launch it this second. An appliance service wants both — enable –now is the shorthand that does the two together.
Where do a service's logs go?
systemd captures everything the service prints to standard output and error into the journal. Read it with journalctl -u yourservice; add -f to follow live like tail -f, or -e to jump to the end. No log files need to be configured — though on an SBC you should bound the journal’s size so it doesn’t wear the SD card.