When you self-host at home, a stable fiber connection usually gives you everything you need: fast speeds, sometimes a public IPv4 address and minimal latency. But when the fiber drops - whether because of an outage, provider work, or hardware failure - your uptime goes with it. So does your household WiFi.

In my homelab setup, I serve dozens of DNS records, for example theawesomegarage.com and viking-dominion.app). My self-hosting strategy worked great for many years, but during the last three months I've experienced three fiber outages from different reasons. A ran over fiber post, carrier misconfiguration and one still from unknown reasons. I decided to finally get a permanent secondary internet connection, ready to take over if the primary one failed. I decided on a 5G LTE backup provided by a TP-Link NX500 router, just because it was available in a store near me when I was without Internet. Ideall I'd have bought a Teltonika RUTX50, but the TP-Link works surprisingly well for now.

Independently of the hardware used for the failover, the 5G LTE carrier put me behind CGNAT. So I didn't get a public IP address like I'm used to with the fiber connection. That meant port forwarding on my Ubiquiti UDM wouldn't cut it anymore, so I had to do something new.

This post explains how I solved it: by renting the tiniest VPS at Hetzner, tunneling traffic back to my home lab over WireGuard, and using Traefik as a public ingress on the VPS. The result: I can switch from fiber to 5G and still keep almost 100% uptime (at lower speed, but service continuity is more important).

I should add that I do not want to use out of the box tunneling services such as Tailscale Funnel or cloudflared. The amount of traffic I have would certainly have my Cloudflare account closed pretty quickly unless I signed up for some of the paid services, and Tailscale would also not be free, so I decided to build my own solution.

Due to privacy concerns, I chose Hetzner because it's European, and to top it, I chose a Finnish location. The pricing is really good and Hetzner has a decent reputation.


Architecture

  • Home network: A TP-Link NX500 with a data SIM sits between my fiber modem and my Ubiquiti UDM. The NX500 WAN port is connected to my fiber central, and one of its two LAN ports to my Ubiquiti UDM. The TP-Link prioritizes EWAN (the WAN port) and falls back to MBB (data SIM) if the EWAN loses connectivity. WiFi is disabled and I've put the TP-Link on a custom IP range that doesn't interfere with my other networks. My internal network is still controlled by the UDM, as before. My Ubiquiti network is comprised of several switches and APs, and partitioned into different VLANs that are firewalled between them and isolated from the internet when needed. The introduction of the NX500 is completely transparent to my internal network and doesn't affect internal network security at all. Added bonus, my family and I can continue using WiFi and streaming services even when the fiber is down - I still get 650 mbps download speeds when on mobile data!
  • Home lab: I run an Traefik proxy on a VM at home, but you could run anything that serves 80/443. It doesn't matter. My Traefik server handles all incoming internet traffic, previously port forwarded from the UDM. The Traefik server does TLS termination and uses Let's Encrypt/ZeroSSL to generate certificates for my sites. I use DNS validation, but HTTP works as well with the setup I'm demonstrating.
  • VPS (Hetzner): I rent a CPX11 which provides a stable public IP, always reachable. The VPS also runs Traefik, which primary role is to route relevant traffic back home over a WireGuard tunnel. I can also run websites directly on the VPS if I want to.
  • WireGuard tunnel: Persistent tunnel initiated from home towards the VPS. If the fiber goes down, the VPS keeps routing traffic to home over LTE, reconnecting automatically if the network drops while failing over. Wireguard only needs an outbound internet connection to make the tunnel back home work.

How it works

  1. DNS points my domains (A-records) at the Hetzner VPS public IPv4 address.
  2. Traefik on the VPS listens on ports 80/443:
    • For most domains, it uses:
      • HostSNI(...) passthrough on port 443 → tunnel back home (TLS termination stays at home).
      • Catch-all rule on port 80 → tunnel back home (so the home Traefik can handle http to https redirects and ACME challenges).
    • For selected exceptions (like helloworld.theawesomegarage.com), Traefik terminates TLS directly on the VPS and serves a Docker container locally.
  3. Traefik at my home lab runs as usual, completely unaware of the VPS. It still handles certificates, middlewares, and routing for the bulk of services. As I said before, I could run anything, doesn't have to be Traefik.
  4. WireGuard ensures a stable and secure link between my VPN and home lab, with keep alive functionality in case of short internet outages during the switch from EWAN to MBB.

