If you’ve ever had your internet go down in the middle of a video call, you know the pain. I decided to fix that by adding LTE failover to my OPNsense router using a Ubiquiti U5G Max. The U5G Max exposes its cellular WAN through a GRE tunnel, which means you don’t need a UniFi gateway to use it — any router that supports GRE can take advantage of it.

This wasn’t entirely straightforward. OPNsense refuses to assign an IP to a GRE tunnel interface through the GUI, there are MTU issues that silently break web browsing, and there’s a system setting that’s disabled by default without which failover simply won’t trigger. This guide covers everything I learned getting it working — the final, tested configuration with all the gotchas addressed.

Ubiquiti U5G Max

What you’ll end up with

  • Automatic failover to LTE within ~6 seconds of your primary WAN going down
  • Automatic failback when the primary recovers
  • All VLANs covered, no per-rule configuration needed
  • Survives reboots
  • No UniFi gateway required

Before You Start

Why priority-based failover (not gateway groups)

I initially tried policy-based routing with gateway groups on individual firewall rules. It works, but it has an annoying side effect: it routes all traffic through the gateway group, including traffic destined for local devices. This breaks inter-VLAN communication because even local traffic gets pushed toward the WAN.

You can work around it with “local traffic exemption” rules, but it’s messy and error-prone. The cleaner approach — and what this guide uses — is system-level failover via gateway priorities. You assign each gateway a priority number, enable gateway switching, and OPNsense handles the rest. All firewall rules stay set to “Default” gateway, local traffic routes normally, and every VLAN gets failover automatically.

The tradeoff is that it’s all-or-nothing — every VLAN fails over together. If you need selective failover per VLAN, you’ll need the policy-based approach with local traffic exemption rules.

Prerequisites

  • U5G Max adopted into your UniFi controller (managed by your Cloud Key, Dream Machine, or whatever runs your UniFi Network Application). Firmware should be up to date.
  • U5G Max on your management VLAN — the same VLAN where your other UniFi infrastructure lives (Cloud Key, switches, access points). The U5G needs to communicate with the controller for adoption and management, so it belongs on this VLAN alongside the rest of your UniFi gear.
  • U5G Max has a fixed/static IP on the management VLAN (DHCP reservation or static assignment). This is critical — the GRE tunnel endpoints are defined by IP address. If the U5G’s IP changes, the tunnel breaks silently.
  • An active cellular data plan — physical SIM tends to be more reliable than eSIM for initial activation.
  • SSH access to both the U5G Max and OPNsense.

In my setup, the U5G Max is at 192.168.1.15 and OPNsense is at 192.168.1.1, both on the management VLAN. Your IPs will be different — the specific addresses don’t matter as long as both devices are on the same VLAN and the U5G has a fixed address.

Throughout this guide, I’ll use my actual IPs in examples. Replace them with yours wherever they appear.


Step 1: Gather the Tunnel Parameters from the U5G Max

SSH into the U5G Max and inspect the GRE tunnel it automatically creates:

ssh [email protected]
ip a show gre1

You’ll see something like:

14: gre1@NONE: <POINTOPOINT,NOARP,UP,LOWER_UP> mtu 1476
    link/gre 192.168.1.15 peer 192.168.1.1
    inet 100.127.125.128/31 scope global gre1
       valid_lft forever preferred_lft forever
    inet6 fe80::5efe:c0a8:10f/64 scope link
       valid_lft forever preferred_lft forever

There are two layers of addresses here:

LayerU5G Max sideOPNsense sidePurpose
Outer tunnel192.168.1.15192.168.1.1Real VLAN IPs — how the devices reach each other
Inner tunnel100.127.125.128100.127.125.129Point-to-point addresses inside the GRE tunnel

The inner addresses (100.127.125.128/31) appear to be hardcoded on the U5G Max. Your OPNsense side will always be 100.127.125.129.

The outer peer address must match OPNsense’s actual IP on the management VLAN. If they don’t match, the tunnel won’t come up. If you only see a link-local fe80:: IPv6 address with no global inet6, skip IPv6 — it’s not needed for failover.


Step 2: Create the GRE Tunnel in OPNsense

Navigate to Interfaces → Other Types → GRE and click +.

