Systemd Timers vs. Cron Jobs
I needed to schedule a task on a Linux VM that sends a POST request to a local webserver with an auth header, every Tuesday at 6am Pacific. My first impulse was to use a cron job, but in this case a systemd timer ended up being the cleaner choice.
Cron jobs
Cron runs commands in isolation. It doesn’t know whether the service you’re calling is up or where logs should go. You can make it behave, but you’re usually stitching together dependency checks and log redirection on your own.
Timezones are another sore spot. Cron schedules jobs in the system’s local
time, and like most cloud VMs, mine runs on UTC. 6am Pacific is 14:00 UTC in
the winter and 13:00 UTC in the summer, so any fixed cron schedule is off by
an hour for half the year. Some implementations, like cronie, support a
CRON_TZ variable for this, but not all of them do.
Here’s what my task looks like as a 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
It works, but the secret has to be sourced inline, and nothing makes sure the webserver is running first.
Systemd timers
Systemd timers trigger a service, so the job gets the same dependency handling and journal logging as anything else systemd manages. Timers also understand timezones, so I can schedule in Pacific time and not worry about DST.
Here’s the same task 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 the 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 reload systemd and enable the timer:
sudo systemctl daemon-reload
sudo systemctl enable --now weekly-task.timer
The timer only schedules the work, and the service defines the actual
behavior. Requires and After start the webserver first if it isn’t already
running, and the token comes in through the environment file. --fail makes
curl exit non-zero on an HTTP error, so a bad response will show up as a failed
unit in journalctl -u weekly-task. And if the VM is off on a Tuesday
morning, Persistent=true runs the missed job as soon as it boots.
Cron is still fine for quick one-liners. But when a job depends on another service or a specific timezone, timers handle it without any extra glue.