pfSense Custom Scripts and Scheduled Tasks
pfSense is built on FreeBSD and provides a full command-line environment, a PHP interpreter, and startup mechanisms. This allows the creation of custom scripts for tasks not covered by the web interface: automated remote backup, log rotation, monitoring checks, and certificate renewal. The Shellcmd and Cron packages are available for managing scripts and scheduled tasks.
Executing Commands via the Web Interface
pfSense provides two tools for interacting with the system through a browser without SSH access.
Diagnostics - Command Prompt
The Diagnostics > Command Prompt page allows execution of shell commands and PHP code directly from the web interface.
Execute Shell Command runs arbitrary FreeBSD commands:
# Check disk space
df -h
# View running processes
ps aux | grep openvpn
# Count established connections
netstat -an | grep ESTABLISHED | wc -lExecute PHP Command runs PHP code with access to pfSense functions:
require_once("config.inc");
require_once("interfaces.inc");
// Display hostname
echo $config['system']['hostname'] . "." . $config['system']['domain'];Diagnostics - Edit File
The Diagnostics > Edit File page provides a text editor for viewing and modifying files on the filesystem. Useful for editing configuration files without SSH access.
Shellcmd Package - Boot Commands
The Shellcmd package allows defining commands that execute during pfSense boot. It replaces the need to manually edit config.xml to add <shellcmd> tags.
Installation
Navigate to System > Package Manager > Available Packages, search for Shellcmd, and install it.
After installation, configuration is available under Services > Shellcmd.
Command Types
| Type | Execution Timing | Usage |
|---|---|---|
| shellcmd | End of boot process | Primary startup tasks |
| earlyshellcmd | Start of boot process (before services) | Kernel module loading, filesystem mounting |
| afterfilterchangeshellcmd | After every firewall rule change | Dynamic rules, integrations |
Configuration Example
Command: /usr/local/bin/custom-startup.sh
Type: shellcmd
Description: Custom startup scriptCommands can also be added directly to config.xml:
<shellcmd>/usr/local/bin/custom-startup.sh</shellcmd>
<earlyshellcmd>kldload if_tap</earlyshellcmd>Best Practices
- Use absolute paths to executables
- Scripts must have executable permissions (
chmod +x) - Run long-running operations in the background with
& - Redirect output to a log for debugging:
/usr/local/bin/script.sh > /var/log/custom-startup.log 2>&1 &
Cron Package - Scheduled Tasks
The Cron package provides a web interface for managing cron jobs. pfSense uses the standard FreeBSD cron, but the package adds graphical management under Services > Cron.
Installation
Navigate to System > Package Manager > Available Packages, search for Cron, and install it.
Schedule Format
* * * * * command
| | | | |
| | | | +--- Day of week (0-7, 0 and 7 = Sunday)
| | | +-------- Month (1-12)
| | +------------- Day of month (1-31)
| +------------------ Hour (0-23)
+----------------------- Minute (0-59)Task Examples
| Task | Schedule | Command |
|---|---|---|
| Daily config backup at 2:00 AM | 0 2 * * * | /usr/local/bin/backup-config.sh |
| Restart OpenVPN every 6 hours | 0 */6 * * * | /usr/local/sbin/pfSsh.php playback svc restart openvpn |
| Clean tmp every Sunday | 0 3 * * 0 | /usr/bin/find /tmp -type f -mtime +7 -delete |
| Check disk space every hour | 0 * * * * | /usr/local/bin/check-disk.sh |
Custom PHP Scripts
PHP scripts have full access to pfSense configuration and functions. The interpreter is located at /usr/local/bin/php.
Script Structure
#!/usr/local/bin/php
<?php
require_once("config.inc");
require_once("functions.inc");
require_once("filter.inc");
// Access configuration
$hostname = $config['system']['hostname'];
$interfaces = $config['interfaces'];
// Example: count active firewall rules
$rules = $config['filter']['rule'];
$active_rules = array_filter($rules, function($rule) {
return !isset($rule['disabled']);
});
echo "Active firewall rules: " . count($active_rules) . "\n";Useful Include Files
| File | Purpose |
|---|---|
config.inc | Load the $config array |
functions.inc | General utility functions |
filter.inc | Firewall rule management |
interfaces.inc | Interface functions |
openvpn.inc | OpenVPN management functions |
certs.inc | Certificate functions |
notices.inc | Notification system |
rc.d Startup Scripts
Scripts placed in /usr/local/etc/rc.d/ are automatically executed at system boot in FreeBSD rc.d style.
Example rc.d Script
#!/bin/sh
# PROVIDE: custom_monitor
# REQUIRE: NETWORKING
# BEFORE: LOGIN
. /etc/rc.subr
name="custom_monitor"
rcvar="${name}_enable"
command="/usr/local/bin/custom-monitor.sh"
command_args="&"
load_rc_config $name
run_rc_command "$1"To activate, add the following to /etc/rc.conf.local:
custom_monitor_enable="YES"devd Rules
devd (device daemon) responds to system events such as device connections and network interface changes. Custom devd rules are placed in /etc/devd/.
notify 0 {
match "system" "IFNET";
match "subsystem" "igb0";
match "type" "LINK_UP";
action "/usr/local/bin/link-up-handler.sh";
};After modifying rules, restart devd:
service devd restartCommon Automation Tasks
Automated Remote Backup
#!/bin/sh
# /usr/local/bin/backup-config.sh
# Back up config.xml to a remote server via SCP
REMOTE_USER="backup"
REMOTE_HOST="backup.example.com"
REMOTE_DIR="/backups/pfsense"
DATE=$(date +%Y%m%d-%H%M)
cp /cf/conf/config.xml /tmp/config-${DATE}.xml
scp -i /root/.ssh/backup_key /tmp/config-${DATE}.xml \
${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_DIR}/
rm /tmp/config-${DATE}.xml
logger -t backup "Configuration backed up to ${REMOTE_HOST}"Log Rotation
#!/bin/sh
# /usr/local/bin/rotate-custom-logs.sh
# Rotate custom log files
LOG_DIR="/var/log/custom"
MAX_FILES=7
for logfile in ${LOG_DIR}/*.log; do
if [ -f "${logfile}" ]; then
cp "${logfile}" "${logfile}.$(date +%Y%m%d)"
: > "${logfile}"
fi
done
# Remove old files
find ${LOG_DIR} -name "*.log.*" -mtime +${MAX_FILES} -deleteGateway Monitoring Check
#!/bin/sh
# /usr/local/bin/check-gateway.sh
# Check gateway reachability and send alert
GATEWAY="10.0.0.1"
ALERT_EMAIL="admin@example.com"
if ! ping -c 3 -W 5 ${GATEWAY} > /dev/null 2>&1; then
echo "Gateway ${GATEWAY} unreachable at $(date)" | \
mail -s "pfSense: Gateway Down" ${ALERT_EMAIL}
logger -t gateway-check "Gateway ${GATEWAY} is unreachable"
fiCertificate Expiry Check Script
#!/usr/local/bin/php
<?php
// /usr/local/bin/check-cert-expiry.php
// Check certificate expiration dates
require_once("config.inc");
require_once("certs.inc");
$warning_days = 30;
foreach ($config['cert'] as $cert) {
$info = openssl_x509_parse(base64_decode($cert['crt']));
if ($info === false) continue;
$expires = $info['validTo_time_t'];
$days_left = floor(($expires - time()) / 86400);
if ($days_left < $warning_days) {
$msg = "Certificate '{$cert['descr']}' expires in {$days_left} days";
log_error($msg);
// Notification can be added here
}
}Warnings About Custom Modifications
Custom changes in pfSense carry specific risks. Failure to follow these guidelines may render the system inoperable after an upgrade.
| Risk | Description | Recommendation |
|---|---|---|
| Lost on upgrade | Files outside /usr/local/ and /root/ are overwritten during pfSense upgrades | Place scripts in /usr/local/bin/ or /root/ |
| API incompatibility | pfSense PHP functions may change between versions | Verify script operation after every upgrade |
| Security | Scripts run with root privileges | Minimize privileges, validate input |
| config.xml | Direct config.xml editing can break the structure | Use the PHP API (write_config()) instead of direct editing |
| Shellcmd/Cron packages | A failing shellcmd script can delay boot | Test scripts before adding them to startup |
Before every pfSense upgrade, create a full backup and maintain a list of all custom modifications.
Related Sections
- pfSense API and Automation - REST API, Ansible, Terraform, and programmatic management via HTTP
- pfSense Backup - built-in backup and restore tools for configuration management
- pfSense Packages - installing and managing Shellcmd, Cron, and other extension packages