FieldValue
Local addressYour management VLAN interface
Remote address192.168.1.15 (U5G’s fixed MGMT VLAN IP)
Tunnel local address100.127.125.129
Tunnel remote address100.127.125.128
Tunnel netmask / prefix31
DescriptionU5G_GRE

Click Save. This creates the gre0 device.


Step 3: Assign the Interface and Apply the Tunnel IP

This step has a workaround baked into it because of an OPNsense limitation.

Assign the interface

  1. Go to Interfaces → Assignments
  2. Find gre0 in the dropdown and click Add
  3. Click on the new interface and configure it:
    • Enable: âś…
    • Description: WAN_LTE
    • IPv4 Configuration Type: None
    • IPv6 Configuration Type: None
  4. Save and Apply changes

You might be wondering why we’re setting IPv4 to “None” on something that clearly needs an IP. If you try “Static IPv4” and enter 100.127.125.129/31, OPNsense will reject it with “Cannot assign an IP configuration type to a tunnel interface”. This is a known limitation — tunnel interfaces don’t support GUI-based IP assignment.

Apply the IP manually

SSH into OPNsense (or use Interfaces → Diagnostics → Shell) and run:

ifconfig gre0 inet 100.127.125.129 100.127.125.128 netmask 255.255.255.254

Verify with ifconfig gre0 — you should see inet 100.127.125.129 --> 100.127.125.128 netmask 0xfffffffe.

This assignment is not persistent and will vanish on reboot. The next step makes it permanent.


Step 4: Make the IP Survive Reboots

Create a startup script:

vi /usr/local/etc/rc.syshook.d/start/99-gre_ip.sh

Contents:

#!/bin/sh
sleep 10
/sbin/ifconfig gre0 inet 100.127.125.129 100.127.125.128 netmask 255.255.255.254

Make it executable:

chmod +x /usr/local/etc/rc.syshook.d/start/99-gre_ip.sh

The sleep 10 gives the GRE interface time to initialize before the IP is applied. If failover doesn’t work after a reboot, try increasing this to 15 or 20.


Step 5: Configure the Gateways

This step configures both the LTE gateway and your primary WAN gateway for monitoring and failover. Both need proper configuration — if either one is misconfigured, failover won’t work.

Create the LTE gateway

Go to System → Gateways → Configuration, click +, and enable advanced mode.

FieldValue
NameWAN_LTE_TUNNELV4
InterfaceWAN_LTE
Address FamilyIPv4
Priority200
IP Address100.127.125.128
Upstream Gateway❌ Unchecked
Far Gatewayâś… Checked
Disable Gateway Monitoring❌ Unchecked
Failover Statesâś… Checked
Failback Statesâś… Checked
Monitor IP8.8.8.8
Probe Interval1
Time Period6
Loss Interval2

Save and Apply.

About “Far Gateway”: Normally a gateway must be on the same subnet as its interface. Since we assigned the tunnel IP manually via CLI, OPNsense doesn’t know that 100.127.125.128 is directly reachable. “Far Gateway” tells OPNsense to trust you and add a host route to that address regardless. This is standard for tunnel interfaces and VPN setups — you wouldn’t need it on a regular WAN.

Configure the primary WAN gateway

Edit your existing primary WAN gateway with these settings:

FieldValue
Priority100
Upstream Gatewayâś… Checked
Disable Gateway Monitoring❌ Unchecked
Failover Statesâś… Checked
Failback Statesâś… Checked
Monitor IP8.8.4.4
Probe Interval1
Time Period6
Loss Interval2

Save and Apply.

How these settings work together

Upstream Gateway marks a gateway as a candidate for the default route. Only check this on the primary WAN — it tells OPNsense “this is the preferred path, use it whenever it’s healthy.” Leaving it unchecked on the LTE gateway means LTE will only be selected when the primary is down. Priority adds a second layer of preference: 100 (primary) beats 200 (LTE). Monitor IPs must be different per gateway — I use 8.8.8.8 for LTE and 8.8.4.4 for primary, both Google DNS but different endpoints. The Time Period must be at least 2 Ă— (Probe Interval + Loss Interval), so with 1 and 2, the minimum is 6. Failover/Failback States flush active connections when a gateway transitions, forcing clients to immediately reconnect through the new path instead of hanging for 30-60 seconds.


Step 6: Enable Gateway Switching

