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\n# m h dom mon dow  command\n15 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\n[Unit]\nDescription=Site maintenance\n\n[Service]\nType=oneshot\nUser=www-data\nWorkingDirectory=/var/www/site\nExecStart=/usr/bin/php cron.php
# /etc/systemd/system/site-cron.timer\n[Unit]\nDescription=Run site maintenance nightly\n\n[Timer]\nOnCalendar=*-*-* 03:15:00\nPersistent=true\nRandomizedDelaySec=300\n\n[Install]\nWantedBy=timers.target
sudo systemctl daemon-reload\nsudo systemctl enable --now site-cron.timer\nsystemctl list-timers --all\njournalctl -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.