The Unix Way ■ Episode 12
You have inherited a server. Congratulations. It runs seventeen cron jobs. Three were written by someone who left in 2019. One backs up a database that was decommissioned last March. Two conflict with each other at 02:15 every Sunday. Nobody knows what the remaining eleven do, because cron does not tell you whether a job succeeded. It tells you it ran. Marvellous distinction, that.
cron is a clock with a trigger. It fires and forgets. Scatter your jobs across per-user crontabs and root's crontab, and within six months you have a system held together by faith, habit, and the vague hope that no one touches it. This is not a theoretical scenario. This is every production server one has ever inherited.
The BSDs looked at this and asked a rather sensible question: what if the system told you what happened?
FreeBSD: periodic(8)
periodic(8) is a framework. Not a scheduler, not a daemon, not a service. A framework that sits on top of cron and provides structure, configuration, and output management for recurring system tasks. FreeBSD built it in the 1990s. It has worked without drama since.
The architecture is straightforward. Scripts live in directories:
/etc/periodic/daily/
/etc/periodic/weekly/
/etc/periodic/monthly/
/etc/periodic/security/
Each script is a self-contained shell script that performs one task and exits with a status code:
One configuration file controls the entire system:
# /etc/periodic.conf
daily_clean_tmps_enable="YES"
daily_backup_passwd_enable="YES"
daily_status_security_enable="YES"
daily_accounting_enable="YES"
daily_show_success="NO"
daily_show_info="YES"
daily_show_badconfig="YES"
daily_output="/var/log/daily.log"
Enable a task: set it to "YES". Disable it:
"NO". Change the output destination: set
daily_output to a file path or an email address.
One file. Every task. Every setting. The pattern is identical
to rc.conf: system defaults are never modified,
local changes are applied on top.
The output is collected from all scripts, filtered by severity,
and delivered as a single report. If daily_output
is an email address, you receive one email every morning with
the complete state of your system: which temp files were cleaned,
whether the password database was backed up, what the security
check found, and whether anything went wrong. You read one email
with your morning coffee and know the state of the machine.
Adding a custom task is trivial: write a shell script, make it
executable, drop it into /etc/periodic/daily/. That
is the entire API. No registration, no configuration reload, no
daemon restart. The framework discovers it on the next run. One
does not recall the last time an API consisted of "put a file
somewhere."
newsyslog(8)
already knows about /var/log/daily.log,
/var/log/weekly.log, and
/var/log/monthly.log. The logging of your
maintenance framework is itself maintained. One does appreciate
the recursion.
cron still does the scheduling. Three lines in
/etc/crontab:
0 2 * * * root periodic daily
0 3 * * 6 root periodic weekly
0 5 1 * * root periodic monthly
cron triggers periodic. periodic runs the scripts. The scripts report their status. The framework collects the output. The administrator reads one email. The separation of concerns is, one must note, rather elegant.
OpenBSD: daily(8)
OpenBSD
takes a characteristically minimal approach. Three shell scripts
ship with the base system: /etc/daily,
/etc/weekly, and /etc/monthly. These
are system scripts. You never modify them.
Your additions go into /etc/daily.local,
/etc/weekly.local, and
/etc/monthly.local. These local scripts run first,
before the system scripts, which makes it convenient to define
variables, perform cleanup, or prepare state that the system
scripts depend on.
The daily script performs a comprehensive set of checks: removes
scratch files from /tmp, purges accounting records,
checks daemon status (lists any daemons enabled in
rc.conf.local that are not actually running),
reports which file systems need to be dumped, runs the
security(8)
check script, and optionally backs up the root file system to
/altroot.
The rather splendid part: the daily script reports processes
killed by pledge(2) and unveil(2)
violations. Software that attempted to exceed its declared
capabilities or access files it had no business accessing.
Security is not a product you install. It is not an agent you
deploy. It is a shell script that runs every morning and tells
you who misbehaved. Quite civilised.
Linux: systemd timers
Linux offers
systemd timers
as the modern alternative to cron. Each scheduled task requires
two files: a .service unit file defining what to
run, and a .timer unit file defining when to run
it.
# /etc/systemd/system/cleanup.service
[Unit]
Description=Daily cleanup
[Service]
Type=oneshot
ExecStart=/usr/local/bin/cleanup.sh
# /etc/systemd/system/cleanup.timer
[Unit]
Description=Run cleanup daily
[Timer]
OnCalendar=daily
Persistent=true
[Install]
WantedBy=timers.target
Then enable and start it:
systemctl daemon-reload
systemctl enable --now cleanup.timer
For ten maintenance tasks, this produces twenty files and ten
reload-and-enable sequences. The output goes to the journal,
retrievable via journalctl -u cleanup.service.
There is no collected daily report. There is no severity
filtering. There is no single email summarising the state of
the system. Each task lives in its own universe.
systemd timers do offer features that cron does not: monotonic timers (relative to boot), persistent timers (catch up missed runs), calendar expressions with second-level granularity, and dependency ordering. These are genuine capabilities. Whether the complexity is justified for "run this script every morning" is, one suspects, a matter of taste. And file count tolerance.
The Point
cron tells you when. periodic tells you what happened.
The difference between a scheduler and a maintenance framework is the difference between "the job ran" and "the job ran, succeeded, found nothing unusual, and here is the proof." One of those lets you sleep. The other requires you to check.
FreeBSD built a framework: structured directories, a single configuration file, severity-coded output, collected daily reports. OpenBSD built simplicity: three scripts, three local overrides, security baked into the daily routine. Linux built a general-purpose process management system and asked it to also handle cron jobs.
All three work. The BSDs understood, decades ago, that the problem was never scheduling. It was accountability. One configuration file. One email. One morning coffee. Rather civilised, really.
cron tells you when. periodic tells you what happened. FreeBSD: one config file, severity-coded output, daily email report. OpenBSD: pledge/unveil violation reports every morning. Linux: twenty files for ten tasks. The BSDs understood that the problem was never scheduling. It was accountability.