Skip to content
Go back

Pangolin: Self-Hosted Cloudflare Tunnel Alternative

· Updated:
By SumGuy 11 min read
Pangolin: Self-Hosted Cloudflare Tunnel Alternative
Contents

You Don’t Have to Trust Cloudflare

Cloudflare Tunnels are great. You install cloudflared on your home server, point it at your service, and your domain starts resolving from CF’s edge, no port forwarding, no exposed public IP, no drama. We’ve covered how to deploy them securely and the edge security model in earlier posts.

But consider this: every byte of your traffic flows through Cloudflare’s infrastructure. They can read it. They can terminate TLS. Their ToS can change. And if you’re running something sensitive, a private dashboard, internal tools, anything your threat model says “no third parties”, you’re stuck trusting a corporation you don’t control.

Pangolin is what happens when you decide you’re done with that.

It’s a self-hosted tunnel platform: a VPS acts as your ingress, WireGuard meshes back to your home server, and a Traefik reverse proxy (driven by Pangolin’s dashboard) routes traffic to your services. You own every hop. The Community Edition you’re deploying here is free and AGPL-3 licensed. Pangolin itself has since grown a paid Enterprise Edition and a hosted Pangolin Cloud option, but self-hosting the core platform costs nothing beyond your VPS.

There’s a second reason to reach for Pangolin that has nothing to do with trust: CGNAT. If your ISP has you behind Carrier-Grade NAT (look at your WAN IP, anything in the 100.64.0.0/10 range means you’re sharing one public address with a pile of other customers), port forwarding is permanently off the table. No router config rescues you, not even after you’ve replaced the ISP box with your own router, because the block sits upstream on your ISP’s network where you have no reach. Pangolin sidesteps it completely: your home server dials out to the VPS, so nothing ever needs to dial in.


The Architecture in Plain English

Pangolin has four moving parts:

Pangolin, runs on your VPS (the $5/month kind). It handles the identity/auth layer, the admin dashboard, and the API that drives everything else. It’s the brain that lives on a public IP.

Gerbil, the WireGuard daemon. It sits on the VPS next to Pangolin, holds the actual WireGuard interface, and terminates the public ports (WireGuard, plus 443/80 on its network namespace since Traefik shares it).

Newt, the agent that runs on your home server (or wherever your actual services live). It’s a fully userspace WireGuard client, no kernel module or special network capabilities needed on that side. It dials out to Pangolin over WireGuard, registers your exposed services, and forwards traffic back. No inbound firewall rules needed. Your home router never opens a port, which is exactly why this keeps working behind CGNAT.

