Quick Start: Run Your First Task in 5 Minutes
Need a quick deployment? Here is how to create a simple script that logs every minute so you can see the power of systemd firsthand.
Step 1: Prepare the Script
# Create a directory to manage scripts
sudo mkdir -p /opt/scripts
# Create a simple script file
sudo bash -c 'cat <<EOF > /opt/scripts/hello-timer.sh
#!/bin/bash
echo "Task executed at: \$(date)"
EOF'
# Grant execution permissions
sudo chmod +x /opt/scripts/hello-timer.sh
Step 2: Define the Service
This file describes the specific task. Create the file at /etc/systemd/system/hello-timer.service:
[Unit]
Description=Periodic logging script
[Service]
Type=oneshot
ExecStart=/opt/scripts/hello-timer.sh
Step 3: Set up the Timer
This file acts as the timer. Create the file /etc/systemd/system/hello-timer.timer (the name must match the service file):
[Unit]
Description=Run hello-timer every minute
[Timer]
OnCalendar=*:*:00
Unit=hello-timer.service
[Install]
WantedBy=timers.target
Enable it so the system can start working:
sudo systemctl daemon-reload
sudo systemctl enable --now hello-timer.timer
To check, use the command: journalctl -u hello-timer.service. Logs will appear regularly every minute.
Why I Switched from Cron to systemd Timers
Crontab is very convenient for small personal tasks. However, when managing an Ubuntu 22.04 server cluster with 4GB of RAM and dozens of simultaneous backup jobs, Cron made me lose control.
The biggest issue is Logging. With Cron, script errors often silently disappear. You have to manually redirect logs using >> /var/log/app.log 2>&1. With systemd, everything is pushed directly to journald. You only need a single command to view the entire execution history and error traces.
Next is Dependency management. Do you want a script to run only after MySQL has started or the network is up? Cron cannot do this naturally. Systemd is different; it manages relationships between services extremely tightly.
Particularly resource limits. I once faced a situation where a backup script consumed 100% of the CPU, crashing the web server. With systemd, I can immediately set MemoryLimit=500M and CPUQuota=30% in the service file to protect the system.
Deep Dive into systemd Timer Structure
The secret lies in the separation between the Service (what to do) and the Timer (when to do it).
1. Service File (.service)
Focuses on how the script is executed:
Type=oneshot: Suitable for jobs that run and then exit, such as log cleaning or database backups.User=www-data: Run the script under a specific user instead of root to reduce security risks.
2. Timer File (.timer)
Where flexible timing is configured:
- Monotonic timers: Measure time from boot or from the last run. Example:
OnUnitActiveSec=1h(runs every hour). - Realtime timers (OnCalendar): Run according to a schedule like Cron but are more human-readable.
Some common OnCalendar examples:
*-*-* 03:00:00: Exactly 3 AM every day.Mon *-*-* 00:00:00: Midnight every Monday.*:0/20: Every 20 minutes.
Smart Error Handling with Automatic Retries
This is a “killer feature.” If a cloud data sync script fails due to a spotty network, systemd will automatically retry for you.
Add this configuration to the .service file:
[Service]
ExecStart=/opt/scripts/sync-cloud.sh
Restart=on-failure
RestartSec=60s
StartLimitBurst=5
This configuration requires: If it fails, wait 60 seconds and try again. It will only truly stop and send an alert after 5 consecutive failures.
Handy Management Commands
To control running timers, I often use these three commands:
systemctl list-timers: View the job list and see exactly when the next job will run.systemctl status task.timer: Check the status of the timer.journalctl -u task.service -f: Follow logs in real-time for quick debugging.
Real-world Experience to Avoid Headaches
After many troubleshooting sessions in production environments, here are three important notes:
- Use absolute paths: Systemd doesn’t know where
python3is located. Always write/usr/bin/python3 /path/to/script.py. - Leverage the Persistent feature: If the server is off at 2 AM (backup time), when it turns back on at 7 AM, a timer with
Persistent=truewill catch up and run immediately. Cron would simply skip that job by default. - Keep file names consistent: Always name your
app-sync.serviceandapp-sync.timeridentically for easier management and so the system automatically understands the link.
While creating two files might seem more cumbersome than a single Crontab line, the transparency and resource control that systemd Timers provide are absolutely worth it to optimize Linux server performance.

