Prerequisites
- Basic shell scripting ability
- sudo access on a Linux host running systemd (most modern distros)
What You'll Learn
- Write correct crontab schedule expressions, including special strings like @reboot
- Diagnose why a cron job runs manually but silently fails on schedule
- Create a systemd .service + .timer unit pair as a cron replacement
- Decide when a systemd timer is the better choice over cron
Theory
cronis a daemon that wakes once a minute, reads every user's crontab plus /etc/crontab and /etc/cron.d/*, and executes any job whose five-field schedule matches the current minute. The fields are, in order: minute, hour, day-of-month, month, day-of-week — each accepting a number, a range (1-5), a step (*/15), a list (1,15,30), or a wildcard (*).
Special strings
| String | Equivalent |
|---|---|
@reboot | Run once, at system startup |
@daily | 0 0 * * * |
@hourly | 0 * * * * |
@weekly | 0 0 * * 0 |
Why a cron job that runs manually can fail on schedule
cron executes jobs with a minimal environment — no login shell profile, a bare PATH (often just /usr/bin:/bin), and no working directory guarantee. A script that relies on a tool installed outside that PATH, or a relative file path, works fine when you run it interactively (your shell has a full environment) and fails silently under cron.
systemd timers as a modern alternative
A systemd timer unit (.timer) is paired with a service unit of the same base name (.service) that defines the actual work. Timers support calendar expressions (OnCalendar=Mon..Fri 09:00) and monotonic expressions (OnBootSec=15min), and — unlike cron — every run is logged to the journal, missed runs can be caught up with Persistent=true, and a timer can depend on other units being ready first.
Architecture
cron polls every minute against crontab entries; systemd timers are event-driven and journal-logged
Hands-On Lab
Part 1 — a cron job that backs up a directory nightly:
crontab -e
# Add this line — runs at 02:30 every day, full paths only:
30 2 * * * /usr/bin/tar -czf /backups/home-$(date +\%Y\%m\%d).tar.gz /home/deploy 2>> /var/log/backup-cron.log
crontab -l # verify it saved
grep CRON /var/log/syslog # confirm cron actually triggered it (Debian/Ubuntu)Part 2 — the same job as a systemd timer:
sudo tee /etc/systemd/system/home-backup.service <<'EOF'
[Unit]
Description=Nightly home directory backup
[Service]
Type=oneshot
# ExecStart isn't run through a shell, so command substitution needs an
# explicit shell — and a literal "%" must be escaped as "%%" since systemd
# treats a single "%" as the start of its own specifier syntax (e.g. %n, %i).
ExecStart=/bin/bash -c 'tar -czf /backups/home-$(date +%%Y%%m%%d).tar.gz /home/deploy'
EOF
sudo tee /etc/systemd/system/home-backup.timer <<'EOF'
[Unit]
Description=Run home-backup.service nightly at 02:30
[Timer]
OnCalendar=*-*-* 02:30:00
Persistent=true
[Install]
WantedBy=timers.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable --now home-backup.timer
systemctl list-timers --all # confirm next scheduled run
journalctl -u home-backup.service # see every past run's outputBest Practices
Always use full paths in cron
Never rely on cron's minimal PATH. Use full paths for both the command and any files it touches, or explicitly setPATH= at the top of the crontab.Redirect output, don't discard it
A cron job with no output redirection has its stdout/stderr mailed to the local user by default (if a mail system is even configured) — explicitly redirect to a log file so failures are actually visible.Common Mistakes
Testing a script manually, then trusting cron blindly
A script that works when you run it interactively can fail under cron purely due to environment differences (PATH, HOME, missing shell profile). Always test withenv -i /path/to/script.shto simulate cron's bare environment before trusting it's cron-safe.Forgetting daemon-reload after editing a unit file
systemd caches unit files in memory. Editing/etc/systemd/system/*.timer or .service has no effect until systemctl daemon-reload— a very common "I changed it but nothing happened" trap.Troubleshooting
Cron job doesn't run at all: confirm the cron daemon is active (systemctl status cron or crond), check /var/log/syslog or journalctl -u cronfor a line showing the job was picked up, and verify the crontab's owner has permission to run under /etc/cron.allow/cron.deny if configured.
systemd timer shows as active but the service never fires: run systemctl list-timers and compare the NEXT column against the current time — a common cause is an OnCalendarexpression that's syntactically valid but not what you intended; validate it directly with systemd-analyze calendar "Mon..Fri 09:00".
Interview Questions
Cheat Sheet
Processes & Services
ps aux | grep nginxFind running processes matching 'nginx'top / htopLive CPU/memory usage per processkill -9 PIDForce-kill a process by PIDsystemctl status sshdShow a service's current statussystemctl restart nginxRestart a systemd-managed servicesystemctl enable --now nginxEnable a service on boot and start it nowjournalctl -u nginx -fFollow live logs for a specific serviceFrequently Asked Questions
Summary
cron polls a five-field schedule every minute against a minimal execution environment — always use full paths and redirect output. systemd timers pair a .timer with a .service, support calendar and monotonic schedules, log every run to the journal, and can catch up missed runs — the better default on any systemd-based host.