July 8, 2025

The FreeBSD periodic utility is a built-in system to schedule and run regular (daily, weekly, monthly) maintenance jobs in the form of shell scripts. They include system health checks, security audits, and cleanup jobs. Custom jobs integrate into the existing framework thanks to periodic’s modular structure.

In this post, we look at how to use the periodic scripts provided by the system and how to integrate a script of our own.

Locations of periodic scripts

The following directories contain scripts intended to run via the periodic system:

  • /etc/periodic/daily, /etc/periodic/weekly, etc/periodic/monthly: scripts that should run at specific time periods (i.e. weekly on a weekly basis).
  • /etc/periodic/security: Security-related checks for parts of the system like activated firewalls or login failures.
  • /usr/local/etc/periodic/daily, /usr/local/etc/periodic/weekly: third-party scripts that typically come from ports or packages and are time based. For example, to rotate nginx log files or backup pkg files.
  • /usr/local/etc/periodic/security: Security checks that come from third-party sources like ports or packages. A typical example is a script to run pkg audit.

The scheduling itself happens due to the following three lines in /etc/crontab:

1       3       *       *       *       root    periodic daily
15      4       *       *       6       root    periodic weekly
30      5       1       *       *       root    periodic monthly

To configure the periodic system itself and which scripts it should run, FreeBSD provides a separate configuration file /etc/periodic.conf. By default, the file is empty or does not even exist. The /etc/defaults/ directory provides a well documented example file. More details are in the periodic.conf(5) man page.

For example, to activate the daily backup of the /etc/passwd and /etc/group files, located in the script /etc/periodic/200.backup-passwd, add this line to /etc/periodic.conf:

daily_backup_passwd_enable="YES"

The prefix number (200 in our example) does not need to be provided. The number deals with running the scripts in that order when multiple such scripts from the same category (here: daily) get called. List all the scripts that should run line by line in this file.

When those scripts execute, any output produced is mailed to the system administrator account (root). To redirect the output to a file in /var/log instead add these lines for each of the daily, weekly, and monthly scripts, respectively:

daily_output=/var/log/daily.log
weekly_output=/var/log/weekly.log
monthly_output=/var/log/monthly.log

The filenames may be arbitrarily chosen, as long as they don’t use the same name as a different log file already present. With this approach, these logs get rotated when they become too big. Lines in /etc/newsyslog.conf take care of this rotation for the three filenames used above.

Adding a custom periodic script

The custom script below checks if any ZFS pool found in the system has a capacity of 80 percent. If that is the case, a warning message provides the current value above this threshold and the pool it concerns:

#!/bin/sh

if [ -r /etc/defaults/periodic.conf ]
then
  . /etc/defaults/periodic.conf
  source_periodic_confs
fi

: ${zfs_pool_usage_enable:="YES"}
: ${zfs_pool_usage_threshold:=80}

[ "$zfs_pool_usage_enable" = "YES" ] || exit 0

echo ""
echo "Checking ZFS pool usage (threshold: ${zfs_pool_usage_threshold}%)..."

zpool list -H -o name,capacity | while read -r pool usage; do
    percent=${usage%%%}  # remove the '%' sign
    if [ "${percent}" -ge "${zfs_pool_usage_threshold}" ]; then
        echo "WARNING: ZFS pool '${pool}' is ${percent}% full!"
    else
        echo "OK: ZFS pool '${pool}' is below capacity threshold (${percent}%)."
    fi
done

exit 0

Next, make the script executable using elevated privileges:

chmod +x /etc/periodic/daily/405.zfs_pool_usage

We activate the script from the global configuration file /etc/periodic.conf and lower the threshold to 75 percent:

daily_show_success="YES"
zfs_pool_usage_enable="YES"
zfs_pool_usage_threshold="75"

If the zfs_pool_usage_threshold line is missing the script will use the builtin value of 80 percent. The daily_show_success is there to see script output in the log file. Having this value set to NO during tests will cause a lot of head scratching as you will not see any log output, whereas manual script execution works fine.

To test the script, run this command with root privileges:

periodic daily

This may take a while as it will go through each daily script to see which needs to run (because we configured it to do so). At the end, you will have new lines at the end of your /var/log/daily.log with the script outputs.

Here is an example output from one of my systems:

Checking ZFS pool usage (threshold: 75%)...
OK: ZFS pool 'data' is below capacity threshold (6%).
OK: ZFS pool 'zroot' is below capacity threshold (27%).

Some advice for your own scripts:

  • Ensure the scripts do not run too long
  • Use proper exit codes (i.e. 0 upon successful execution, nonzero for failures)
  • Reduce script output to essential messages to avoid logs growing too fast
  • Bear in mind that scripts run non-interactively; do not wait for user input or run commands that require it
  • Add error handling to catch edge cases and communicate these states; do not let the script fail silently
  • Test your scripts by calling them manually and separately via calls to periodic (see above)

Summary

With these tips in hand, you should be able to add all kinds of useful tasks to your system. Look in the directories listed above to see what kind of functionality is already available to and avoid reinventing the wheel. Also, when installing ports, see if they provide any periodic scripts under /usr/local/etc/periodic. They are provided for a reason and will fit nicely into your existing scheduled tasks.

Get Involved!

We’re dropping new posts — and videos — for technical topics regularly. So make sure you’re subscribed to the YouTube channel and following this feed in your favorite RSS reader. There’s also the newsletter, if you’d like to receive updates by email.

We’d like this content series to be interactive too — so what would you like to see us cover? What FreeBSD questions can we help you tackle?  Get in touch with your ideas.


Contributed by Benedict Reuschling