systemd timer on Linux VPS

Using systemd Timer Instead of Cron on Linux VPS

Updated October 15, 2026 · 8 min read

Most Linux administrators learned cron first — it is simple, reliable, and universally available. However, systemd timers offer significant advantages on modern VPS instances that already run systemd: integrated logging through journald, dependency ordering, and the ability to catch up on missed runs after reboots.

cron vs systemd timer: Key Differences

Featurecronsystemd timer
LoggingMust redirect stdout manuallyjournalctl shows all output
Catch-up after rebootNot supportedSupported via Persistent=true
DependenciesNoneAfter=network.target, etc.
Relative timingNot supportedOnBootSec, OnUnitActiveSec
Temporary disableMust edit crontabsystemctl disable --now

Components of a systemd Timer

Each timer consists of two files placed in /etc/systemd/system/:

Creating Your First systemd Timer

Example: run a backup script every day at 02:00.

1. Create the service unit

sudo nano /etc/systemd/system/daily-backup.service
[Unit]
Description=Daily backup script
After=network.target

[Service]
Type=oneshot
User=root
ExecStart=/usr/local/bin/backup.sh
StandardOutput=journal
StandardError=journal
Note: Type=oneshot is correct for tasks that run once and exit — equivalent to how cron works. Use simple only for long-running daemons.

2. Create the timer unit

sudo nano /etc/systemd/system/daily-backup.timer
[Unit]
Description=Run daily-backup.service every day at 02:00

[Timer]
OnCalendar=*-*-* 02:00:00
Persistent=true
RandomizedDelaySec=300

[Install]
WantedBy=timers.target
Tip: Persistent=true ensures that if the server was off at the scheduled time, the job runs immediately after boot — preventing silent backup failures during maintenance windows.

3. Enable and start the timer

# Reload systemd to pick up new unit files
sudo systemctl daemon-reload

# Enable timer to start on boot and start it now
sudo systemctl enable --now daily-backup.timer

# Check timer status
sudo systemctl status daily-backup.timer

Common OnCalendar Expressions

ScheduleOnCalendar value
Every hourhourly
Daily at 03:00*-*-* 03:00:00
Every Monday at 09:00Mon *-*-* 09:00:00
First of month*-*-01 00:00:00
Every 5 minutes*:0/5
Every 30 secondsOnUnitActiveSec=30s

Boot-relative Timers (OnBootSec)

Use OnBootSec when you want timing relative to system boot rather than wall-clock time:

[Timer]
OnBootSec=5min
OnUnitActiveSec=1h

This runs the service 5 minutes after boot, then repeats every hour thereafter.

User-level Timers (No sudo Required)

Timers can run under a regular user account without root privileges:

# Create user systemd directory
mkdir -p ~/.config/systemd/user/

# Create service
nano ~/.config/systemd/user/myapp-task.service
[Unit]
Description=My app periodic task

[Service]
Type=oneshot
ExecStart=/home/user/scripts/run-task.sh
nano ~/.config/systemd/user/myapp-task.timer
[Unit]
Description=Run myapp-task every 6 hours

[Timer]
OnCalendar=*-*-* 00/6:00:00
Persistent=true

[Install]
WantedBy=timers.target
# Enable without sudo
systemctl --user daemon-reload
systemctl --user enable --now myapp-task.timer
systemctl --user list-timers
Note: User timers only run while the user is logged in unless you enable lingering: sudo loginctl enable-linger username

Listing All Timers

# List all system timers
systemctl list-timers --all

# List only active timers
systemctl list-timers

# List user-level timers
systemctl --user list-timers

Verify That systemd Timer Is Working Correctly

# Check timer status
systemctl status daily-backup.timer

# Check when service last ran successfully
systemctl status daily-backup.service

# View service logs
journalctl -u daily-backup.service -n 50

# View logs from the past 24 hours
journalctl -u daily-backup.service --since "24 hours ago"

# Manually trigger the service to test it immediately
systemctl start daily-backup.service
Tip: Run systemd-analyze calendar "*-*-* 02:00:00" to validate your calendar expression and see the next scheduled trigger time.

Troubleshooting Common Errors

Error: "Failed to enable unit: Unit file daily-backup.timer does not exist"

systemd has not loaded the new unit file, or the filename is incorrect.

# Always reload after creating unit files
sudo systemctl daemon-reload

# Confirm the file exists with correct name
ls -la /etc/systemd/system/daily-backup.*

# Validate unit file syntax
systemd-analyze verify /etc/systemd/system/daily-backup.timer

Error: "daily-backup.service: Main process exited, code=exited, status=127"

Exit code 127 means the command was not found — the script path is wrong or the file lacks execute permission.

# Verify script exists and is executable
ls -la /usr/local/bin/backup.sh
chmod +x /usr/local/bin/backup.sh

# ExecStart must use an absolute path
# Wrong: ExecStart=backup.sh
# Correct: ExecStart=/usr/local/bin/backup.sh

Error: Timer does not run at scheduled time

Check that the timer is active and the system clock matches the expected timezone.

# Confirm timer is active
systemctl is-active daily-backup.timer

# Check system time and timezone
timedatectl status

# Show next trigger time
systemctl list-timers daily-backup.timer

Error: "daily-backup.service: Skipped due to already running"

A previous run is still in progress when the timer fires again.

# Ensure Type=oneshot and RemainAfterExit=no
[Service]
Type=oneshot
RemainAfterExit=no

# Or reset a failed/stale service state
systemctl reset-failed daily-backup.service

Error: "Condition check resulted in daily-backup.service being skipped"

A ConditionPathExists or similar condition directive is not satisfied.

# Review conditions defined in the service
systemctl cat daily-backup.service

# Check logs for which condition failed
journalctl -u daily-backup.service -n 20 --no-pager

Frequently Asked Questions

Should I switch from cron to systemd timers on my VPS?

On modern Ubuntu, Debian, or CentOS VPS instances, systemd timers are generally the better choice: logs are automatically captured in journald without redirection, and Persistent=true prevents missed runs during reboots. Stick with cron if you need portability across systems without systemd or prefer the concise syntax for simple schedules.

How do I view the output of a job run through a systemd timer?

Use journalctl -u servicename.service — no log file configuration required. Add -f for live output, or --since "1 hour ago" to filter by time range. The service and its timer share the same unit name for easy filtering.

Can a systemd timer run multiple commands?

With Type=oneshot, you can add multiple ExecStart= lines in a single service unit and they execute sequentially. Alternatively, point ExecStart at a shell script that contains all the commands — this keeps the unit file clean and makes all output visible in a single journal stream.