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 runpkg 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