Once Raspberry Pi projects move from prototype to unattended deployment, process supervision matters more than application code.
My standard unit files include explicit dependencies, restart backoff, and environment files for configuration. I avoid hardcoding paths and credentials directly in unit definitions.
Timer units are useful for periodic maintenance tasks like backups, cleanup, and health probes. They are easier to audit than ad-hoc cron scripts spread across machines.
With journal persistence enabled, debugging startup issues is straightforward. Service logs, exit codes, and restart counters are all in one place.
A representative service unit
Here is the shape of the unit file I reuse for a long-running Python or Rust worker on a Pi. It captures most of the patterns I care about in one place: dependency ordering, restart backoff, a restart storm limit, and configuration pulled from an environment file instead of being baked into the unit.
[Unit]
Description=Sensor ingest worker
After=network-online.target
Wants=network-online.target
Requires=mosquitto.service
[Service]
Type=simple
User=sensor
Group=sensor
WorkingDirectory=/opt/sensor-ingest
EnvironmentFile=/etc/sensor-ingest/worker.env
ExecStart=/opt/sensor-ingest/.venv/bin/python -m sensor_ingest.worker
Restart=on-failure
RestartSec=5
StartLimitIntervalSec=120
StartLimitBurst=5
[Install]
WantedBy=multi-user.target
A few notes on the choices. Restart=on-failure restarts on a non-zero exit or signal but leaves a clean systemctl stop alone. RestartSec=5 gives the network or broker a moment to settle instead of hammering a dependency. StartLimitIntervalSec and StartLimitBurst mean that if the worker crashes five times inside two minutes, systemd gives up and leaves the unit in a failed state rather than looping forever — which is exactly what you want to notice on a headless Pi. I put Requires= on the broker because this worker is useless without it, and After=network-online.target because it needs a real address, not just a configured interface.
The EnvironmentFile keeps secrets and host-specific paths out of the unit itself. That file (/etc/sensor-ingest/worker.env) is a plain KEY=value list, owned by the service user and mode 0600:
MQTT_HOST=127.0.0.1
MQTT_PORT=1883
MQTT_USERNAME=ingest
MQTT_PASSWORD=change-me
LOG_LEVEL=info
A timer for periodic maintenance
For backups, cleanup, and health probes I prefer a .timer paired with a oneshot service over a cron entry. It logs to the journal like everything else and survives reboots cleanly. The service unit (sensor-backup.service) is a plain oneshot:
[Unit]
Description=Nightly sensor database backup
[Service]
Type=oneshot
ExecStart=/opt/sensor-ingest/scripts/backup.sh
The timer (sensor-backup.timer) drives it:
[Unit]
Description=Run sensor backup nightly
[Timer]
OnCalendar=*-*-* 02:30:00
Persistent=true
RandomizedDelaySec=300
[Install]
WantedBy=timers.target
Persistent=true runs a missed job after the Pi comes back from downtime, and RandomizedDelaySec avoids every device on the LAN waking at the exact same second.
Persistent logs and enabling units
By default the journal on a Pi is volatile and disappears on reboot, which is the opposite of what you want when chasing a startup failure that only happens once a week. I set persistence explicitly in /etc/systemd/journald.conf:
[Journal]
Storage=persistent
SystemMaxUse=200M
Then create /var/log/journal (or let journald create it) and restart the service:
sudo mkdir -p /var/log/journal
sudo systemctl restart systemd-journald
Enabling and starting units in one step is the last habit worth mentioning. --now both enables (starts on boot) and starts immediately, which avoids the classic "it works until the next reboot" surprise:
sudo systemctl daemon-reload
sudo systemctl enable --now sensor-ingest.service
sudo systemctl enable --now sensor-backup.timer
After that, systemctl status, journalctl -u sensor-ingest.service, and systemctl list-timers give me everything I need to see what ran, why it stopped, and how many times it restarted.
Sources
- Imported from the previous version of the site (gaborl.hu). Original publication around 2024-12-11.
- systemd service unit documentation,
systemd.service(5): https://www.freedesktop.org/software/systemd/man/latest/systemd.service.html - systemd timer documentation,
systemd.timer(5): https://www.freedesktop.org/software/systemd/man/latest/systemd.timer.html - journald configuration,
journald.conf(5): https://www.freedesktop.org/software/systemd/man/latest/journald.conf.html
Update history
- — Adopted from previous gaborl.hu (Hugo) site via legacy import.