When working with systemd timers, a common requirement is triggering multiple services from a single timer. The documentation shows conflicting approaches, leaving many administrators uncertain about the optimal implementation. Let's clarify the correct methodology.
For a single service, the configuration is straightforward:
# /etc/systemd/system/backup.service
[Unit]
Description=Database Backup
[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup.sh
# /etc/systemd/system/backup.timer
[Unit]
Description=Daily Backup Timer
[Timer]
OnCalendar=daily
Unit=backup.service
[Install]
WantedBy=timers.target
The most reliable method uses a custom target that groups services:
# /etc/systemd/system/daily-tasks.target
[Unit]
Description=Target for daily maintenance tasks
StopWhenUnneeded=yes
# Service files (example for service1)
[Unit]
Description=Service 1
WantedBy=daily-tasks.target
[Service]
Type=oneshot
ExecStart=/usr/bin/service1.sh
# Timer configuration
[Unit]
Description=Daily Tasks Timer
[Timer]
OnCalendar=daily
Unit=daily-tasks.target
[Install]
WantedBy=timers.target
The target-based approach provides several advantages:
- Explicit dependency management
- Clean separation of concerns
- Consistent with systemd's design patterns
- Easier to maintain and extend
While other approaches exist, they have limitations:
# Problematic alternative (not recommended)
[Timer]
Unit=service1.service # Only triggers one service
The manual symlink method (creating .wants directories) works but violates the principle of declarative configuration.
For production systems, follow these guidelines:
- Always use a dedicated target for multiple services
- Keep service units independent of timer activation
- Use
WantedBy
in services rather than manual symlinks - Document the relationship between components
Here's a full implementation for a maintenance system:
# /etc/systemd/system/nightly-maintenance.target
[Unit]
Description=Nightly maintenance tasks
StopWhenUnneeded=yes
# /etc/systemd/system/clean-temp.service
[Unit]
Description=Clean temporary files
WantedBy=nightly-maintenance.target
[Service]
Type=oneshot
ExecStart=/usr/bin/clean-temp.sh
# /etc/systemd/system/rotate-logs.service
[Unit]
Description=Rotate application logs
WantedBy=nightly-maintenance.target
[Service]
Type=oneshot
ExecStart=/usr/bin/logrotate -f /etc/logrotate.d/applications
# /etc/systemd/system/nightly.timer
[Unit]
Description=Nightly maintenance timer
[Timer]
OnCalendar=*-*-* 03:00:00
Unit=nightly-maintenance.target
Persistent=true
[Install]
WantedBy=timers.target
If services aren't triggering as expected:
systemctl list-timers --all
journalctl -u nightly-maintenance.target
systemctl list-dependencies nightly-maintenance.target
When designing systemd timer-service architectures, engineers often need to trigger multiple services from a single timer event. The complexity arises from systemd's dependency management system and the various documented approaches that sometimes conflict.
The most robust solution involves creating a custom target that aggregates multiple services:
# /etc/systemd/system/batch-jobs.target
[Unit]
Description=Target for aggregated batch jobs
Each service should be configured to be "wanted by" our custom target:
# /etc/systemd/system/service1.service
[Unit]
Description=First batch job
[Service]
ExecStart=/usr/local/bin/job1.sh
[Install]
WantedBy=batch-jobs.target
The timer unit activates the target rather than individual services:
# /etc/systemd/system/hourly-jobs.timer
[Unit]
Description=Timer for hourly batch jobs
[Timer]
OnCalendar=hourly
Unit=batch-jobs.target
[Install]
WantedBy=timers.target
The most reliable method combines both Install directives and symlinks:
# After creating units
sudo systemctl enable service1.service
sudo systemctl enable service2.service
sudo mkdir -p /etc/systemd/system/batch-jobs.target.wants
sudo ln -s /etc/systemd/system/service1.service /etc/systemd/system/batch-jobs.target.wants/
sudo ln -s /etc/systemd/system/service2.service /etc/systemd/system/batch-jobs.target.wants/
sudo systemctl enable hourly-jobs.timer
The target unit acts as an abstraction layer that:
- Decouples the timer from specific service implementations
- Allows flexible service addition/removal without timer modifications
- Maintains proper dependency ordering through systemd's native mechanisms
Key verification commands:
systemctl list-dependencies batch-jobs.target
systemctl list-timers --all
journalctl -u hourly-jobs.timer -u batch-jobs.target