By default, OPNsense only picks a default gateway at startup or when an interface is physically connected/disconnected. If your ISP goes down but the physical link stays up — which is the most common failure scenario — OPNsense will not switch to the LTE gateway, even though dpinger sees the primary as offline.

  1. Go to System → Settings → General
  2. Check Allow default gateway switching
  3. Click Save

This tells OPNsense to actively re-evaluate and switch the default route whenever gateway monitoring detects a change. Without it, the Failover States and Failback States options on your gateways also do nothing — they depend on gateway switching being active.

Why this matters especially for PPPoE connections

My primary WAN is a PPPoE connection, and this is where gateway switching really earns its keep. PPPoE adds a session layer on top of the physical link — your router authenticates with the ISP and establishes a PPP session over Ethernet. The critical thing is that a PPPoE session can die while the physical Ethernet link stays perfectly healthy. The fiber or DSL sync remains active, the cable is plugged in, the link light is green, but the PPPoE session has dropped and you have no internet.

Without gateway switching, OPNsense only reacts to physical link changes, so it would never notice a PPPoE session drop — dpinger would detect the loss, but OPNsense wouldn’t act on it. With gateway switching enabled, the monitoring-based re-evaluation catches this.

With a non-PPPoE WAN (plain DHCP or static IP behind a modem/router), the failure modes are slightly different. If the upstream device dies or the cable is unplugged, the physical interface goes down and OPNsense detects that without needing gateway switching. However, there are still scenarios where the link stays up but connectivity is lost — an upstream router crash where the Ethernet handshake is maintained, an ISP routing failure, or a CGNAT issue. Gateway switching covers these too. So regardless of your WAN type, enabling it is the more robust choice.

Note on the “Upstream Gateway” flag: Gateway switching and the upstream flag work together. When gateway switching is enabled, OPNsense constantly re-evaluates which gateway should be the default. The upstream flag on your primary WAN tells it “this is the preferred candidate” — without it, OPNsense may not know which gateway to prefer and can select the wrong one. In my testing, failover also works without gateway switching enabled (and without the upstream flag), because OPNsense still re-evaluates gateways when a physical interface goes up or down. However, that only covers cable-pull scenarios. With gateway switching enabled and the upstream flag set on the primary WAN, you get proper failover for both physical link loss and ISP outages where the link stays up — which is the more robust configuration.


Step 7: Configure Outbound NAT

Without NAT on the GRE interface, traffic through the LTE tunnel carries your internal 192.168.x.x addresses. The cellular carrier will drop these.

  1. Go to Firewall → NAT → Outbound
  2. Switch to Hybrid outbound NAT rule generation and Save
  3. Click + to add a manual rule:
FieldValue
InterfaceWAN_LTE
Protocolany
Source addressLOCAL_VLANS net
Destination addressany
Translation / targetInterface address
DescriptionNAT out via LTE
  1. Save and Apply

About LOCAL_VLANS net: This is an OPNsense interface group I created that bundles all my internal VLAN interfaces (desk, servers, IoT, guest) under one name. Using it as a source means any VLAN in the group gets covered by a single rule. To create one, go to Interfaces → Other Types → Group. If you don’t use interface groups, substitute your LAN subnet or select RFC1918 networks to cover all private ranges.

Hybrid mode preserves your existing automatic NAT rules while letting you add manual ones. Your primary WAN NAT is unaffected.


Step 8: Fix the MTU

This one is sneaky. After everything was configured, I found that ping worked, chat apps worked, but web pages wouldn’t load. GRE encapsulation adds overhead that reduces the effective packet size, and large packets get silently dropped.

You can confirm the problem from the OPNsense shell:

# Works:
ping -S 100.127.125.129 -s 1400 8.8.8.8

# Fails with 100% packet loss:
ping -S 100.127.125.129 -s 1476 8.8.8.8

Two fixes are needed:

Set the interface MTU

Go to Interfaces → [WAN_LTE], set MTU to 1400 and MSS to 1360, then Save and Apply.

Add a normalization rule

Go to Firewall → Settings → Normalization, click Add:

FieldValue
InterfaceWAN_LTE
Directionany
Protocoltcp
Max MSS1360

Save and Apply. The value 1360 provides headroom for cellular carrier encapsulation on top of the GRE overhead. If you still have issues, try 1300.


