Your Raspberry Pi is humming along, running a Python script that logs temperature data every five minutes. It works—until the script crashes at 3 AM, the log file fills up, or the process silently dies after a memory leak. You only notice when you check the dashboard two days later.
If this sounds familiar, you're ready to move beyond nohup and cron into the world of systemd. It's already on your system, it's free, and it can turn fragile home automation scripts into resilient, self-healing services.
Systemd has been the default init system for most major Linux distributions—Fedora, Debian, Ubuntu, Arch Linux, openSUSE—for over a decade. As of 2023, it powers more than 70% of Linux distributions, making it the de facto standard for process management on Linux. While it sparked heated debates in the early 2010s, the dust has settled: systemd is here, it's mature, and it's incredibly capable.
Home automation projects share common needs: run scripts reliably, start them at boot, restart them if they crash, schedule tasks, and react to events. Systemd addresses all of these natively. It provides dependency-based startup ordering, automatic restarts, precise timers, file-change monitoring, socket activation, resource limits, and centralized logging—all through simple text files you can edit with any text editor.
We'll walk through systemd's core concepts, then build real automation examples: a temperature sensor logger, a nightly backup timer, a photo upload watcher, an on-demand MQTT broker, and a resilient Home Assistant service. You'll learn how to debug services with journald, secure them with user-level units, and combine systemd with udev for hardware-triggered automation.
Systemd is an init system—the first process that runs when your Linux machine boots. It manages all other processes, services, and system resources. But calling it just an "init system" undersells it. Systemd is a complete service management framework that handles process supervision, logging, scheduling, device management, and more.
Systemd organizes everything into units. A unit is a configuration file that describes a resource systemd manages. Common unit types include:
.service): Long-running processes or one-shot commands.timer): Scheduled triggers for services.path): File or directory change monitors.socket): Network or IPC socket listenersTargets are groups of units that represent system states. Think of them as "runlevels" on steroids. For example, multi-user.target is the standard state for a running system without a GUI.
The systemctl command is your control panel. You'll use it constantly:
systemctl start myservice
systemctl stop myservice
systemctl status myservice
systemctl enable myservice # start at boot
systemctl disable myservice # remove from boot
A unit file is an INI-style text file with sections. Here's the skeleton:
[Unit]
Description=My automation service
After=network.target
[Service]
ExecStart=/usr/bin/python3 /home/pi/scripts/automation.py
Restart=on-failure
[Install]
WantedBy=multi-user.target
[Unit]: Metadata and dependencies. After= specifies ordering; Requires= and Wants= specify hard and soft dependencies.[Service]: How to run the process. ExecStart= is the command, Restart= controls failure behavior.[Install]: How the unit integrates into boot. WantedBy= creates a symlink in the specified target's .wants directory when you run systemctl enable.Key Takeaway: A unit file is just a text file. The
[Service]section defines how to run your process; the[Install]section defines when it starts at boot.
Let's turn a temperature sensor script into a managed service. First, create your script at /home/pi/scripts/temp_logger.py:
#!/usr/bin/env python3
import time
import random
while True:
temp = random.uniform(18.0, 28.0)
with open("/home/pi/data/temp.log", "a") as f:
f.write(f"{time.time()},{temp:.2f}\n")
time.sleep(300)
Now create the service unit at /etc/systemd/system/temp-logger.service:
[Unit]
Description=Temperature Sensor Logger
After=network.target
[Service]
ExecStart=/usr/bin/python3 /home/pi/scripts/temp_logger.py
WorkingDirectory=/home/pi/scripts
Environment=PYTHONUNBUFFERED=1
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl start temp-logger
sudo systemctl enable temp-logger
The daemon-reload tells systemd to re-read unit files. start launches it immediately; enable makes it start at boot. Check status with:
systemctl status temp-logger
The Environment= directive sets variables for your script. You can also use EnvironmentFile= to load from a file:
EnvironmentFile=/etc/temp-logger.conf
WorkingDirectory= sets the process's current directory, which is useful if your script uses relative paths.
This service now runs in the background, restarts automatically if it crashes (with a 5-second delay), and starts on boot. Your logging continues without manual intervention.
Key Takeaway:
Restart=on-failurecombined withRestartSec=5means your script gets back up within seconds of any crash—no more dead sensors at 3 AM.
Cron has served Linux for decades, but systemd timers offer several advantages:
systemctl) manages everything.A timer unit is paired with a service unit. Here's a timer that runs a backup every night at 2:30 AM:
# /etc/systemd/system/backup.timer
[Unit]
Description=Nightly backup timer
[Timer]
OnCalendar=*-*-* 02:30:00
Persistent=true
[Install]
WantedBy=timers.target
The associated service:
# /etc/systemd/system/backup.service
[Unit]
Description=Nightly database backup
[Service]
Type=oneshot
ExecStart=/home/pi/scripts/backup_db.sh
Note Type=oneshot—the service runs once and exits. The timer repeats it.
Persistent=true is a killer feature. If your Raspberry Pi was powered off at 2:30 AM, the timer fires immediately at next boot, catching up on the missed backup. Cron simply skips missed jobs.
sudo systemctl enable --now backup.timer
systemctl list-timers
You'll see the timer listed with its next trigger time.
Key Takeaway: Systemd timers aren't just cron replacements—they're better.
Persistent=trueensures scheduled tasks run even if the system was off.
Path units watch files or directories and trigger a service when changes occur. This is perfect for automation: new photo detected, config file modified, download completed.
# /etc/systemd/system/photo-watch.path
[Unit]
Description=Watch for new photos
[Path]
PathModified=/home/pi/photos/watch
Unit=photo-upload.service
[Install]
WantedBy=multi-user.target
# /etc/systemd/system/photo-upload.service
[Unit]
Description=Upload new photos
[Service]
Type=oneshot
ExecStart=/home/pi/scripts/upload_photos.sh
Beyond photo uploads, path units are ideal for:
/etc/myapp/config.yaml and restart the service on changedownloads/ folder and process new filessudo systemctl enable --now photo-watch.path
Drop a photo into /home/pi/photos/watch and the upload script fires within seconds.
Key Takeaway: Path units turn filesystem events into automation triggers, eliminating the need for polling loops in your scripts.
Socket activation starts a service only when a connection arrives on a listening socket. The socket exists before the service runs; when traffic hits it, systemd spawns the service to handle it. When idle, the service can stop, freeing memory.
# /etc/systemd/system/mqtt.socket
[Unit]
Description=MQTT broker socket
[Socket]
ListenStream=1883
Accept=no
[Install]
WantedBy=sockets.target
# /etc/systemd/system/mqtt.service
[Unit]
Description=MQTT broker
[Service]
ExecStart=/usr/sbin/mosquitto
NonBlocking=true
On a Raspberry Pi with limited RAM, running an MQTT broker 24/7 wastes resources. With socket activation, the broker only runs when a client connects. Idle memory usage drops to near zero.
sudo systemctl enable --now mqtt.socket
Test it: start a subscriber, then check systemctl status mqtt. The service starts only when needed.
Key Takeaway: Socket activation gives you on-demand services without the complexity of writing your own daemon logic. Systemd handles the socket; your script just processes connections.
The Restart= directive controls crash recovery:
no (default): No restarton-failure: Restart on non-zero exit, signal, or timeoutalways: Restart regardless of exit statuson-abnormal: Restart on signals and timeoutsPair with RestartSec= to add a delay between restarts, preventing tight restart loops.
Systemd's watchdog feature monitors service liveness. The service must periodically ping systemd via sd_notify or it gets killed and restarted.
[Service]
WatchdogSec=30
Restart=on-failure
Your script must call sd_notify(0, "WATCHDOG=1") every 30 seconds. Python's systemd package provides bindings:
import systemd.daemon
systemd.daemon.notify("WATCHDOG=1")
For services that can't use sd_notify, create a health check script that runs periodically via a timer and restarts the service if it's unresponsive:
#!/bin/bash
if ! curl -f http://localhost:8123/api/ > /dev/null 2>&1; then
systemctl restart home-assistant
echo "Home Assistant restarted" | systemd-cat -t health-check
fi
[Unit]
Description=Home Assistant
After=network.target
[Service]
ExecStart=/usr/local/bin/hass
Restart=always
RestartSec=10
WatchdogSec=60
[Install]
WantedBy=multi-user.target
Key Takeaway:
Restart=always+RestartSec=10means your automation recovers from crashes automatically. Watchdogs catch hangs, not just crashes.
Systemd uses cgroups to enforce resource limits per service. Prevent runaway scripts from hogging your Pi:
[Service]
MemoryMax=256M
CPUQuota=50%
IOWeight=10
MemoryMax=256M: Hard memory capCPUQuota=50%: Limit to half a CPU coreIOWeight=10: Low disk I/O priorityNever run automation as root. Specify a dedicated user:
[Service]
User=pi
Group=pi
Ensure the user has permissions to access required files and devices.
Systemd isn't just for system services. You can run user-level services that start when you log in:
systemctl --user enable --now my-automation.service
User units live in ~/.config/systemd/user/. Enable lingering to run user services without an active login session:
loginctl enable-linger pi
# ~/.config/systemd/user/photo-organizer.service
[Unit]
Description=Organize photos
[Service]
ExecStart=/home/pi/scripts/organize_photos.py
Restart=on-failure
[Install]
WantedBy=default.target
Key Takeaway: User-level services with
loginctl enable-lingergive you systemd's power without root privileges. Your automation runs with the least privilege needed.
All service output—stdout and stderr—goes to the journal. No more scattered log files:
journalctl -u temp-logger
# Last hour of logs
journalctl -u temp-logger --since "1 hour ago"
# Errors only
journalctl -u temp-logger -p err
# Follow new logs live
journalctl -u temp-logger -f
# Logs since boot
journalctl -b
Print structured data from your scripts:
print(f"temp={temp:.2f} sensor=kitchen", flush=True)
The journal indexes this output, making it searchable:
journalctl -u temp-logger | grep "sensor=kitchen"
systemctl status temp-logger
journalctl -u temp-logger -n 50
You'll see the exact error message, exit code, and stack trace if any.
Key Takeaway: Journald captures everything your service outputs. When something breaks,
journalctl -u servicename -n 50gives you the full story.
Udev rules can trigger systemd services when hardware appears. Create a rule in /etc/udev/rules.d/99-usb.rules:
ACTION=="add", SUBSYSTEM=="usb", ATTRS{idVendor}=="1234", TAG+="systemd", ENV{SYSTEMD_WANTS}="usb-automation.service"
Type=oneshot services run once and exit. Perfect for setup tasks:
[Service]
Type=oneshot
ExecStart=/home/pi/scripts/init_gpio.sh
RemainAfterExit=yes
You can manage Docker containers with systemd units:
[Service]
ExecStart=/usr/bin/docker start -a my-container
ExecStop=/usr/bin/docker stop my-container
Restart=always
When you plug in a USB drive, systemd can automatically mount it and run a backup script. The udev rule above triggers the service, and your service handles the rest.
Key Takeaway: Systemd is the glue that connects hardware events, processes, and scheduling. Combine udev rules with service units for reactive automation.
Many think systemd is only for enterprise servers. It's equally valuable on Raspberry Pis, home servers, and IoT devices. Any Linux box running automation benefits from systemd's supervision.
Cron works, but systemd timers provide precision, persistence, and integration. Once you switch, you won't go back.
Unit files look intimidating at first, but they're just key-value pairs. Start with the examples in this article and adapt.
User-level services cover most home automation needs. Use root only when you truly need system-wide access.
Systemd runs any executable. Your existing Python, Bash, or Node.js scripts work as-is. You're just adding a management layer on top.
Key Takeaway: The biggest mistake is overcomplicating it. Start with one service, get it running, then expand.
You now have a complete toolkit:
Start small. Pick one script that currently runs via nohup or cron and convert it to a systemd service. Add a timer for a scheduled task. Then explore path units and socket activation.
man systemd.service, man systemd.timer, man systemd.unitSystemd has a steep learning curve, but the payoff is massive. Your home automation will be more reliable, easier to debug, and more efficient. The time you invest in learning systemd pays dividends every time a service crashes and comes back on its own—or better yet, never crashes at all.
Create a unit file in /etc/systemd/system/ (or ~/.config/systemd/user/ for user-level). Define [Unit], [Service] with ExecStart=, and [Install] with WantedBy=. Run systemctl daemon-reload, then systemctl start and systemctl enable your service.
Yes. Systemd timers offer sub-second precision, persistent catch-up for missed runs, dependency ordering, and integration with journald. They're strictly more capable than cron.
Add Restart=on-failure (or always) and RestartSec=5 to the [Service] section. Systemd restarts the service after the specified delay.
Yes. Use user-level units with systemctl --user. Enable lingering with loginctl enable-linger to run them without an active login session.
Use journalctl -u servicename. Add flags like -f to follow, -n 50 for last 50 lines, -p err for errors only, or --since "1 hour ago" for time filtering.
Socket activation starts a service only when a connection arrives on its socket. This saves memory by keeping idle services stopped. Ideal for rarely-used services like MQTT brokers or web dashboards.
Create a path unit with PathModified= or PathChanged= pointing to the directory or file, and set Unit= to your service. Enable the path unit, not the service.
Yes. Use MemoryMax=, CPUQuota=, and IOWeight= in the [Service] section. Systemd enforces these via cgroups.
Use Environment=KEY=VALUE or EnvironmentFile=/path/to/file in the [Service] section.
start launches the service immediately. enable configures it to start at boot. You typically run both: systemctl enable --now servicename does both at once.
Ready to supercharge your home automation with systemd? Start by creating your first service unit today, and explore the power of timers, path units, and socket activation. Share your projects and questions in the comments below!