Pangolin & Chisel for Reverse Tunneling and Ingress
Self-host a Cloudflare Tunnel alternative with Pangolin + Newt + Traefik, and use Chisel for ad-hoc TCP/UDP tunnels — when each is the right tool, and when neither is.
Public-IP RamNode VPS, a domain with wildcard DNS
~70 minutes
Production-grade reverse tunnel ingress + an ad-hoc tunnel toolkit
Reverse Tunnels vs a Mesh
Mesh VPNs (Netbird, Netmaker, Nebula) connect peers to each other. Reverse tunnels do something narrower: they let a service running behind NAT — no public IP, no port forwarding — be reached from the public internet through a server you do control.
This is the Cloudflare Tunnel pattern. Pangolin is the most polished self-hosted equivalent; Chisel is a single-binary option for quick ad-hoc tunneling. They are different tools and they live in this series because anyone evaluating self-hosted overlay networking eventually asks "but what about exposing services?"
Pangolin Architecture
Pangolin is three cooperating pieces deployed on the public RamNode VPS, plus an agent on each origin host:
- • Pangolin server — Go API + UI. Resource definitions, auth, organisations.
- • Gerbil — embedded WireGuard relay. Origin agents dial in; Pangolin terminates the tunnels here.
- • Traefik — fronts everything on 80/443. Pangolin programs Traefik dynamically per resource.
- • Newt — the origin-side agent. One per origin host (or one per network it reaches).
Net effect: a request to https://app.example.com hits Traefik on the public VPS, which routes through Gerbil's WireGuard tunnel to Newt on the origin, which proxies to http://localhost:3000 on the origin's loopback.
Installing Pangolin + Newt
Pangolin ships an installer that scaffolds Docker Compose, generates secrets, and walks through admin setup. Run on the public VPS:
# Public VPS — Ubuntu 24.04, Docker + Compose v2 already installed
mkdir -p /opt/pangolin && cd /opt/pangolin
curl -fsSL https://digpangolin.com/get-installer.sh | bash
./installer
# Answers:
# Domain: pangolin.example.com (wildcard *.example.com points here)
# Admin email: ops@example.com
# Email provider: smtp / sendgrid / resend / disabled
# Enable Gerbil: yes
docker compose up -d
docker compose psOpen https://pangolin.example.com, sign in with the admin credentials the installer printed, and create your first organisation.
On each origin host, install the Newt agent. From the dashboard, create a "Site" — Pangolin generates a one-line registration command:
# On the origin host
docker run -d --name newt --restart unless-stopped \
-e PANGOLIN_ENDPOINT=https://pangolin.example.com \
-e NEWT_ID=<site-id> \
-e NEWT_SECRET=<site-secret> \
--cap-add=NET_ADMIN \
fosrl/newt:latestWithin seconds the dashboard shows the site as connected. Newt has dialed Gerbil over WireGuard; no inbound port is open on the origin.
Exposing a Resource
In the dashboard, Resources → New:
- • Type: HTTP (Pangolin also supports raw TCP/UDP via Gerbil — useful for game servers, SSH, MQTT).
- • Hostname:
app.example.com. - • Site: pick the registered Newt site.
- • Origin:
http://localhost:3000(resolved on the origin host). - • SSL: leave on. Pangolin requests a Let's Encrypt cert via Traefik using DNS-01 or HTTP-01.
Save. Pangolin programs Traefik, requests the cert, and a few seconds later https://app.example.com serves the origin's app — even though that origin sits behind NAT with no inbound rules.
Authentication & SSO
The killer feature vs raw Cloudflare Tunnel: Pangolin gates each resource with its own auth layer. Per-resource you choose:
- • Public — anyone with the URL.
- • Password — single shared password.
- • PIN code — short numeric code, useful for short-lived shares.
- • Email whitelist — magic-link login restricted to listed addresses.
- • SSO — Pangolin user accounts (with TOTP), or external OIDC providers (Authentik, Keycloak, Pocket-ID).
For external OIDC, Settings → Identity Providers → New OIDC provider, paste the discovery URL and client credentials. Authentik and Keycloak both work cleanly; Authentik is usually the better fit because it co-deploys with the rest of the self-hosted stack from the AI Stack series.
Operations & Backup
Pangolin's state lives in two places:
- •
/opt/pangolin/config/— yaml configs, generated secrets. - •
/opt/pangolin/data/— SQLite DB with orgs, users, resources, sites, certs.
#!/usr/bin/env bash
set -euo pipefail
docker compose -f /opt/pangolin/docker-compose.yml stop pangolin
restic -r b2:my-bucket:pangolin backup /opt/pangolin/config /opt/pangolin/data
docker compose -f /opt/pangolin/docker-compose.yml start pangolin
restic -r b2:my-bucket:pangolin forget --keep-daily 14 --keep-weekly 8 --pruneRestoring is symmetric: untar config + data, docker compose up -d, re-issue Newt credentials only if a site secret was lost.
Chisel — Ad-Hoc TCP/UDP Tunnels
Chisel is a single Go binary (server and client are the same binary) that tunnels TCP and UDP over a single HTTPS WebSocket. It is not a Pangolin replacement — it has no UI, no per-resource auth, no automatic TLS. It is the "I need this port available for the next four hours" tool.
Chisel Server on the Public VPS
# Install
wget -O /usr/local/bin/chisel \
https://github.com/jpillora/chisel/releases/latest/download/chisel_linux_amd64
chmod +x /usr/local/bin/chisel
# Generate an auth secret
SECRET=$(openssl rand -hex 32)
echo "$SECRET" > /etc/chisel.secret
chmod 600 /etc/chisel.secret[Unit]
Description=Chisel server
After=network.target
[Service]
Environment=CHISEL_KEY=<persistent-server-key>
ExecStart=/usr/local/bin/chisel server \
--port 8443 \
--auth user:$(cat /etc/chisel.secret) \
--reverse \
--keepalive 25s
Restart=always
[Install]
WantedBy=multi-user.targetFront it with Caddy or Traefik on 443 with proper TLS so the connection from clients is real-world TLS, not the embedded TLS Chisel can do.
Chisel Client Patterns
Three common shapes:
chisel client --auth user:$SECRET https://chisel.example.com:443 \
R:5432:localhost:5432chisel client --auth user:$SECRET https://chisel.example.com:443 \
R:0.0.0.0:8000:localhost:3000/httpchisel client --auth user:$SECRET https://chisel.example.com:443 \
R:25565:localhost:25565/udpRun the client under systemd, tmux, or as a quick foreground process — Chisel is intentionally minimal and does not try to be a service manager.
When to Use Which
- • Production HTTP services with auth → Pangolin. SSO, per-resource gating, automatic TLS, dashboard.
- • A handful of TCP/UDP services for a team → Pangolin (Gerbil supports raw TCP/UDP).
- • Ad-hoc, "open this port for an hour" → Chisel.
- • CI runner that needs to reach a private DB briefly → Chisel.
- • Peer-to-peer between operator workstations → not these. Use Netbird, Netmaker, or Nebula from earlier parts.
Hardening Checklist
- Pangolin admin behind SSO + TOTP from day one. The dashboard is root authority over every tunnel.
- DNS-01 over HTTP-01 for Let's Encrypt where possible — works for non-public origins and avoids opening 80 widely.
- Per-resource auth is mandatory. "Public" should be a deliberate, audited choice.
- Newt secrets are credentials — rotate when an origin changes hands or is decommissioned.
- Chisel auth secret in a file, not the unit file. Treat it like a SSH host key.
- Rate-limit at Traefik for high-value endpoints (login, admin).
- Egress allowlist on origin hosts. Newt only needs to reach the Pangolin VPS — block everything else outbound from the agent's network namespace where you can.
- Backups verified.
restic checkweekly, restore drill quarterly.
