Credits to Grok (xAI) – Full IPv6 Boot Watchdog Script with Daily Reboot Limit
Thanks to Bob.Dig, Gertjan, and the community for all the help and ideas along the way.
But in the end, I went full nuclear with Grok's help to solve the annoying "IPv6 gateway pending" / DHCPv6 fails at boot issue on 25.11 (and earlier versions) with ixgbe/ix interfaces.
Grok helped build, debug, refine, and harden this script over dozens of iterations — from parsing issues in ash, ambiguous redirects, long shutdown delays, false positives, to daily reboot protection and input validation.
Big thanks to Grok for turning a frustrating problem into a reliable workaround!
What the script does
Runs automatically after boot
Checks if all specified interfaces (INTERFACES=) have at least one global IPv6 address (2000::/3 range, non-link-local)
If yes → exits cleanly (no reboot)
If no → waits a timeout period (default 120 s of checking) → reboots pfSense
Safety: max 2 reboots per calendar day — prevents endless loops if ISP has outage
Counter resets automatically at midnight
Manual reset: rm /var/db/ipv6_watchdog_reboot_count
Extra: Early exit if any interface is physically down (no carrier)
Quiet: Logs only important events to syslog (via logger) — no spam
Robust: Validates config (interfaces exist, no spaces, numbers valid, etc.)
Recommended Installation (fast shutdown, no delays)
Save the script (anywhere, e.g. /usr/local/etc/ipv6_watchdog.sh):vi /usr/local/etc/ipv6_watchdog.sh
#!/bin/sh
# /usr/local/etc/ipv6_watchdog.sh
# IPv6 Global Address Watchdog for pfSense - Built with Grok (xAI)
# Daily reboot limit (max 2/day), quiet syslog logging, input validation, early exit if link down
# More info at: https://forum.netgate.com/topic/199716/25.11-ipv6-gateway-pending/11?_=1767700010718
# ================= CONFIG =================
TIMEOUT=120 # seconds (min 30)
INITIAL_DELAY=60 # seconds (min 10)
CHECK_INTERVAL=20 # seconds (min 5)
INTERFACES="ix2,ix3" # comma-separated, NO spaces!
MAX_REBOOTS_PER_DAY=2
LOG_TO_SYSTEM_LOGS=1 # 1 = syslog (recommended), 0 = file
# ================= VALIDATION & LOGGING =================
validate_positive_int() {
local var="$1" name="$2" min="${3:-1}"
if ! echo "$var" | grep -qE '^[0-9]+$'; then
logger -t ipv6_watchdog "ERROR: $name must be positive integer (got '$var')"
exit 1
fi
if [ "$var" -lt "$min" ]; then
logger -t ipv6_watchdog "ERROR: $name >= $min (got $var)"
exit 1
fi
}
validate_positive_int "$TIMEOUT" "TIMEOUT" 30
validate_positive_int "$INITIAL_DELAY" "INITIAL_DELAY" 10
validate_positive_int "$CHECK_INTERVAL" "CHECK_INTERVAL" 5
validate_positive_int "$MAX_REBOOTS_PER_DAY" "MAX_REBOOTS_PER_DAY" 1
if [ "$LOG_TO_SYSTEM_LOGS" != "0" ] && [ "$LOG_TO_SYSTEM_LOGS" != "1" ]; then
logger -t ipv6_watchdog "ERROR: LOG_TO_SYSTEM_LOGS must be 0 or 1"
exit 1
fi
if [ -z "$INTERFACES" ]; then
logger -t ipv6_watchdog "ERROR: INTERFACES is empty"
exit 1
fi
if echo "$INTERFACES" | grep -q '[[:space:]]'; then
logger -t ipv6_watchdog "ERROR: INTERFACES contains spaces (use 'ix2,ix3')"
exit 1
fi
OLD_IFS="$IFS"; IFS=','; set -- $INTERFACES; IFS="$OLD_IFS"
for iface; do
iface=$(echo "$iface" | tr -d '[:space:]')
if ! ifconfig "$iface" >/dev/null 2>&1; then
logger -t ipv6_watchdog "ERROR: Interface '$iface' does not exist"
exit 1
fi
done
# ================= DETECTION =================
has_global_ipv6() {
local iface="$1"
local addrs
addrs=$(ifconfig "$iface" 2>/dev/null | grep 'inet6 ' | grep -v 'fe80::' | \
sed -E 's/.*inet6[[:space:]]+([0-9a-fA-F:]+).*/\1/')
[ -z "$addrs" ] && return 1
echo "$addrs" | grep -qE '^(2|3)'
return $?
}
# ================= MAIN =================
START=$(date +%s)
# Early exit if any interface down
for iface; do
iface=$(echo "$iface" | tr -d '[:space:]')
if ! ifconfig "$iface" 2>/dev/null | grep -q 'status: active'; then
logger -t ipv6_watchdog "Interface $iface DOWN → watchdog exiting early"
exit 0
fi
done
current_date=$(date '+%Y-%m-%d')
if [ -f "$COUNT_FILE" ]; then
read saved_date saved_count < "$COUNT_FILE" 2>/dev/null || { saved_date=""; saved_count=0; }
else
saved_count=0
fi
if [ "$saved_date" != "$current_date" ]; then
logger -t ipv6_watchdog "New day ($current_date) → reset count to 0"
saved_count=0
fi
logger -t ipv6_watchdog "IPv6 watchdog starting (count: $saved_count / $MAX_REBOOTS_PER_DAY)"
if [ "$saved_count" -ge "$MAX_REBOOTS_PER_DAY" ]; then
logger -t ipv6_watchdog "Daily limit reached ($MAX_REBOOTS_PER_DAY). Skipping today."
exit 0
fi
sleep "$INITIAL_DELAY"
while [ $(( $(date +%s) - START )) -lt "$TIMEOUT" ]; do
all_good=1
for iface; do
iface=$(echo "$iface" | tr -d '[:space:]')
if ! has_global_ipv6 "$iface"; then
all_good=0
break
fi
done
[ $all_good -eq 1 ] && exit 0
sleep "$CHECK_INTERVAL"
done
logger -t ipv6_watchdog "CRITICAL TIMEOUT after ${TIMEOUT}s - no global IPv6"
new_count=$((saved_count + 1))
if [ "$new_count" -le "$MAX_REBOOTS_PER_DAY" ]; then
logger -t ipv6_watchdog "Rebooting ($new_count of $MAX_REBOOTS_PER_DAY today)"
echo "$current_date $new_count" > "$COUNT_FILE"
/sbin/shutdown -r now "IPv6 watchdog timeout (daily $new_count/$MAX_REBOOTS_PER_DAY)"
else
logger -t ipv6_watchdog "Daily limit reached. No reboot today."
fi
exit 1
Make it executable:
chmod +x /usr/local/etc/ipv6_watchdog.sh
Install Shellcmd package if not present (System → Package Manager → Available Packages → shellcmd)
Add Shellcmd entry (Services → Shellcmd → Add):Command (paste exactly):
/bin/sh -c 'nohup /usr/local/etc/ipv6_watchdog.sh >/dev/null 2>/dev/null' &
Customization Tips
Increase TIMEOUT=300 (5 min) if your modem takes longer to restore IPv6
Change INITIAL_DELAY if needed (give more time for interfaces to come up)
Set LOG_TO_SYSTEM_LOGS=0 if you want file logging instead
Add more WAN interfaces if needed: INTERFACES="ix2,ix3,igb0"