Systemd Timers vs. Cron Jobs
I had to run a task on a Linux VM that would send a POST request to a local webserver with an auth header. It needed to happen on Tuesdays at 6am Pacific. I thought I’d use a cron job, but a systemd timer turned out to be the better fit.
Cron jobs
Cron will execute commands in a vacuum. It’s not aware of any dependencies or where logs should be placed. You can force it to act how you want, but you’ll have to write those checks yourself.
Another issue is time zones. Cron uses the local time on your system, and my VM, like most cloud VMs, uses UTC. So if I want to run a job at 6am Pacific, I’d have to schedule it for 14:00 during the winter and 13:00 in the summer. Some cron daemons allow you to set a CRON_TZ environment variable, but not all of them do.
Here’s my cron job:
# 6am PST, but 7am once daylight saving time starts.
0 14 * * TUE . /etc/secrets/env && curl --fail -X POST -H "X-Auth-Token: $TOKEN" http://localhost:8080/task
You’ll notice that I have to source my secret in the job, and there’s nothing to ensure my webserver is running.
Systemd timers
Systemd timers allow you to run a service, which gives you the benefits of systemd for logging and making sure your dependencies are running. Timers are also time zone aware, so I can run my job at 6am Pacific and not worry about daylight saving time.
Here’s my job using systemd:
# /etc/systemd/system/weekly-task.service
[Unit]
Description=Send scheduled POST request
After=local-web.service
Requires=local-web.service
[Service]
Type=oneshot
EnvironmentFile=/etc/secrets/env
ExecStart=/usr/bin/curl --fail -X POST \
-H "X-Auth-Token: ${TOKEN}" \
http://localhost:8080/task
And my timer:
# /etc/systemd/system/weekly-task.timer
[Unit]
Description=Run weekly task every Tuesday at 6am Pacific
[Timer]
OnCalendar=Tue 06:00 America/Los_Angeles
Persistent=true
[Install]
WantedBy=timers.target
Then you can reload systemd and enable the timer:
sudo systemctl daemon-reload
sudo systemctl enable --now weekly-task.timer
The timer just schedules the job, and the service is where all the magic happens. I can use Requires and After to make sure my webserver is up and running. I also pass my token through an environment file. Using --fail will cause curl to return a non-zero exit code on HTTP errors, which will allow me to see it in journalctl -u weekly-task as a failed unit. If my VM is down on a Tuesday, Persistent=true will run the job when the machine is back online.
Cron can be used for simple tasks, but if you need to run something in a certain time zone or have dependencies, timers can help.