Hetzner also provides a built-in firewall and other security tools if needed.


WireGuard tunnel (VPS ↔ Home)

A central component to make this work is a persistent WireGuard tunnel between the VPS (Hetzner) and the home server. All traffic Traefik needs to forward is sent through this tunnel.

Step 1: Install WireGuard

On both VPS and home:

sudo apt update
sudo apt install wireguard -y

Step 2: Generate Wireguard keys

On each side (run separately):

wg genkey | tee privatekey | wg pubkey > publickey

You'll now have privatekey and publickey for both sides.

  • On the VPS, let's call the keys vps-private-key, vps-public-key
  • On the Home lab, let's call the keys home-private-key, home-public-key

Step 3: Configure VPS

Create /etc/wireguard/wg0.conf:

[Interface]
Address = 10.10.0.1/24
ListenPort = 51820
PrivateKey = <vps-private-key>

[Peer]
PublicKey = <home-public-key>
AllowedIPs = 10.10.0.2/32

Step 4: Configure Home lab server

Create /etc/wireguard/wg0.conf:

[Interface]
Address = 10.10.0.2/24
PrivateKey = <home-private-key>

[Peer]
PublicKey = <vps-public-key>
Endpoint = <vps-public-ip>:51820
AllowedIPs = 10.10.0.1/32
PersistentKeepalive = 25

Note: PersistentKeepalive is critical on the home side if it's behind NAT/CGNAT. It keeps the tunnel open by sending a packet every 25s.

Step 5: Enable and start

On both VPS and home:

sudo systemctl enable wg-quick@wg0
sudo systemctl start wg-quick@wg0

Check status:

sudo wg show

The tunnel will automatically spin up on server boot. If it fails, check that you allow outgoing UDP on port 51820 to flow from your home lab to the VPS, and open for incoming traffic on UDP port 51820 in your VPS firewall.


How Traefik uses the tunnel

With WireGuard up:

  • VPS sees home at 10.10.0.2
  • Home sees VPS at 10.10.0.1

