Both run something on a schedule. The difference shows up when a job fails, when a job runs long, or when the machine was off at the moment it was due.

Cron, and its two traps

crontab -e
# m h dom mon dow  command
15 3 * * *  /usr/bin/php /var/www/site/cron.php >> /var/log/site-cron.log 2>&1
  • The environment is almost empty. PATH is minimal and your shell profile is not read, so a command that works when you type it fails here. Use absolute paths.
  • Output is mail, not a log. Without the redirect above, the output goes to a local mailbox nobody reads and the job appears to have run silently.

If a cron job never seems to run at all, the checklist in cron jobs that never run covers the rest.

A timer, and what it adds

# /etc/systemd/system/site-cron.service
[Unit]
Description=Site maintenance

[Service]
Type=oneshot
User=www-data
WorkingDirectory=/var/www/site
ExecStart=/usr/bin/php cron.php
# /etc/systemd/system/site-cron.timer
[Unit]
Description=Run site maintenance nightly

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

[Install]
WantedBy=timers.target
sudo systemctl daemon-reload
sudo systemctl enable --now site-cron.timer
systemctl list-timers --all
journalctl -u site-cron -n 50

What the timer gives you

  • Logs in the journal, with the exit status, per run.
  • No overlap - a run still going when the next is due does not start a second copy.
  • Persistent=true - a run missed while the machine was off happens at the next boot.
  • RandomizedDelaySec - a fleet of servers does not all hit the same API at 03:15:00 exactly.

Which to use

A one-line job on one machine: cron, and do not overthink it. Anything you need to see the history of, anything that must not run twice, and anything that matters if it is missed: a timer.

Do not schedule the same job in both. It is a common result of migrating half-way, and it doubles every run - including the ones that send mail or charge cards.