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.