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

Execute 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

TypeExecution TimingUsage
shellcmdEnd of boot processPrimary startup tasks
earlyshellcmdStart of boot process (before services)Kernel module loading, filesystem mounting
afterfilterchangeshellcmdAfter every firewall rule changeDynamic rules, integrations

Configuration Example

Command: /usr/local/bin/custom-startup.sh
Type: shellcmd
Description: Custom startup script

Commands 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

TaskScheduleCommand
Daily config backup at 2:00 AM0 2 * * */usr/local/bin/backup-config.sh
Restart OpenVPN every 6 hours0 */6 * * */usr/local/sbin/pfSsh.php playback svc restart openvpn
Clean tmp every Sunday0 3 * * 0/usr/bin/find /tmp -type f -mtime +7 -delete
Check disk space every hour0 * * * */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

FilePurpose
config.incLoad the $config array
functions.incGeneral utility functions
filter.incFirewall rule management
interfaces.incInterface functions
openvpn.incOpenVPN management functions
certs.incCertificate functions
notices.incNotification 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 restart

Common 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} -delete

Gateway 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"
fi

Certificate 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.

RiskDescriptionRecommendation
Lost on upgradeFiles outside /usr/local/ and /root/ are overwritten during pfSense upgradesPlace scripts in /usr/local/bin/ or /root/
API incompatibilitypfSense PHP functions may change between versionsVerify script operation after every upgrade
SecurityScripts run with root privilegesMinimize privileges, validate input
config.xmlDirect config.xml editing can break the structureUse the PHP API (write_config()) instead of direct editing
Shellcmd/Cron packagesA failing shellcmd script can delay bootTest scripts before adding them to startup

Before every pfSense upgrade, create a full backup and maintain a list of all custom modifications.

Related Sections

Last updated on