Step 9: Set Up DNS for Failover

If you run a local DNS server like AdGuard, you might find that the tunnel works during failover but pages still don’t load. The reason: your DNS server’s upstream queries are routed through the primary WAN, which is down.

Set your DNS server’s upstream resolver to 9.9.9.9 (Quad9). Public DNS routes through whichever WAN is currently active, so resolution works regardless of which gateway is handling traffic.

Alternatively, configure OPNsense’s Unbound DNS (Services → Unbound DNS → General) to use both WAN and WAN_LTE as outgoing interfaces.


Step 10: Test Everything

Tunnel connectivity

From the OPNsense shell, test each hop in sequence:

# 1. U5G reachable on the MGMT VLAN?
ping 192.168.1.15

# 2. Tunnel peer reachable?
ping -S 100.127.125.129 100.127.125.128

# 3. Internet reachable through the tunnel?
ping -S 100.127.125.129 8.8.8.8

If #1 fails, the U5G isn’t reachable on the VLAN. If #2 fails, the GRE tunnel or IP assignment is wrong. If #3 fails, there’s a NAT or upstream routing issue.

Failover

  1. Disconnect your primary WAN cable
  2. Wait about 6–10 seconds
  3. Browse to whatismyip.com — you should see your cellular carrier’s IP
  4. Reconnect the cable — traffic should return to the primary WAN automatically

A note on this test: Unplugging a cable triggers a physical link state change, which OPNsense detects regardless of whether gateway switching is enabled. This means a cable-pull test can pass even if your configuration is incomplete. It’s a good first test, but it only covers one failure mode. In a real outage — especially with PPPoE — the cable stays plugged in and the physical link stays up while connectivity is lost. To be confident failover actually works in production, make sure gateway switching is enabled (Step 6). You can simulate a more realistic outage by disabling the WAN interface in OPNsense’s GUI or by logging into your modem and dropping the PPPoE session, rather than pulling a cable.

Reboot persistence

  1. Reboot OPNsense
  2. SSH in and run ifconfig gre0 — confirm 100.127.125.129 --> 100.127.125.128 is present
  3. Repeat the failover test

If the GRE IP is missing after reboot, increase the sleep value in the startup script.


Optional: Pushover Notifications on Gateway Switch

If you want to know the moment your connection fails over to LTE or fails back to the primary WAN, you can hook into OPNsense’s gateway monitoring system to send Pushover notifications. No service or cron job needed — it runs automatically.

How the hook system works

OPNsense’s failover detection is a chain of components that are already running:

  1. dpinger runs continuously in the background as a daemon, pinging your monitor IPs (8.8.4.4 and 8.8.8.8) on each gateway
  2. When dpinger detects a state change (gateway goes down or comes back up), it triggers OPNsense’s gateway watcher
  3. The gateway watcher processes the event — updates routes, reloads firewall filters — and then automatically executes every script in /usr/local/etc/rc.syshook.d/monitor/ in numerical order
  4. The core script 10-dpinger handles the route switch. Any script you add with a higher number runs after it

So the chain is: dpinger → gateway watcher → 10-dpinger (core, handles the route switch) → 50-gw-notify.sh (yours, sends the notification).

There’s nothing to enable, no daemon to start. As long as your script is in that directory and has the executable flag set, OPNsense calls it automatically on every gateway alarm state change. This is the same rc.syshook mechanism used by the GRE persistence script earlier — just a different hook directory (monitor/ instead of start/).

The 50- prefix is a convention: OPNsense core uses 10- or 20- for its own scripts, plugins use 50-. It just controls execution order. You could use any number higher than 10.

Setup

  1. Create a Pushover application at pushover.net if you haven’t already. You need an Application Token and your User Key.

  2. Create the script on OPNsense:

vi /usr/local/etc/rc.syshook.d/monitor/50-gw-notify.sh
  1. Paste the following script, replacing YOUR_APP_TOKEN and YOUR_USER_KEY with your Pushover credentials:
#!/bin/sh

# ============================================================================
# Gateway Failover/Failback Pushover Notification Script for OPNsense
# ============================================================================
#
# Sends a Pushover notification whenever OPNsense switches the active
# default gateway (failover or failback).
#
# Location: /usr/local/etc/rc.syshook.d/monitor/50-gw-notify.sh
# Dependencies: curl (included in OPNsense base)
# ============================================================================

