DEV Community

Vivian Voss
Vivian Voss

Posted on • Originally published at vivianvoss.net

periodic

A young developer with cat-ear headphones, stands between two server racks with her arms crossed. The left rack is buried under colourful sticky notes, tangled cables, and alarm clocks: cron job chaos. The right rack is clean with a single green status light and an email icon above it: one daily report, everything accounted for.

The Unix Way — Episode 12

The problem: you need scheduled maintenance. Disk cleanup, log rotation, security audits, database backups, certificate renewal checks. Every server has them. Every sysadmin has cron jobs for them. And every sysadmin has, at some point, discovered a critical cron job that died silently three months ago because nobody checked the output.

cron is a scheduler. It runs commands at specified times. It does not care what those commands do, whether they succeed, whether they produce output, or whether anyone reads that output. It is a clock with a trigger. Nothing more.

The real problem is not scheduling. Scheduling is trivial. The problem is knowing what happened. And cron, by design, does not answer that question.

Scatter your jobs across per-user crontabs and root's crontab, and within six months you have a system where nobody knows what runs when, what produces output, what has been silently failing, or who added the job in the first place. This is not a theoretical scenario. This is every production server one has ever inherited.

FreeBSD: periodic(8)

FreeBSD solved this problem in the 1990s with periodic(8), a framework that sits on top of cron and provides structure, configuration, and output management for recurring system tasks.

The architecture is straightforward. Scripts live in directories:

/etc/periodic/daily/
/etc/periodic/weekly/
/etc/periodic/monthly/
/etc/periodic/security/
Enter fullscreen mode Exit fullscreen mode

Each script is a self-contained shell script that performs one task and exits with a status code:

Exit Code Meaning Output Handling
0 Nothing notable Shown only if daily_show_success="YES"
1 Notable information Shown only if daily_show_info="YES"
2 Configuration warning Shown only if daily_show_badconfig="YES"
>2 Critical, always shown Cannot be suppressed

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"
Enter fullscreen mode Exit fullscreen mode

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 default configuration in /etc/defaults/periodic.conf documents every available option. Your overrides go in /etc/periodic.conf. 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.

newsyslog(8) already knows about /var/log/daily.log, /var/log/weekly.log, and /var/log/monthly.log. If those files exist, they are rotated automatically. 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
Enter fullscreen mode Exit fullscreen mode

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 and junk files from /tmp
  • Purges accounting records and reports processes killed by pledge(2) or unveil(2) violations
  • 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
  • Optionally backs up the root file system to /altroot
  • Optionally runs calendar(1) and file system checks

The security integration is notable. OpenBSD's daily maintenance does not merely check disk space and rotate logs. It reports on pledge and unveil violations: processes that attempted to exceed their declared capabilities or access files outside their declared scope. Security is not an add-on, not an agent, not a third-party tool. It is a shell script that runs every morning and tells you who misbehaved.

Output is mailed to root. The OpenBSD handbook strongly recommends that root's mail is aliased to a real user account. Otherwise, root's mail simply accumulates until the partition runs out of space. One does note the pragmatism.

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.

A daily cleanup requires a service file:

# /etc/systemd/system/cleanup.service
[Unit]
Description=Daily cleanup

[Service]
Type=oneshot
ExecStart=/usr/local/bin/cleanup.sh
Enter fullscreen mode Exit fullscreen mode

And a timer file:

# /etc/systemd/system/cleanup.timer
[Unit]
Description=Run cleanup daily

[Timer]
OnCalendar=daily
Persistent=true

[Install]
WantedBy=timers.target
Enter fullscreen mode Exit fullscreen mode

Then enable and start it:

systemctl daemon-reload
systemctl enable --now cleanup.timer
Enter fullscreen mode Exit fullscreen mode

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 via the unit system. 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.

Read the full article on vivianvoss.net →


By Vivian Voss — System Architect & Software Developer. Follow me on LinkedIn for daily technical writing.

Top comments (0)