WireGuard Mesh & Tunnel Series
    Part 5 of 6

    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.

    70 minutes
    2 GB RamNode VPS for Pangolin/Chisel server, peers anywhere
    Prerequisites

    Public-IP RamNode VPS, a domain with wildcard DNS

    Time

    ~70 minutes

    Outcome

    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 ps

    Open 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:latest

    Within 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.
    Nightly backup with restic
    #!/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 --prune

    Restoring 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
    /etc/systemd/system/chisel.service
    [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.target

    Front 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:

    Forward localhost:5432 on the client to the server's localhost:5432
    chisel client --auth user:$SECRET https://chisel.example.com:443 \
      R:5432:localhost:5432
    Expose the client's local web app on the server's public 0.0.0.0:8000
    chisel client --auth user:$SECRET https://chisel.example.com:443 \
      R:0.0.0.0:8000:localhost:3000/http
    UDP tunnel for a game server
    chisel client --auth user:$SECRET https://chisel.example.com:443 \
      R:25565:localhost:25565/udp

    Run 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

    1. Pangolin admin behind SSO + TOTP from day one. The dashboard is root authority over every tunnel.
    2. DNS-01 over HTTP-01 for Let's Encrypt where possible — works for non-public origins and avoids opening 80 widely.
    3. Per-resource auth is mandatory. "Public" should be a deliberate, audited choice.
    4. Newt secrets are credentials — rotate when an origin changes hands or is decommissioned.
    5. Chisel auth secret in a file, not the unit file. Treat it like a SSH host key.
    6. Rate-limit at Traefik for high-value endpoints (login, admin).
    7. 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.
    8. Backups verified. restic check weekly, restore drill quarterly.