# --- Pushover credentials ---------------------------------------------------
PUSHOVER_APP_TOKEN="YOUR_APP_TOKEN"
PUSHOVER_USER_KEY="YOUR_USER_KEY"

# --- Configuration ----------------------------------------------------------
STATE_FILE="/tmp/gw_notify_last_active.state"
LOCK_FILE="/tmp/gw_notify.lock"
LOG_TAG="gw-notify"

# --- Functions --------------------------------------------------------------

log_msg() {
    /usr/bin/logger -t "${LOG_TAG}" "$1"
}

send_pushover() {
    _title="$1"
    _message="$2"
    _priority="${3:-0}"

    /usr/local/bin/curl -s \
        --form-string "token=${PUSHOVER_APP_TOKEN}" \
        --form-string "user=${PUSHOVER_USER_KEY}" \
        --form-string "title=${_title}" \
        --form-string "message=${_message}" \
        --form-string "priority=${_priority}" \
        --form-string "sound=siren" \
        --max-time 10 \
        https://api.pushover.net/1/messages.json > /dev/null 2>&1

    if [ $? -eq 0 ]; then
        log_msg "Pushover notification sent: ${_title}"
    else
        log_msg "ERROR: Failed to send Pushover notification"
    fi
}

get_active_gateway() {
    # Parse the default IPv4 route to find the active gateway
    _gw_ip=$(/usr/bin/netstat -rn -f inet | /usr/bin/awk '/^default/ { print $2; exit }')
    _gw_if=$(/usr/bin/netstat -rn -f inet | /usr/bin/awk '/^default/ { print $NF; exit }')
    echo "${_gw_ip}|${_gw_if}"
}

resolve_gateway_name() {
    _gw_ip="$1"
    _gw_if="$2"

    # Try to match via OPNsense's pluginctl
    _status=$(/usr/local/sbin/pluginctl -d gateway 2>/dev/null)
    if [ -n "${_status}" ]; then
        _name=$(echo "${_status}" | /usr/bin/sed -n \
            "s/.*\"name\":\"\([^\"]*\)\".*\"address\":\"${_gw_ip}\".*/\1/p" \
            | head -1)
        if [ -n "${_name}" ]; then
            echo "${_name}"
            return
        fi
    fi

    # Fallback: configctl
    _status2=$(/usr/local/sbin/configctl interface list gateways json 2>/dev/null)
    if [ -n "${_status2}" ]; then
        _name2=$(echo "${_status2}" | /usr/bin/grep -B5 "\"gateway\":\"${_gw_ip}\"" \
            | /usr/bin/sed -n 's/.*"name":"\([^"]*\)".*/\1/p' | head -1)
        if [ -n "${_name2}" ]; then
            echo "${_name2}"
            return
        fi
    fi

    echo "Unknown (${_gw_if})"
}

# --- Main -------------------------------------------------------------------

# Lock to prevent duplicate notifications from rapid alarm events
if [ -f "${LOCK_FILE}" ]; then
    _lock_age=$(( $(date +%s) - $(stat -f %m "${LOCK_FILE}" 2>/dev/null || echo 0) ))
    if [ "${_lock_age}" -lt 5 ]; then
        log_msg "Skipping duplicate trigger (lock age: ${_lock_age}s)"
        exit 0
    fi
fi
touch "${LOCK_FILE}"

# Wait for the routing table to settle after the gateway switch
sleep 3

# Get current active default gateway
_active=$(get_active_gateway)
_active_ip=$(echo "${_active}" | cut -d'|' -f1)
_active_if=$(echo "${_active}" | cut -d'|' -f2)

if [ -z "${_active_ip}" ] || [ "${_active_ip}" = "" ]; then
    log_msg "WARNING: No default gateway found in routing table"
    send_pushover \
        "OPNsense: No Default Gateway" \
        "No default IPv4 route found in the routing table. All WAN connectivity may be down.
Time: $(date '+%Y-%m-%d %H:%M:%S')" \
        "1"
    rm -f "${LOCK_FILE}"
    exit 1
fi

_gw_name=$(resolve_gateway_name "${_active_ip}" "${_active_if}")

