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.