[Release] WAN Failover Monitor — Auto DDNS + Cloudflare Tunnel + Portainer + Proxmox integration for UniFi 5G Backup failover

Hi everyone,

I recently added a UniFi 5G Backup to set up WAN redundancy at home. But once I actually tested a failover (main line down, LTE taking over), a bunch of things broke that I hadn't expected:

  • DDNS for the services I run at home (NAS, a personal wiki, etc.) didn't update, so they became unreachable from outside
  • My reverse proxy (Nginx Proxy Manager) didn't notice the IP had changed, so I had to manually restart the container every time

At first I assumed auto-updating DDNS would fix everything. Turns out there was a more fundamental problem:

Cellular (5G/LTE) connections are almost always behind Carrier-Grade NAT (CGNAT). The WAN IP shown in the UniFi dashboard and the actual public IP the outside world sees are two different addresses. So no matter how accurately DDNS gets updated, inbound port forwarding simply doesn't work most of the time over a cellular backup line.

So I used Claude to build a Hubitat driver that detects public IP changes and automates the entire recovery process.

What it automates

  1. Polls the public IP periodically via api.ipify.org to automatically tell Primary vs. Backup (Failover) WAN apart
  2. Automatically switches the Cloudflare DNS record depending on the situation:
    • On Primary: a CNAME pointing at a domain that itself has an A record (e.g. a root domain already tracked by router-level DDNS)
    • On Failover (5G, CGNAT): automatically switches to a CNAME pointing at a Cloudflare Tunnel — keeps external access working with no port forwarding needed
  3. Restarts Docker containers (e.g. NPM) automatically via the Portainer API
  4. Reboots LXC/VMs automatically via the Proxmox API (for services run standalone in an LXC/VM instead of Docker)
  5. Starts/stops the Cloudflare Tunnel container (cloudflared) automatically — off during normal operation, only spun up during a failover

How it actually works

Primary → 5G Backup transition:

  1. Hubitat checks the public IP every few minutes and detects a change (poll interval configurable, minimum 1 minute)
  2. If the new IP isn't in the Primary WAN's IP range, it's classified as "Failover" (based on a configurable Primary WAN IP prefix)
  3. The Cloudflare DNS record is switched to a CNAME pointing at the Cloudflare Tunnel (the CNAME that used to point at the A-record domain now points at the tunnel address instead)
  4. The cloudflared (tunnel) container is started automatically via the Portainer API
  5. After a configurable delay (10s by default), Docker containers like NPM are restarted (to clear up networking bindings that got confused by the IP change)
  6. If needed, Proxmox LXC/VMs are rebooted too (for anything running standalone, not in Docker)

From this point on, the address you connect to from outside stays exactly the same, but traffic is actually coming in through the Cloudflare Tunnel — completely unaffected by the carrier's CGNAT.

5G Backup → Primary recovery:

  1. Detects the public IP switching back into the Primary WAN's range
  2. Cloudflare DNS is switched back from CNAME(tunnel) to CNAME(the A-record domain)
  3. The cloudflared container is stopped
  4. Docker/Proxmox restarts are triggered again if needed

Environment this is built for

  • UniFi UDR + wired main line + UniFi 5G Backup WAN redundancy
  • Services running on Docker, managed via Portainer
  • (Optional) LXC/VMs running on Proxmox
  • Domain managed through Cloudflare

Since I built this incrementally, feature by feature, it's split into stages so you can install just what you need.

Staged install

  • Stage 1 — public IP detection + Primary/Failover classification only
  • Stage 2 — Stage 1 + automatic Cloudflare DDNS updates
  • Stage 3 — Stage 2 + automatic Docker container restart via Portainer
  • Stage 4 — Stage 3 + Proxmox LXC/VM reboot + automatic Cloudflare Tunnel switching (the full, all-in-one driver)

Download / Install

https://raw.githubusercontent.com/kwon2288/hubitat-driver/refs/heads/main/wan-failover-monitor/drivers/stage4-proxmox-restart-en.groovy

There are quite a few credentials/IDs to gather (Cloudflare API Token, Zone ID, Record ID, Tunnel ID, Portainer API Key, Proxmox API Token, etc.), so it's easy to get lost. Here are notes on the parts that tripped me up.

Looking up a Cloudflare DNS Record ID

Format:

curl -s -X GET "https://api.cloudflare.com/client/v4/zones/{ZONE_ID}/dns_records?name={record_name}" \
  -H "Authorization: Bearer {API_TOKEN}"

Example:

curl -s -X GET "https://api.cloudflare.com/client/v4/zones/1234565789asdffgh/dns_records?name=abc.abc.com" \
  -H "Authorization: Bearer abcde93kr9dkadljfa"

Proxmox API Token

When entering it into the driver, it needs to be in this exact format:

Format: user@realm!tokenid=secret
Example: root@pam!hubitat=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

After creating the token, you also need to grant it permissions under Proxmox's Permissions section — otherwise it won't be able to actually do anything.

Portainer

The Portainer Endpoint ID is just the number in your browser's address bar. If your URL is https://portainer.abc.com/#!/10/docker/dashboard, the number 10 is your Endpoint ID.

Creating the Cloudflare Tunnel container

docker run -d --name cloudflared --restart no \
  cloudflare/cloudflared:latest tunnel run --token <tunnel-token>
docker stop cloudflared

Deleting and recreating subdomains in Cloudflare Tunnel

If you already have CNAME records set up for your subdomains, delete them first, then recreate the routes from the Cloudflare Tunnel dashboard so it can create its own DNS records without conflicts.