Table of Contents
- Introduction
- What is Systemd?
- Core Components of Systemd for Automation
- Creating Custom Services: Automate Long-Running Tasks
- Scheduling Tasks with Timers: Beyond Cron
- Managing Dependencies: Ensure Services Start in Order
- Logging and Monitoring: Debugging Automations
- Advanced Tips: Template Units and Socket Activation
- Conclusion
- References
What is Systemd?
Systemd is a system and service manager for Linux operating systems, designed to replace the traditional SysV init system. Launched in 2010, it has since become the default init system for major distributions due to its speed, parallelization capabilities, and robust feature set.
At its core, systemd’s mission is to manage the entire lifecycle of the system, from boot to shutdown. But it’s far more than just an init system: it handles service management, device management, mount points, network configuration, and even logging (via journald). For automation, systemd shines by providing a unified framework to define, schedule, and monitor tasks—all through declarative configuration files and a powerful command-line interface (systemctl).
Core Components of Systemd for Automation
To harness systemd’s automation features, you first need to understand its core building blocks:
Units
Units are the fundamental objects systemd manages. They represent resources like services, scheduled tasks, or mount points, defined by plain-text configuration files (usually ending in .service, .timer, etc.).
Common unit types relevant to automation:
.service: Manages long-running processes (e.g., web servers, databases)..timer: Schedules when a.serviceunit runs (replaces cron for many use cases)..target: Groups units to define system states (e.g.,multi-user.targetfor a text-based login,graphical.targetfor a desktop environment)..socket: Triggers services when a network socket or file descriptor is accessed (on-demand activation).
Targets
Targets are like “runlevels” in traditional init systems but more flexible. They don’t execute tasks directly; instead, they depend on other units to define a system state. For example:
multi-user.target: Ensures all services needed for a multi-user command-line environment are running.network-online.target: Ensures the network is fully configured (useful for services requiring network access).
You can use targets to control when your automation runs (e.g., “start my backup service only after the network is online”).
Timers
Timers are systemd’s answer to cron jobs, but with more precision and integration. A timer unit (*.timer) schedules when a corresponding service unit (*.service) runs. Unlike cron, timers:
- Integrate with systemd’s dependency management (e.g., run a task only after a service starts).
- Provide detailed logging via
journald. - Support calendar-based scheduling, boot-time delays, and monotonic timers (e.g., “run 10 minutes after boot”).
Services
Service units (*.service) define how to start, stop, and restart processes. They are the workhorses of automation, executing scripts, apps, or background tasks. Services can be configured to restart on failure, run as specific users, and depend on other services (e.g., “start my app only after the database starts”).
Creating Custom Services: Automate Long-Running Tasks
One of the most common automation use cases is running a script or application as a background service. Let’s walk through creating a custom service unit.
Service File Structure
A service file has three main sections:
| Section | Purpose |
|---|---|
[Unit] | Metadata (description, dependencies, documentation). |
[Service] | How to run the service (executable path, restart policy, user, etc.). |
[Install] | How the service is installed (e.g., which target to enable it under). |
Example: A Simple Backup Service
Let’s create a service to run a daily backup script. Suppose we have a backup script at /usr/local/bin/daily-backup.sh that archives /home/user/documents to /backups.
Step 1: Write the Service File
Create /etc/systemd/system/daily-backup.service with:
[Unit]
Description=Daily backup of user documents
Documentation=man:tar(1)
Requires=network-online.target # Ensure network is up (if backup uses cloud storage)
After=network-online.target # Start only after network is ready
[Service]
Type=oneshot # Run once and exit (not a long-running process)
User=user # Run as "user" (not root, for security)
WorkingDirectory=/home/user # Set working directory
ExecStart=/usr/local/bin/daily-backup.sh # Path to the backup script
Restart=on-failure # Restart if the script fails
[Install]
WantedBy=multi-user.target # Enable under multi-user environment
Managing Services with systemctl
Once your service file is created, use systemctl to manage it:
| Command | Purpose |
|---|---|
sudo systemctl daemon-reload | Reload systemd to detect new units |
sudo systemctl start daily-backup.service | Start the service immediately |
sudo systemctl status daily-backup.service | Check if the service is running |
sudo systemctl enable daily-backup.service | Start the service on boot |
Scheduling Tasks with Timers: Beyond Cron
To run our daily-backup.service automatically, we’ll pair it with a timer unit.
Timer Unit Structure
A timer unit (*.timer) has a [Timer] section defining when to trigger its service. Key directives:
| Directive | Purpose |
|---|---|
OnCalendar | Calendar event (e.g., daily, weekly, 2024-03-01 03:00:00). |
OnBootSec | Delay after boot (e.g., 5min to run 5 minutes after boot). |
OnUnitActiveSec | Run again X time after the service last finished (e.g., 12h for every 12 hours). |
AccuracySec | Tolerance for scheduling delays (default: 1min; use 1s for precision). |
Example: Daily Backup Timer
Create /etc/systemd/system/daily-backup.timer:
[Unit]
Description=Timer for daily backup service
[Timer]
OnCalendar=daily # Run daily at 00:00 (midnight)
Persistent=true # Run missed tasks if the system was off
AccuracySec=1min # Allow 1-minute delay for system load
[Install]
WantedBy=timers.target # Enable under the timers target
Linking Timer and Service
The timer and service must share the same base name (e.g., daily-backup.timer triggers daily-backup.service). To activate:
sudo systemctl daemon-reload # Detect the new timer
sudo systemctl enable --now daily-backup.timer # Enable and start the timer
sudo systemctl list-timers --all # Verify the timer is scheduled
Output will show the next run time:
NEXT LEFT LAST PASSED UNIT ACTIVATES
Wed 2024-03-01 00:00:00 UTC 11h left Tue 2024-02-28 00:00:00 UTC 13h ago daily-backup.timer daily-backup.service
Advantages Over Cron
- Dependency-aware: Timers can depend on targets/services (e.g., run after
network-online.target). - Logging: All timer activity is logged to
journald(e.g.,journalctl -u daily-backup.timer). - Missed tasks:
Persistent=trueensures tasks run even if the system was off during the scheduled time.
Managing Dependencies: Ensure Services Start in Order
Many automations depend on other services (e.g., a web app needs PostgreSQL to start first). Systemd’s dependency directives let you define these relationships.
Dependency Directives
| Directive | Behavior |
|---|---|
Requires | Strict dependency: If the required service fails, this service fails. |
Wants | Weak dependency: This service prefers the required service but starts anyway if it fails. |
After | Start this service after the listed services/targets. |
Before | Start this service before the listed services/targets. |
Example: A Web App with Database Dependencies
Suppose we have a Node.js app that depends on PostgreSQL. Create /etc/systemd/system/node-app.service:
[Unit]
Description=Node.js web application
Requires=postgresql.service # App needs PostgreSQL to run
After=postgresql.service network-online.target # Start after PostgreSQL and network
[Service]
Type=simple
User=app-user
WorkingDirectory=/opt/node-app
ExecStart=/usr/bin/node server.js
Restart=always # Restart on crash
[Install]
WantedBy=multi-user.target
Now, when node-app.service starts, systemd ensures postgresql.service and network-online.target are active first.
Logging and Monitoring: Debugging Automations
Systemd’s journald collects logs from all units, making it easy to debug failed automations.
Using journalctl for Logs
journalctl is the command-line tool to query journald logs. Basic usage for a service:
# View logs for the backup service
journalctl -u daily-backup.service
# View logs from the last hour
journalctl -u daily-backup.service --since "1 hour ago"
# Follow logs in real-time (like tail -f)
journalctl -u daily-backup.service -f
Filtering and Analyzing Logs
- By priority: Use
-p errto show only errors, or-p infofor informational messages.journalctl -u daily-backup.service -p err - By time:
--since "2024-02-28"or--until "yesterday". - By unit and timer: Check why a timer didn’t run:
journalctl -u daily-backup.timer
Advanced Tips: Template Units and Socket Activation
For more complex workflows, systemd offers advanced features to scale and optimize automation.
Template Units for Scalability
Template units let you create dynamic, reusable services using a @ in the filename (e.g., [email protected]). They’re ideal for running multiple instances of the same app (e.g., different ports).
Example: /etc/systemd/system/[email protected] (note the @):
[Unit]
Description=App instance %I
After=network.target
[Service]
Type=simple
User=app-user
WorkingDirectory=/opt/app-%I
ExecStart=/usr/bin/node server.js --port %I # %I is replaced with the instance name
[Install]
WantedBy=multi-user.target
Now start two instances (port 3000 and 3001):
sudo systemctl start [email protected]
sudo systemctl start [email protected]
Socket Activation: On-Demand Service Start
Socket activation starts a service only when a socket is accessed (e.g., a port, file, or Unix socket). This saves resources for rarely used services.
Example: A socket unit /etc/systemd/system/app.socket that triggers app.service when port 8080 is accessed:
[Unit]
Description=Socket for app
[Socket]
ListenStream=8080 # Listen on TCP port 8080
Accept=yes # Handle multiple connections
[Install]
WantedBy=sockets.target
The corresponding service ([email protected], using a template for multiple connections):
[Unit]
Description=App instance for %I
[Service]
User=app-user
ExecStart=-/usr/bin/node server.js --socket %I # %I is the socket file descriptor
Now, when a request hits port 8080, systemd starts app@<fd>.service automatically.
Conclusion
Systemd is more than an init system—it’s a powerful automation engine that centralizes task scheduling, service management, and logging. By mastering units, timers, dependencies, and logging, you can streamline workflows, reduce manual intervention, and build robust, self-healing systems.
Whether you’re replacing cron jobs with timers, ensuring services start in order, or debugging with journalctl, systemd provides the tools to automate with confidence. Start small (e.g., a backup service), then explore advanced features like template units and socket activation to scale your automation.