In dynamic.yml (a Traefik config we'll come back to later), services use those IPs:

services:
  home80:
    loadBalancer:
      servers:
        - url: "http://10.10.0.2:80"

  home443:
    loadBalancer:
      servers:
        - address: "10.10.0.2:443"

That way, Traefik on the VPS doesn't care whether home is on fiber or LTE - it always routes through WireGuard.


Steps on the VPS (Hetzner)

  1. Install Traefik:

This downloads Traefik as a binary from Traefik themselves. You could as well run it as a Docker container with docker-compose, as outlined in an article I wrote back in 2024, but then you'd have to adjust the rest of the steps below.

   curl -sL https://github.com/traefik/traefik/releases/download/v3.5.2/traefik_v3.5.2_linux_amd64.tar.gz | sudo tar -xz -C /usr/local/bin traefik
   sudo useradd -r -d /var/lib/traefik -s /usr/sbin/nologin traefik
   sudo mkdir -p /etc/traefik /var/lib/traefik
   sudo chown -R traefik:traefik /etc/traefik /var/lib/traefik
  1. Systemd service /etc/systemd/system/traefik.service:

Make Traefik start on boot, give permissions to bind to reserved ports and define traefik user.

   [Unit]
   Description=Traefik
   After=network.target

   [Service]
   User=traefik
   Group=traefik
   ExecStart=/usr/local/bin/traefik --configFile=/etc/traefik/traefik.yml
   AmbientCapabilities=CAP_NET_BIND_SERVICE
   CapabilityBoundingSet=CAP_NET_BIND_SERVICE
   NoNewPrivileges=true
   Restart=always

   Environment=CF_DNS_API_TOKEN=***

   [Install]
   WantedBy=multi-user.target

Note, that the Environment variable we add here is a necessary token to run DNS-based certificate management through plugins for various DNS providers that are compatible with Lego. If you use something else than CloudFlare, for example Domeneshop for dnsChallenge, check out the Lego link to find the tokens you need to provide.

  1. Static config /etc/traefik/traefik.yml:

Static config used by the routers and services you set up in the dynamic configuration later.

   entryPoints:
     web:
       address: ":80"
     websecure:
       address: ":443"

   providers:
     file:
       filename: /etc/traefik/dynamic.yml
     docker:
       endpoint: "unix:///var/run/docker.sock"
       exposedByDefault: false

   log: { level: DEBUG }
   accessLog: {}

   certificatesResolvers:
     zerosslCloudflare:
       acme:
         caServer: https://acme.zerossl.com/v2/DV90
         email: [email protected]
         storage: /var/lib/traefik/acme-zerossl-cloudflare.json
         dnsChallenge:
           provider: cloudflare
         eab:
           kid: ***
           hmacEncoded: ***
Note, you need to refer to [ZeroSSL documentation](https://zerossl.com/documentation/acme/generate-eab-credentials/) to get the kid and hmacEncoded values. This is necessary because ZeroSSL is a paid service, and you need to authenticate to generate certs. It would be simple to use Let's Encrypt instead. Refer to [Traefik docs](https://doc.traefik.io/traefik/reference/install-configuration/tls/certificate-resolvers/acme/) for a basic Acme web resolution example (http challenge).

Also note that here, we tell Traefik to use the Docker as a provider (allows us to use Docker labels to configure Traefik), so we need to give Traefik access to Docker.
# Add traefik user to the docker group
sudo usermod -aG docker traefik

# Apply group membership by restarting traefik
sudo systemctl restart traefik

If you have no intentions of running local services on your VPS, drop docker as a provider and don't add traefik to the docker group.

  1. Dynamic config /etc/traefik/dynamic.yml:

This file tells Traefik on the VPS what to do with incoming requests. Specifically, it decides whether traffic should be served locally on the VPS or forwarded ("tunneled") back to the home server over WireGuard.

  • Under tcp:, we configure rules for HTTPS (port 443).
    • The to-home router matches a list of domains (via SNI, the TLS extension that carries the requested hostname).
    • Any connection for one of these domains is not terminated on the VPS - instead it is passed through (tls.passthrough: true) directly to the home server at 10.10.0.2:443.
    • The home443 service defines where to send that traffic inside the WireGuard tunnel.
  • Under http:, we configure rules for plain HTTP (port 80).
    • The to-home-80 router is a catch-all: it matches any hostname (HostRegexp({host:.+})) and forwards it to the home server at 10.10.0.2:80.
    • This ensures that HTTP → HTTPS redirects and ACME challenges (for certificate issuance) are still handled by the home Traefik, just as if the VPS wasn't there.

In short: this config makes the VPS act like a smart relay - only terminating TLS for domains you explicitly define elsewhere, and sending all the rest through to your home lab unchanged.

   tcp:
     routers:
       to-home:
         entryPoints: ["websecure"]
         rule: HostSNI(`remim.com`) || HostSNI(`www.remim.com`) || HostSNI(`theawesomegarage.com`) || HostSNI(`skillia.app`) # etc.
         service: home443
         tls:
           passthrough: true

     services:
       home443:
         loadBalancer:
           servers:
             - address: "10.10.0.2:443"

   http:
     routers:
       to-home-80:
         entryPoints: ["web"]
         rule: "HostRegexp(`{host:.+}`)"
         service: home80
         priority: 1

     services:
       home80:
         loadBalancer:
           servers:
             - url: "http://10.10.0.2:80"
  1. Hello World container docker-compose.yml:

A warning! The hello world container in this chapter is a sidetrack in this guide. You don't need it for the purpose of the guide, but consider it a bonus example of what you can do if you do cash out on a VPS.

If a request isn't caught by the dynamic.yml definition, your Traefik will not serve a valid page. Unless you set up a new site to be served locally by for example running docker containers that are labelled in such a way that the VPS Traefik can understand it. Using labels on docker containers to configure Traefik is possible because we added docker as a provider for Traefik in traefik.yml.

This example sets up a helloworld site that runs a simple server that only serves out the page helloworld.html. The helloworld.html page is read relatively from the docker-compose.yml location, so you almost certainly want to organize your volumes better than this example shows.

   services:
     helloworld:
       image: nginx:alpine
       container_name: helloworld
       restart: unless-stopped
       volumes:
         - ./helloworld.html:/usr/share/nginx/html/index.html:ro
       labels:
         - "traefik.enable=true"
         - "traefik.http.routers.helloworld.entrypoints=websecure"
         - "traefik.http.routers.helloworld.rule=Host(`helloworld.theawesomegarage.com`)"
         - "traefik.http.routers.helloworld.tls.certresolver=zerosslCloudflare"

This way, your VPS can double up as a secondary web-server off-site. If you want your services to auto-deploy when you release new versions, check out my guide on building and deploying locally using GitHub actions and webhooks. Normally you wouldn't want to create a runner on your VPS, so just stick to adding webhooks for deploying new images!

You could also set up additional local services by adding http routers and loadbalancer services in dynamic.yml directly, instead of using docker at all, but refer to the Traefik docs for this.


Steps on the home server

When the VPS is set up and Wireguard connected, you really don't have to do much on the home server.

  1. Your home server can run Traefik, NginX, Apache or whatever as before, as long as it accepts connections to ports 80/443.
  2. Certificates (Let's Encrypt/ZeroSSL via DNS) keep terminating here for your old sites.
  3. WireGuard peer configured with VPS, so traffic from 10.10.0.1 (VPS) reaches 10.10.0.2 (home).

Results

  • With fiber up: traffic goes via Hetzner → WireGuard → home → services.
  • With fiber down: WireGuard reconnects over LTE, and everything still works (slower, but available).
  • For some demo/test sites, the VPS is capable of serving traffic directly.

This hybrid model enables you to self host without a public IP, and as an added bonus, gives you a 5G LTE fallback out of the box. This works pretty good with the Unifi UDM, which unfortunately lacks this fallback capability by itself.


Takeaways

  • You don't need BGP, complex SD-WAN, or an expensive router to get multi-WAN failover for self-hosting.
  • A cheap VPS, WireGuard, and Traefik is enough to ride out ISP outages while keeping your domains online.
  • Bonus: you can selectively host things on the VPS itself, while tunneling the rest.
  • Putting your entire home network behind 5G LTE can be expensive if you don't have a flexible data plan. 24 hours equals about 20-25G of data usage on average in my household.
  • Upload speeds can be painful on 5G LTE. This is normally because the 5G towers have powerful transmitters making it perfect to download/consume content, while your 5G device has a weak transmitter, making upload speeds really slow. You can improve on this a lot by connecting an external antenna to your NX500.

Security considerations

  • Traefik API/dashboard: only expose it if you really need it, with auth/IP allowlist. By default it's off. Keep it that way unless you know what you are doing.
  • MySQL/Postgres/etc.: your homelab database servers and other services are not exposed, since the VPS Traefik only proxies 80/443. On the VPS, keep all other ports than 80/443 bound to localhost and never expose ports to the public.
  • Firewall on VPS: If using Iptables/ufw/etc., understand the caveats with Docker. Know that Hetzner has a built in firewall you can use that takes away the uncertainty with Docker. Allow only 22/tcp, 80/tcp, 443/tcp, 51820/udp.
  • SSH: Only allow key based login on your VPS. Remember it will be exposed to the public. Don't use passwords based auth on SSH.

Previous Post