# Check if the active gateway actually changed
_last_active=""
if [ -f "${STATE_FILE}" ]; then
    _last_active=$(cat "${STATE_FILE}")
fi

_current_state="${_gw_name}|${_active_ip}|${_active_if}"

if [ "${_current_state}" = "${_last_active}" ]; then
    log_msg "Gateway unchanged (${_gw_name}), no notification sent"
    rm -f "${LOCK_FILE}"
    exit 0
fi

echo "${_current_state}" > "${STATE_FILE}"

# Determine event type based on gateway name
# Adjust these patterns to match your gateway names
_event_type="Gateway Changed"
_priority=0
case "${_gw_name}" in
    *WAN_PPPOE*|*WAN_DHCP*)
        _event_type="Failback to Primary WAN"
        _priority=0
        ;;
    *LTE*|*TUNNEL*)
        _event_type="Failover to LTE Backup"
        _priority=1
        ;;
esac

_old_name="(first run)"
if [ -n "${_last_active}" ]; then
    _old_name=$(echo "${_last_active}" | cut -d'|' -f1)
fi

_message="Active gateway: ${_gw_name}
Gateway IP: ${_active_ip}
Interface: ${_active_if}
Previous: ${_old_name}
Time: $(date '+%Y-%m-%d %H:%M:%S')"

log_msg "${_event_type}: now using ${_gw_name} (${_active_ip} on ${_active_if})"

send_pushover "OPNsense: ${_event_type}" "${_message}" "${_priority}"

rm -f "${LOCK_FILE}"
exit 0
  1. Make it executable:
chmod +x /usr/local/etc/rc.syshook.d/monitor/50-gw-notify.sh

What the script does

The script tracks the current active gateway and only notifies you when it actually changes — not on every dpinger alarm flap. Here’s the flow:

  1. A lock file (/tmp/gw_notify.lock) prevents duplicate notifications if multiple alarm events fire in quick succession (within 5 seconds of each other)
  2. A 3-second delay lets the routing table settle after the gateway switch before the script checks anything
  3. It reads the current default route from netstat -rn to find the active gateway IP and interface
  4. It resolves the IP to a friendly name (like WAN_PPPOE or WAN_LTE_TUNNELV4) using OPNsense’s pluginctl
  5. It compares the current gateway to the state file (/tmp/gw_notify_last_active.state) — if the gateway hasn’t changed, it exits silently
  6. If the gateway has changed, it sends a Pushover notification and updates the state file

The notification includes the active gateway name, its IP, the interface, the previous gateway, and a timestamp. Failovers to LTE are sent at high priority (which bypasses quiet hours on your phone), while failbacks to the primary WAN are sent at normal priority. If no default route is found at all (both paths down), it sends a high-priority warning.

Note: The state file lives in /tmp and won’t survive a reboot. This is intentional — the first gateway alarm after a reboot always sends a notification, which confirms which gateway came up as default.

Testing

Run it manually to verify Pushover delivery:

/usr/local/etc/rc.syshook.d/monitor/50-gw-notify.sh

You should receive a notification showing your current active gateway (WAN_PPPOE if everything is normal).

Run it a second time to verify deduplication:

/usr/local/etc/rc.syshook.d/monitor/50-gw-notify.sh

This time you should not get a notification. Check the log to confirm:

clog -f /var/log/system.log | grep gw-notify

You should see “Gateway unchanged, no notification sent.”

Reset the state and run again to simulate a reboot scenario:

rm /tmp/gw_notify_last_active.state
/usr/local/etc/rc.syshook.d/monitor/50-gw-notify.sh

You should get another notification.

Trigger a real failover to test the full chain:

Unplug your primary WAN cable and wait about 10 seconds. You should receive a notification saying it failed over to LTE. Plug the cable back in and wait — you should get a second notification confirming failback to the primary WAN. Then check the logs to confirm the script was triggered automatically by the monitor hook (not just by your manual runs):

clog -f /var/log/system.log | grep gw-notify

If you only see entries from your manual runs and nothing from the cable-pull test, check that the script is executable:

ls -la /usr/local/etc/rc.syshook.d/monitor/50-gw-notify.sh

Look for the x flag in the permissions. If it’s missing, run chmod +x again.