Traefik, Pangolin ships an actual Traefik container as its reverse proxy and dynamically writes its routing config. You define a resource (e.g. homeassistant.yourdomain.com) and bind it to a target behind Newt. HTTP, HTTPS, or raw TCP. Cloudflare Tunnels can proxy raw TCP too (SSH, RDP, generic tcp:// in config.yml), but the connecting client needs cloudflared access tcp or WARP installed to reach it. Pangolin’s TCP resources are just an open port on the VPS, no special client software required on the other end.

The traffic flow looks like this:

Browser → VPS:443 → Traefik → WireGuard tunnel (Gerbil) → Newt → Home Service

Your home server never touches the public internet directly. The VPS is the only exposed machine, and it’s only doing routing: no state, no data.


What You Need


Deploying Pangolin on Your VPS

Create a directory and drop in a docker-compose.yml:

compose.yaml
name: pangolin
services:
pangolin:
image: fosrl/pangolin:latest
container_name: pangolin
restart: unless-stopped
volumes:
- ./config:/app/config
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3001/api/v1/"]
interval: 3s
timeout: 3s
retries: 15
gerbil:
image: fosrl/gerbil:latest
container_name: gerbil
restart: unless-stopped
depends_on:
pangolin:
condition: service_healthy
command:
- --reachableAt=http://gerbil:3004
- --generateAndSaveKeyTo=/var/config/key
- --remoteConfig=http://pangolin:3001/api/v1/
volumes:
- ./config/:/var/config
cap_add:
- NET_ADMIN
- SYS_MODULE
ports:
- 51820:51820/udp
- 21820:21820/udp
- 443:443 # for traefik, since traefik shares gerbil's network
- 80:80 # for traefik, since traefik shares gerbil's network
traefik:
image: traefik:v3.7
container_name: traefik
restart: unless-stopped
network_mode: service:gerbil
depends_on:
pangolin:
condition: service_healthy
command:
- --configFile=/etc/traefik/traefik_config.yml
volumes:
- ./config/traefik:/etc/traefik:ro
- ./config/letsencrypt:/letsencrypt
networks:
default:
driver: bridge
name: pangolin

Three containers now, not two: pangolin is the control plane and API, gerbil is the WireGuard daemon (needs NET_ADMIN/SYS_MODULE to manage kernel interfaces, that’s normal, not scary) and it’s also the one holding the public ports, and traefik is the actual reverse proxy, sharing gerbil’s network namespace so it can bind 443/80. Pangolin writes Traefik’s dynamic routing config for you as you create resources in the dashboard.

Configuration lives in ./config/config.yml, not environment variables:

config/config.yml
app:
dashboard_url: "https://tunnel.yourdomain.com"
log_level: "info"
domains:
domain1:
base_domain: "tunnel.yourdomain.com"
server:
secret: "generate-a-real-random-secret-here"
gerbil:
start_port: 51820
base_endpoint: "tunnel.yourdomain.com"

The easier path, and the one the project now points people toward, is the official installer:

Terminal window
curl -fsSL https://static.pangolin.net/get-installer.sh | bash
sudo ./installer

It asks for your domain and admin email/password interactively and writes the compose file and config.yml for you. The hand-rolled compose above is here so you understand what the installer is actually doing under the hood.

Terminal window
docker compose up -d
docker compose logs -f pangolin

On first boot, Pangolin reads config.yml, generates WireGuard keys via Gerbil, and starts listening. Hit https://tunnel.yourdomain.com, you’ll land on the dashboard and create the admin account there.


DNS Setup

Point your base domain at the VPS IP, then add a wildcard:

tunnel.yourdomain.com A <vps-ip>
*.tunnel.yourdomain.com A <vps-ip>

The wildcard is how Pangolin routes per-service subdomains without you manually creating DNS records for every service. Define homeassistant.tunnel.yourdomain.com in the dashboard, and it just works.


Connecting Your Home Server with Newt

On your home server, the one running the actual services, you need Newt. You can run it as a container or a binary. Container is easier:

docker-compose.yml
services:
newt:
image: fosrl/newt:latest
container_name: newt
restart: unless-stopped
network_mode: host
environment:
- PANGOLIN_ENDPOINT=https://tunnel.yourdomain.com
- NEWT_ID=your-newt-id # from Pangolin dashboard
- NEWT_SECRET=your-newt-secret # from Pangolin dashboard

The NEWT_ID and NEWT_SECRET come from the Pangolin dashboard when you create a new “site.” Think of a site as a named tunnel endpoint, one Newt per physical location or network segment.

network_mode: host lets Newt reach your local services directly. It dials out to Pangolin, establishes the WireGuard peer, and registers. You’ll see it appear in the dashboard within seconds.


Exposing Your First Service

Once Newt is connected, add a resource in the Pangolin dashboard:

  1. New Resource → give it a subdomain (homeassistant)
  2. Targethttp://192.168.1.100:8123 (your local HA IP)
  3. Site → select the Newt you just registered
  4. Save

That’s it. https://homeassistant.tunnel.yourdomain.com now routes through your VPS, down the WireGuard tunnel, and hits Home Assistant on your LAN. TLS is handled by Pangolin (Let’s Encrypt via ACME). Your home server doesn’t need a cert.

For TCP resources, say you want to forward SSH:

  1. New Resource → type: TCP
  2. Port2222 (the VPS-side port that gets opened)
  3. Target192.168.1.10:22
  4. Site → your Newt

Now ssh -p 2222 [email protected] lands on your home server’s SSH daemon, no extra client software needed on the connecting end. Cloudflare Tunnels can forward SSH too on the free tier, but only if the connecting machine also runs cloudflared access tcp. Pangolin just opens the port.


Auth Layer

This is where Pangolin earns its keep beyond “just another WireGuard helper.” Every resource can sit behind Pangolin’s identity layer:

The OIDC integration means you can gate every service behind your existing SSO stack. Hit paperless.tunnel.yourdomain.com, get redirected to Authentik, log in once, and you’re good for all resources. No per-service auth configuration in Traefik, no middleware chains, no forwarded auth headers to debug at 2 AM.


CrowdSec Integration

Pangolin has native CrowdSec support, wired in through Traefik rather than through Pangolin’s own config. The official installer has a CrowdSec step that drops a crowdsec container into your compose file and adds a bouncer middleware to config/traefik/dynamic_config.yml, pointing at your CrowdSec LAPI key and host:

config/traefik/dynamic_config.yml (excerpt)
http:
middlewares:
crowdsec:
plugin:
crowdsec:
crowdsecMode: live
crowdsecLapiHost: crowdsec:8080
crowdsecLapiKey: your-bouncer-key

Run CrowdSec as a sidecar on the VPS alongside Pangolin and Traefik. Combine with the community blocklists and you’ve got a reasonably aggressive WAF-lite for free. Not Cloudflare’s billion-IP reputation database, but better than nothing, and you control what gets blocked.


Exit Node ACLs

One thing Pangolin does that Cloudflare Tunnels don’t: you can configure ACLs on which Newt (exit node) a resource routes through. If you have multiple sites, home lab, office, friend’s server, you can create resources that only use specific exit nodes.

This matters for:

It’s not a full zero-trust network access platform, but for a homelab context it’s surprisingly capable.


Pangolin vs. Cloudflare Tunnels: When to Pick Which

Choose Pangolin when:

Stick with Cloudflare Tunnels when:

Honestly, these aren’t mutually exclusive. You could run Pangolin for sensitive internal tools and keep Cloudflare Tunnels for the public-facing stuff that benefits from CF’s edge network. Belt and suspenders.


Resource Usage

On a Hetzner CAX11 (Ampere ARM, 2 vCPU, 4 GB RAM, $4.15/month), Pangolin + Gerbil idles at around 80 MB RAM and barely touches the CPU between requests. Under moderate load (a handful of active browser sessions), it stays well under 200 MB. You could probably run it on 512 MB if you had to.

The WireGuard overhead is negligible: it’s in-kernel on Linux, same as Tailscale’s approach. Latency is just your VPS-to-home round trip, which for a geographically close VPS is typically 10-30ms.


What Pangolin Doesn’t Do (Yet)

Be honest about the gaps:


The Bottom Line

If you’ve been running Cloudflare Tunnels because it was the easiest path to exposing home services, and you’re not thrilled about the vendor dependency, Pangolin is the cleanest self-hosted alternative I’ve seen. The architecture is sound: WireGuard handles the mesh, Newt keeps your home network firewalled, and the routing layer is mature enough to handle real workloads.

The $5 VPS cost is the only overhead. You trade Cloudflare’s edge network for full control of your traffic, TCP support, and an auth layer that doesn’t phone home.

Your 2 AM self will still be debugging WireGuard peer states instead of CF tunnel connector logs. Different problems, same hour. At least they’re your problems.

Deploy it, break it, fix it. That’s the homelab way.


Share this post on:

Send a Webmention

Written about this post on your own site? Send a webmention and it'll show up above once verified.


Previous Post
K9s vs Lens vs Headlamp: Cluster UIs
Next Post
Krew: Kubectl Plugins You'll Actually Use

Discussion

Powered by Garrul . Sign in with GitHub or Google, or post anonymously.

Related Posts