Final Configuration Summary

ComponentSettingValue
U5G MaxVLANManagement VLAN (with Cloud Key and other UniFi devices)
U5G MaxIPFixed/static (e.g., 192.168.1.15)
GRE TunnelRemote addressU5G’s fixed MGMT VLAN IP
GRE TunnelTunnel local100.127.125.129
GRE TunnelTunnel remote100.127.125.128
GRE TunnelNetmask/31
InterfaceNameWAN_LTE
InterfaceIPv4 TypeNone (manual via CLI)
InterfaceMTU1400
InterfaceMSS1360
Gateway (LTE)IP100.127.125.128
Gateway (LTE)Priority200
Gateway (LTE)Upstream Gateway❌
Gateway (LTE)Monitor IP8.8.8.8
Gateway (LTE)Far Gatewayâś…
Gateway (Primary)TypePPPoE (also works with DHCP/static)
Gateway (Primary)Priority100
Gateway (Primary)Upstream Gatewayâś…
Gateway (Primary)Monitor IP8.8.4.4
Both GatewaysProbe Interval1
Both GatewaysTime Period6
Both GatewaysLoss Interval2
Both GatewaysFailover Statesâś…
Both GatewaysFailback Statesâś…
Gateway SwitchingSystem → Settings → General✅ Enabled
Outbound NATModeHybrid
Outbound NATSourceLOCAL_VLANS net
NormalizationMax MSS1360
DNS UpstreamResolver9.9.9.9
PersistenceScript/usr/local/etc/rc.syshook.d/start/99-gre_ip.sh

Troubleshooting

GRE tunnel won’t come up

  • Is the U5G reachable? ping 192.168.1.15
  • Does OPNsense’s MGMT VLAN IP match the peer address in ip a show gre1 on the U5G?
  • Does the U5G still have its fixed IP, or did it change via DHCP?
  • Are the outer and inner addresses correct in the GRE config?

Gateway shows offline

  • Is the GRE IP assigned? Run ifconfig gre0
  • Is “Disable Gateway Monitoring” unchecked?
  • Is the Monitor IP set to 8.8.8.8?
  • Is “Far Gateway” checked? Without it, OPNsense can’t route to the tunnel gateway
  • Try service dpinger restart
  • Last resort: reboot and immediately re-apply the GRE IP

Websites don’t load over the tunnel (but ping works)

  • Almost certainly an MTU issue. Test: ping -S 100.127.125.129 -s 1400 8.8.8.8
  • Apply MTU 1400 and MSS 1360 as described in Step 8
  • Still failing? Lower MSS to 1300

Failover doesn’t trigger

  • Check gateway switching first — System → Settings → General, “Allow default gateway switching” must be checked. Without it, OPNsense only switches gateways on boot or physical link changes, not when dpinger detects an outage.
  • Is “Upstream Gateway” checked on the primary WAN and unchecked on the LTE? Without the upstream flag, enabling gateway switching can cause OPNsense to select the wrong gateway as the default.
  • Is monitoring enabled on both gateways? Both must show RTT and Loss values (not “~” or empty) on the Gateways page
  • Are priorities correct? Primary: 100, LTE: 200
  • Verify routing: netstat -rn | grep default

DNS fails during failover

  • Set your DNS upstream to 9.9.9.9, or configure Unbound to use both WAN interfaces

Slow reconnection after failover

  • Enable Failover States and Failback States on both gateways
  • Ensure gateway switching is enabled (required for state flushing to trigger)
  • Time Period minimum: 2 Ă— (Probe Interval + Loss Interval)

GRE IP missing after reboot

  • Script exists? cat /usr/local/etc/rc.syshook.d/start/99-gre_ip.sh
  • Script executable? ls -la /usr/local/etc/rc.syshook.d/start/99-gre_ip.sh
  • Try increasing the sleep value

Local network breaks with policy-based failover

  • This happens when you assign a gateway group on firewall rules — local traffic gets routed to WAN. Add a local exemption rule above the failover rule (source: VLAN net, destination: LOCAL_VLANS net, gateway: Default), or switch to the system-level priority approach used in this guide.

That’s it. About 30 minutes of configuration once you know what you’re doing, and you get seamless LTE failover that kicks in within seconds and recovers automatically. No UniFi gateway required.