Pangolin Deep Intuition
An experienced engineer's guide to Pangolin
1. One-Sentence Essence
Pangolin is a control plane that turns a small public VPS into a programmable identity-aware front door for networks that have no public IP — collapsing “reverse proxy” and “zero-trust VPN” into one product with a shared identity layer underneath.
Internalize that sentence and most of Pangolin’s design choices stop looking arbitrary. Pangolin is not a VPN that grew a UI; it is not a reverse proxy that grew a tunnel. It is a coordinator — a brain — that decides who you are, what you’re allowed to reach, and how the packets should travel. The actual data path is delegated to off-the-shelf parts: WireGuard for tunneling, Traefik for HTTP routing. Pangolin’s job is to make all of that feel like one product.
2. The Problem It Solved
For a decade, anyone who wanted to expose a service on a network without a public IP had three flavors of bad option.
The traditional reverse proxy (nginx, Caddy, Træfik on a public box) needs the public box to be able to reach the backend. If the backend is at home behind CGNAT, on a phone hotspot, on a friend’s network, or in a corporate office with no inbound holes, the proxy can’t get to it. You end up port-forwarding, VPNing back to your own network, or giving up.
The VPN (OpenVPN, WireGuard, IPsec) inverts the problem nicely — the remote network dials out to the central server, so no inbound ports are needed at the edge. But VPNs grant access at the network layer. Once a user is in, they can typically see everything on the LAN. There’s no built-in way to say “this user can hit the wiki on TCP/443 but not the database on TCP/5432,” and there’s certainly no SSO or browser-based access for someone on a coffee-shop laptop who doesn’t have a VPN client installed.
The cloud tunnel (Cloudflare Tunnels, Tailscale Funnel, ngrok) solves both at once — outbound-only connector + identity-aware front door — but it’s somebody else’s platform. Your traffic goes through their servers, you’re bound by their terms (Cloudflare famously prohibits streaming media over their tunnels), and you cannot self-host the control plane.
Pangolin was built by Fossorial (a YC 2025 company; the name “fossorial” means burrowing, like a pangolin) to fill that exact gap: the Cloudflare Tunnel experience, self-hosted, with VPN-style access for non-HTTP services bolted onto the same identity layer. The pitch in one line is “Cloudflare Tunnels, but you own the server.”
The architectural insight that made this work: separate the control plane (who exists, what they can access, where things live) from the data plane (WireGuard tunnels for the dial-out, Traefik for HTTPS termination), and let each piece be replaceable. This is why you’ll see Pangolin docs reference Traefik dynamic config and standard WireGuard concepts — those aren’t implementation hidden away, they’re load-bearing parts of the design.
3. The Concepts You Need
Pangolin has its own vocabulary. Most of it is intuitive once you see how the pieces fit, but you cannot reason about the system without the words.
The named components
These are the actual programs that exist on disk and in Docker images. Knowing the animal names matters because the docs use them constantly and they map to specific processes.
- Pangolin — the control plane. A Go/Next.js process that exposes the web dashboard, the REST API, the WebSocket server for connector control, and the user/auth/policy engine. Stores state in SQLite by default, PostgreSQL in production. This is the brain.
- Gerbil — the WireGuard tunnel manager. A daemon that listens on UDP ports (51820 by default for site tunnels, 21820 for client tunnels), maintains the WireGuard peers, and forwards data plane traffic. Talks to Pangolin over HTTP to fetch the current peer configuration. Runs in the same Docker compose stack as Pangolin on the VPS.
- Newt — the site connector. A userspace WireGuard client (no kernel module, no root needed) that runs on each remote network. It opens a WebSocket back to Pangolin (control), establishes a WireGuard tunnel to Gerbil (data), and proxies TCP/UDP traffic onto the local network behind it.
- Traefik — the HTTP reverse proxy. Standard upstream Traefik, not a fork. Pangolin writes Traefik’s dynamic config and Traefik does the actual TLS termination, routing, and Let’s Encrypt.
- Badger — a small Traefik middleware plugin that calls back into Pangolin for forward-auth on every request to a protected resource. This is what enforces the identity layer on HTTP traffic.
- Olm — the user-facing client app (Mac/Windows/Linux/iOS/Android) that lets a human connect to a Pangolin org and reach private resources. It’s a WireGuard client wrapped in a UI.
- pangctl — the CLI tool for the Pangolin container (rotate secrets, run maintenance commands).
- pangolin CLI — a separate CLI/desktop binary used for things like
pangolin ssh <alias>(the SSH client) andpangolin auth-daemon(for SSH on non-Newt hosts).
If you remember nothing else from this section: Pangolin is the brain, Gerbil is the WireGuard router on the VPS, Newt is the connector at the edge, Traefik is the HTTP front door, Badger is the bouncer.
Logical objects (the things in the database)
These are what you create and manipulate in the dashboard.
- Organization (org) — top-level tenant. Has its own user list, sites, resources, identity providers, and an internal CIDR range (default
100.90.128.0/20, allocated /24 per org). All access policy is scoped inside an org. - Site — a connected remote network. Has a Newt ID and secret, and corresponds to one running Newt process. You can have many sites per org (home + office + cloud), and even multiple sites bridging into the same network for redundancy. Sites deny all traffic by default — installing Newt does not expose anything.
- Resource — the unit of access. A resource says “this destination, on this site, reachable via this method, by these users.” Two kinds:
- Public resource — accessible from the open internet through Traefik (browser-based, HTTPS, or raw TCP/UDP on a port). The “reverse proxy” half of Pangolin.
- Private resource — accessible only when the user is connected with Olm. The “zero-trust VPN” half.
- Target — for a public resource, where the traffic goes after Traefik. An IP+port on a site, with optional path matching and rewriting. A public resource can have multiple targets (load balancing, multi-site failover).
- Destination — the same idea for a private resource: an IP, a CIDR, or an FQDN on the remote network. A private resource has one destination (a single host, a range, or a name).
- Alias — a friendly hostname (e.g.
db.prod.internal) that resolves only inside the Pangolin tunnel. Aliases let users connect by name without setting up internal DNS. Pangolin intercepts DNS on the connected client to make this work. - Client (Olm) — a user’s device or a machine (server/CI runner) connected with credentials. User clients log in with SSO/password; machine clients authenticate with a static ID+secret.
- Role — a bag of users with shared access. Resource access can be granted by user, by role, or by machine. The
Adminrole is reserved. - Rule — a rank-ordered access policy on a public resource:
allow(bypass auth — useful for webhooks),deny(block), orpass(require auth). Matches on path, IP, CIDR, country, or ASN. - Blueprint — declarative YAML (or Docker Compose labels) that describes resources, targets, rules, etc. The Terraform-ish layer for Pangolin. Can be a one-shot bootstrap or continuously reconciled.
Network and reachability concepts
- Dial-out / outbound connector pattern — Newt always initiates the connection to Pangolin (WebSocket + WireGuard outbound to UDP/51820). There is zero inbound to the remote network. This is the entire reason CGNAT users care.
- Relay vs hole-punch — when a client (Olm) wants to reach a private resource on a site, Pangolin first tries to coordinate a direct WireGuard peer-to-peer connection between client and Newt (NAT hole punching). If that fails — common with strict NATs, symmetric NATs, double-NAT setups — it falls back to relaying the WireGuard packets through Gerbil on the VPS. Relayed is slower and uses your VPS bandwidth; hole-punched is direct.
- CGNAT range (100.64.0.0/10) — Pangolin assigns IPs out of this carrier-grade-NAT range by default (
100.89.137.0/20for Gerbil’s WireGuard subnets,100.90.128.0/20for orgs,100.96.128.0/20for utility — DNS lives at100.96.128.1). It picks this range specifically to avoid colliding with your RFC1918 home/office LANs. - Remote nodes — self-hosted Gerbils that you run, but managed by Pangolin Cloud’s control plane. Lets you keep traffic on your own infra while outsourcing the dashboard, DNS, and certificate management to Fossorial. An Enterprise/Cloud feature.
- Forward auth — the Traefik pattern Pangolin uses for public resources: every request hits Traefik, Traefik calls Badger, Badger calls Pangolin’s auth endpoint, and only if Pangolin says “this user is allowed” does the request reach the backend. This is how identity-aware access works for browser traffic.
4. The Distilled Introduction
Here is the journey from “I have a VPS and a service at home” to “users with SSO are accessing it through HTTPS without me opening any ports.” Read this in order; it mirrors how you actually use the product.
Set up: what you need on the table
You need three things before you start:
- A VPS with a public IPv4 and root access. Any provider works. A $4-6/month box is enough for a small homelab — Pangolin and Gerbil are not heavy. Open inbound TCP 80, TCP 443, UDP 51820 (site tunnels), and UDP 21820 (client tunnels) on the cloud firewall and the host iptables.
- A domain name with DNS you can control (Cloudflare, Route 53, Porkbun, whatever). Pangolin needs valid HTTPS for the dashboard and any public resource — Let’s Encrypt is built in. Point an
Arecord likepangolin.example.com(and ideally a wildcard*.example.com) at your VPS IP. - An email address for Let’s Encrypt and the initial admin account.
Install: run the installer
On the VPS:
curl -fsSL https://static.pangolin.net/get-installer.sh | bash
sudo ./installer
The installer is a Go binary that asks a handful of questions (Community vs Enterprise, base domain, dashboard domain, Let’s Encrypt email, install Gerbil? install CrowdSec?) and writes a Docker Compose file plus a config/ directory in the current working directory. It then pulls three images — fosrl/pangolin, fosrl/gerbil, traefik — and starts them. Two to three minutes later, you visit https://pangolin.example.com/auth/initial-setup and create the admin user.
A couple of practical notes from people who have done this:
- If Traefik can’t get a Let’s Encrypt cert (most often because port 80 isn’t actually open or DNS hasn’t propagated), the dashboard won’t load.
docker compose logs -f traefikis where you’ll see this. The fix is often “use a DNS-01 challenge instead of HTTP-01,” which means using a wildcard certificate and a CF API token, but most users won’t need it. - The
config/directory is the entire state of your server — SQLite database, certs, Traefik configs. Back it up. If you blow away the VPS without backing up this directory, your sites and users are gone.
First contact: create a site
In the dashboard: Sites → Add Site. Choose a Newt site (the recommended type). Pangolin generates a NEWT_ID and NEWT_SECRET and shows you a copy-paste Docker Compose snippet to run on your home server:
services:
newt:
image: fosrl/newt
container_name: newt
restart: unless-stopped
network_mode: host # so Newt can reach LAN IPs
environment:
- PANGOLIN_ENDPOINT=https://pangolin.example.com
- NEWT_ID=2ix2t8xk22ubpfy
- NEWT_SECRET=nnisrfsdfc7prqsp9...
network_mode: host is important when Newt needs to reach services on the LAN by their LAN IPs — without it, Newt only sees the Docker network. If your services are all in the same Docker compose, you can drop host mode and refer to containers by service name instead.
Within a few seconds of docker compose up -d, the site shows Online in the dashboard. What just happened: Newt called Pangolin over HTTPS with the ID+secret, got a session token, opened a WebSocket, then opened a userspace WireGuard tunnel to Gerbil. Newt is now sitting there, ready to forward traffic.
The “hello world” — a public HTTP resource
Resources → Add Resource → Public → HTTPS. Give it a subdomain (grafana.example.com), pick the site, and set a target: hostname localhost, port 3000, method http. (If you used host networking in Newt, localhost from Newt’s perspective is your home server.) Save.
Pangolin tells Traefik about the new route, Traefik provisions a Let’s Encrypt cert for grafana.example.com, and the route is live. Visit https://grafana.example.com in a browser — Pangolin’s login page appears first (because by default Platform SSO auth is on), you log in, then Grafana loads.
At this point you have, with no port forwarding, no public IP at home, and no client software on your laptop, an HTTPS-protected, SSO-gated front door to a service on your home network. This is the “aha” moment for most users.
Auth options for public resources
On the resource’s auth tab you have an à la carte menu:
- Platform SSO (default) — users must be in your Pangolin org. Combine with users/roles to scope.
- External IdP — Google, Azure Entra, or any OIDC provider (Authentik, Keycloak, Okta, etc.). For Community Edition, only global OIDC works — per-org IdPs are an Enterprise feature.
- PIN or password — simple, no account needed. Good for sharing with a friend’s grandparent.
- Email one-time passcode with a whitelist (
*@example.com). - Shareable link — a self-destructing URL with an expiry. Useful for “let this contractor in for 2 hours.”
- Bypass rules — for paths that must be public (webhook endpoints, ACME challenges). Rules are top-down with priorities; a
passrule on/adminplus anallowrule on/*would let you keep the admin path authenticated while leaving the rest open. Watch the rule order.
Private resources — the VPN half
Switch to Resources → Add Resource → Private. Pick a site, set a destination (a single IP 192.168.1.10, a CIDR 10.0.0.0/24, or an FQDN host.internal), set TCP/UDP port restrictions, and assign access to users, roles, or machines.
A private resource is invisible until you (a) are a user with access and (b) are connected with Olm. To connect, install Olm from pangolin.net/downloads, sign in to your org, and the client establishes its own WireGuard tunnel — first trying to hole-punch directly to Newt, falling back to relaying through Gerbil. Once connected, you can ssh 192.168.1.10, psql -h db.internal, or just open http://192.168.1.10:8080 in a browser. The traffic goes over WireGuard, through Newt, onto the LAN, to the destination.
If the destination is a single host, you can add an alias — a hostname like db.internal that resolves only inside the tunnel. The client overrides DNS on your machine so that name resolves; this is why you can type ssh db.internal and reach a 10.x address that’s not in any public DNS.
Machine clients and SSH
Two flavors worth knowing exist that go beyond “a person opens an app”:
- Machine clients — for servers and CI runners that need access without a human. Same idea as user clients but auth by ID+secret rather than login. Useful for
psqlfrom a deploy job, or for chaining a VPS into a homelab. - SSH access (Pangolin Cloud + Enterprise only) — instead of managing
authorized_keys, you set up a CA per org, configure each target host’ssshdto trust that CA and call a small auth daemon for principal lookup, and users runpangolin ssh vm-01.prod.internal. Pangolin issues a 5-minute-valid certificate, signs it, the host accepts it, and just-in-time provisions a Unix user. Long-lived keys disappear from your fleet. This is the Tailscale-SSH equivalent, and it’s one of the genuinely best parts of the product if you can use it.
Blueprints — the GitOps mode
For anything beyond a homelab, you’ll want declarative config. Pangolin supports YAML blueprints in three places:
- Pasted into the dashboard or sent via API.
- Passed to Newt via
--blueprint-file blueprint.yaml, where Newt continuously reapplies it (dashboard edits get overwritten). - As Docker Compose labels on application containers (
pangolin.public-resources.app.full-domain=app.example.com), discovered through the Docker socket mounted into Newt. The compose stack becomes the source of truth.
This pattern — describing resources alongside the service that runs them — is one of Pangolin’s best ideas. You stop managing a separate config in the dashboard; the service’s compose file is its own reverse-proxy registration.
Operational reality
A few things you’ll want from day two:
- Logs live in
config/logs/if you setapp.save_logs: true. Off by default. docker compose logs -f pangolin/gerbil/traefikis your debugging triple.- Updating is
docker compose pull && docker compose up -d, but watch the release notes — there have been breaking schema changes between versions. - CrowdSec integration is available through the installer (community-supported guide). Recommended if you’re going to leave anything public exposed for a while.
That’s the introduction. You can drive Pangolin in production with what you now know. The rest of this document is about understanding why it works this way, what bites you, and how to make good decisions.
5. The Mental Model
Three ideas, internalized, will let you predict almost all of Pangolin’s behavior.
Core idea 1: The data plane is somebody else’s job
Pangolin’s identity is “the control plane that programs WireGuard and Traefik.” It is not a network stack. It is not a TLS terminator. It is not a packet forwarder. It is a database of intent, plus a set of orchestrators that turn that intent into peer configurations and Traefik dynamic config.
What this predicts:
- Most performance properties are inherited from the underlying tools. Throughput is bounded by Traefik (for HTTP) and WireGuard (for tunneled traffic). If you want to tune them, you read Traefik and WireGuard docs.
- Outages of Pangolin (the control container) often do not take down existing connections. Newt’s WireGuard tunnel keeps running, Traefik keeps routing already-configured resources. You lose the ability to change things, not the ability to use them.
- Some features are constrained by what Traefik can express. Path-based routing, header injection, middleware composition — these are all Traefik primitives surfaced through Pangolin’s UI.
- The CGNAT IP ranges, the
/24per org, the/30per site — these are deliberate engineering choices to fit WireGuard’s peer model cleanly, not arbitrary.
If your mental model is “Pangolin does the networking,” you will be repeatedly surprised. If your mental model is “Pangolin tells WireGuard and Traefik what to do,” you will mostly be right.
Core idea 2: Outbound dial-out is the load-bearing security and connectivity property
Every interesting thing about Pangolin’s reachability follows from “the connector dials the VPS, never the other way around.”
What this predicts:
- Sites work behind CGNAT, behind double-NAT, behind aggressive corporate firewalls — anywhere you can make outbound HTTPS and UDP/51820. This is why it works where Cloudflare-without-the-tunnel doesn’t.
- You never open inbound ports on the remote network. The only attack surface for the remote LAN is whatever Newt’s binary exposes, which is small.
- The public attack surface is the VPS — Traefik on 80/443, Gerbil on UDP/51820 and UDP/21820, and that’s it. This is why putting Pangolin behind Cloudflare’s orange-cloud proxy or CrowdSec actually adds real defense in depth.
- The bandwidth of relayed private-resource traffic costs you VPS egress. If you have a big media server you’ll stream from, you want hole-punching to work, or you want to pay for VPS bandwidth.
- Failure of the VPS means failure of the control plane and the relay path. Hole-punched peer-to-peer connections might survive briefly, but renewal/rekeying needs Gerbil. This is one of the few hard single points of failure in non-Enterprise deployments.
The dial-out pattern is the foundational decision. The rest of the architecture is just careful engineering on top of it.
Core idea 3: Public resources and private resources share an identity layer, not a network layer
This is the part most people miss for a while. A “public resource” and a “private resource” feel like two products. They are actually two front-ends to the same identity, role, and policy system, separated by how the user’s traffic enters Pangolin.
A public resource is entered through Traefik on port 443 from anywhere on the internet. Badger calls Pangolin to ask “is this request authorized?” and acts as the bouncer. A private resource is entered through a WireGuard tunnel from a logged-in Olm client. The site only allows traffic to that destination if Pangolin’s control loop has installed the route for that user.
The user database, the role system, the IdP integrations, the audit logs — all shared.
What this predicts:
- An external IdP you configure for SSO works for both public-resource login and Olm client login. One source of truth for who’s a person at your company.
- “Defense in depth” comes for free: you can mark a resource public (browser access for convenience) while also requiring the user to be logged in and a member of a specific role.
- You can move a resource from public to private with no change to anything except the access pattern (and the disappearance of the Traefik route). The auth doesn’t change; the network access path does.
- A “private HTTP” resource (Cloud/Enterprise feature) is a clever third state: HTTPS with a real cert, on your domain, but the DNS name resolves only inside the tunnel. That’s possible precisely because the identity layer is decoupled from the network layer.
When you read the docs and see “users, roles, machines, IdP, OTP, PIN, shareable links” all listed together regardless of whether you’re configuring a public or private resource — that’s not redundancy in the docs, that’s the architecture.
6. The Architecture in Plain English
Let’s walk a request end-to-end so the diagram becomes mechanical instead of magical.
What lives on the VPS
In the Docker Compose stack on your public box, three containers cooperate.
Pangolin runs on internal ports (3000 API, 3001 internal API, 3002 Next.js dashboard). It owns the SQLite/PostgreSQL database under config/db. It exposes a WebSocket endpoint that connectors and clients call into. Every time you click “save” in the dashboard, Pangolin writes to the DB, then pushes config — to Gerbil via HTTP for WireGuard peers, and to Traefik via a dynamic config file (or API) for HTTP routes.
Gerbil listens on UDP/51820 for incoming WireGuard from sites and UDP/21820 for clients. It’s the WireGuard endpoint that connectors talk to. It also runs an SNI proxy that gets used in clustered Enterprise setups, but in a single-node deployment it’s mostly “the WireGuard daemon plus a tiny config-fetcher that polls Pangolin.”
Traefik sits on TCP/80 and TCP/443. Its job is the textbook reverse-proxy job — TLS termination with Let’s Encrypt, routing by hostname/path, forwarding to backends. The unusual part is that Traefik’s backends are mostly not on the VPS at all — they’re inside WireGuard tunnels to remote sites. Traefik connects to gerbil:<port> for each site, which forwards the connection through the WireGuard tunnel to Newt on the remote side, which then proxies it to the actual local backend.
What lives on the edge
Newt runs on each remote network. It maintains two things:
- A WebSocket connection back to Pangolin’s WebSocket endpoint over HTTPS — this is how it gets configuration changes, target updates, health-check directives.
- A WireGuard tunnel to Gerbil, running entirely in userspace via the
wireguard-gonetstack. Nowg-quick, no kernel module, nosudo(mostly). The userspace path is what lets it run cleanly in a container without privileged mode.
Newt also acts as a TCP/UDP proxy. When Traefik (via Gerbil) opens a connection through the tunnel destined for 192.168.1.10:8080, Newt picks it up at the WireGuard endpoint and forwards it to 192.168.1.10:8080 on the local network.
A request to a public HTTPS resource, step by step
A user opens https://grafana.example.com in their browser:
- DNS —
grafana.example.comresolves to the VPS public IP (via your wildcard or an explicit A record). - TLS handshake to Traefik on TCP/443. Traefik picks the cert for
grafana.example.comfrom its Let’s Encrypt store. If it’s the first request ever for this hostname, Traefik has already done the ACME dance in the background. - Traefik routes by hostname — its dynamic config (written by Pangolin) has a router that matches
Host(\grafana.example.com`)` and routes to a backend service. - Badger middleware runs first. Badger is a forward-auth plugin. It calls Pangolin’s auth endpoint with the request’s cookies/headers and asks: “should I let this through?”
- Pangolin checks the session cookie. If the user is logged in and the resource’s policy allows them, Badger returns 200 and Traefik continues. If not, Badger returns a redirect to Pangolin’s login page.
- The login flow (if needed) takes the user to
pangolin.example.com/auth/..., runs them through SSO or whatever IdP/PIN/etc. is configured, and sets a session cookie scoped to the parent domain. Now redirect back, and step 5 succeeds. - Traefik forwards the request to its backend for this resource — which is
gerbil:<some-port>on the Docker network. - Gerbil wraps the TCP connection in WireGuard packets and sends them to the right peer (the Newt at the site that owns this resource’s target).
- Newt receives the WireGuard packets on its userspace interface, unwraps them, and proxies the TCP connection to the configured target (
localhost:3000on the remote box, which is Grafana). - The response comes back along the same path: Grafana → Newt → WireGuard → Gerbil → Traefik → browser.
The browser sees a regular HTTPS connection. The user never sees the VPS hop. Grafana’s logs show a connection from 127.0.0.1 (via Newt). The whole thing took maybe 10-30ms of overhead on top of normal HTTPS.
A request to a private TCP resource
A user opens their Olm client, logs in, and SSHes to db.prod.internal:22:
- Olm calls Pangolin at login. Pangolin checks the user’s resource access and returns the list of destinations the user can reach, the aliases for them, and the WireGuard peer info.
- Olm configures a WireGuard interface locally. It sets up routes for each accessible destination (
10.0.0.50/32for a single host,10.0.0.0/24for a CIDR resource), and it overrides DNS so that aliases resolve. - Hole-punch attempt — Pangolin coordinates with both Olm and the target site’s Newt. Both endpoints try to reach each other directly through their NATs, using the UDP information Pangolin shares. If it works, the WireGuard peer is direct. If not, the tunnel goes to Gerbil instead, and Gerbil relays the packets to the right Newt.
- User types
ssh db.prod.internal— the alias resolves to a CGNAT-range IP via Olm’s DNS override. - The TCP connection enters the WireGuard tunnel, comes out at Newt, gets proxied to the configured destination (an internal
10.0.0.50:22). - SSH proceeds normally. Pangolin doesn’t touch the application protocol.
Where state lives
This is the key insight: all important state lives on the VPS. The site connectors are essentially stateless (Newt’s config file holds an ID+secret, the rest is fetched from Pangolin on each startup). The user clients hold a session token, no more. The blueprint files are reapplied on each Newt restart.
If you back up config/ on the VPS, you have everything — DB, certs, encrypted secrets, Traefik dynamic config. If you back up the home server’s Newt config, you’ve backed up… a tiny JSON file with credentials. This is a deliberate design choice and it’s the right one. It makes the edge cheap and the VPS the only thing you need to protect carefully.
7. The Things That Bite You
Six gotchas in the first six months.
1. “Why is localhost in my target not working?” When you configure a public-resource target with hostname localhost and Newt is running in a Docker container without network_mode: host, “localhost” means the Newt container — which has nothing on it. The fix is either (a) put Newt on the host network, (b) use the host’s LAN IP, or (c) put your service and Newt in the same Compose stack and use the service name. This is by far the most common new-user error. It’s a direct consequence of the mental model — Pangolin doesn’t know what’s on your LAN; it asks Newt, and Newt only sees what its network namespace can see.
2. “Why is the Pangolin login page showing the wrong IP in logs?” When you put Pangolin behind Cloudflare’s orange-cloud proxy, every request appears to come from a Cloudflare IP. Rate limiting bans Cloudflare and ban-bombs your whole user base. The fix is server.trust_proxy: 2 in config.yml (Traefik is proxy 1, Cloudflare is proxy 2) and ensuring Badger 1.3.0+ is in your Traefik dynamic config to extract CF-Connecting-IP. Newer installers do this automatically; older ones don’t.
3. “The site says Online but my resource doesn’t work.” Three sub-cases. (a) The destination can’t be reached from Newt’s network namespace — see gotcha #1. (b) The destination resolves to the wrong thing because the FQDN is resolved on the site side, and your home DNS doesn’t know about it. Fix: use IPs in destinations, or run an internal DNS that Newt can reach. (c) For a private resource, port restrictions are wrong — you allowed access but forgot to include the port. The site being green means the control path works; it does not mean any data path works.
4. “SSL is not Full Strict and Cloudflare is breaking things.” Pangolin’s docs are explicit: with Cloudflare orange-cloud, you must use Full (Strict) SSL/TLS mode, you must use wildcard certs via DNS-01, and you must set gerbil.base_endpoint to your VPS IP (not your domain) because Cloudflare obscures the destination IP and WireGuard needs the real address. This trips up almost everyone who tries to combine Cloudflare and Pangolin for the first time.
5. “Multiple sites pointing at the same destination — which one wins?” Pangolin smooths over overlapping destinations across sites by picking one site to handle each IP. The docs are honest that this means traffic could go to either site if you have e.g. two homes both with 192.168.1.0/24 as a destination — it picks one and routes everything there. The fix is aliases: explicitly route nas-home.internal to site A and nas-office.internal to site B, and have users connect by alias, not by raw IP.
6. “Userspace WireGuard is slower than I expected.” Newt uses wireguard-go (userspace) by default. On a Raspberry Pi 4 you might see 200-300 Mbps; kernel WireGuard on the same hardware would give you 800+. Newt has a flag to use kernel WireGuard when available — set it if you’re saturating a gigabit link. For homelab use this almost never matters; for streaming high-bitrate video over a relayed (not hole-punched) connection, it does. Connect this back to the mental model: WireGuard is somebody else’s network stack, and userspace WireGuard has a real cost. Pangolin can’t fix that for you.
8. The Judgment Calls
These are the decisions that separate someone who just installed Pangolin from someone who’s run it for a year.
Cloud vs self-host the control plane. Self-hosting gives you full control, no per-seat pricing, and your data on your infra. The cost is operating it — backups, Let’s Encrypt failures at 3am, version upgrades, the entire blast radius of “your VPS got compromised.” Pangolin Cloud is the inverse: low-touch, HA control plane, and you can still run your own remote nodes so traffic doesn’t go through Fossorial. The right default: self-host for homelabs and learning. Pangolin Cloud + remote nodes for businesses where uptime matters more than the $X/month/seat. Pangolin Cloud as a SaaS for teams that don’t have an ops person.
SQLite vs PostgreSQL. SQLite is the default and it is fine for almost everyone. It’s a single file, it’s fast, it backs up easily, the operational story is trivial. PostgreSQL is required for clustering (Enterprise HA) and useful if you want read replicas, but for a single-node deployment SQLite is the right pick. Use Postgres only when you have a concrete reason — usually clustering or compliance.
Public resources vs private resources for the same service. Tempting to make everything public for the browser convenience. The judgment: public if browser-from-anywhere with SSO is the goal (web apps, dashboards, anything that benefits from being a real HTTPS URL). Private if the service is non-HTTP or sensitive (databases, SSH, RDP, internal admin UIs that don’t need to be on the open web). A good heuristic: would I be comfortable if my login page were brute-forced for a year? If yes, public. If no, private.
Tunneled (Newt) vs basic WireGuard site. Newt site is the default for a reason — it gives you NAT traversal, private resources, health checks, Docker socket integration, and load balancing. Basic WireGuard sites are public-only, require manual NAT/firewall setup, and don’t support most of the features. Pick basic WireGuard only when you have a hard reason: an embedded device that already runs kernel WireGuard, an OPNsense box you don’t want to install a separate connector on. Otherwise, Newt.
Hole-punching vs relaying. You don’t really “pick” — Pangolin tries hole-punching first. But you can invest in making hole-punching work: opening UDP outbound on both sides, setting Newt’s --port to a fixed value and opening that in the firewall, ensuring your NAT is “Full Cone” or “Restricted Cone” rather than symmetric. The judgment: for low-bandwidth admin access, relaying through Gerbil is fine and simpler. For media streaming or large file transfers between two sites, hole-punching matters and is worth the firewall tuning.
Blueprints vs UI. UI is faster for one-off, blueprint is right for everything you’d want to reproduce. The Docker Compose labels pattern is especially nice — the service that runs your app also declares its own resource, which means the compose stack itself is your reverse-proxy registration. The judgment: dashboard for exploring, blueprints for production. And: if you commit blueprints to git, do not also edit through the dashboard, because Newt will overwrite UI edits on its next continuous-apply cycle (unless you used --provisioning-blueprint-file, which only applies once).
Trust-proxy and rate limiting. If you’re behind any proxy at all (Cloudflare, an AWS ALB, anything), set server.trust_proxy correctly. Wrong values cause two failure modes: too low and rate limiting bans your proxy’s IPs (catastrophic), too high and someone can spoof X-Forwarded-For to bypass IP rules (subtle). The judgment: count the proxies between the user and Traefik, set the value, test by visiting a debug endpoint, double-check that bans land on real client IPs.
Authentication strategy: SSO vs local accounts. Local accounts are fine for a homelab — one person, password + TOTP, done. For anything with multiple users, integrate an IdP from day one: it makes user lifecycle (create, disable, audit) a solved problem, and it means revoking access in one place revokes it everywhere. Even for a personal homelab, an OIDC provider like Authentik gives you a real audit trail and easy MFA. The judgment: SSO if there’s any chance of growth past one user.
Where to run Newt: bare metal, container, or Kubernetes. A container with restart: unless-stopped and network_mode: host is the default and the right answer for almost everyone. Bare-metal binary as a systemd service if you can’t run Docker on the edge (NAS appliances, some routers). Kubernetes only when you actually have Kubernetes on the edge and want Newt to discover services via labels — otherwise the operational overhead isn’t worth it. The judgment: keep it boring; container wins.
When to add CrowdSec. If a resource is truly public (no auth, no IP allowlist), turn on CrowdSec. If you’re authenticating everyone, Pangolin’s own rate limiting + a strong auth policy is enough for a homelab. The judgment: CrowdSec is real value at the cost of one more moving part. Don’t enable it on day one — you have enough to debug. Add it when you know your baseline traffic and won’t panic at the first false positive.
Continuous-apply blueprints vs imperative. Continuous-apply (--blueprint-file) makes the file the source of truth; dashboard changes get clobbered. Provisioning blueprint (--provisioning-blueprint-file) applies once at bootstrap, then leaves the system alone. The judgment: continuous-apply for things you want to truly own in git. Provisioning for “set up sensible defaults at install, then let humans manage it through the UI.” Don’t mix the two on the same site.
9. The Commands/APIs That Actually Matter
Pangolin is operated mostly through the UI; there’s no pangolinctl for everything. The pieces below are what you’ll actually type.
Installer and updater:
curl -fsSL https://static.pangolin.net/get-installer.sh | bash
sudo ./installer
# Updating later:
docker compose pull && docker compose up -d
Newt — the connector:
# Simplest form
newt --id $NEWT_ID --secret $NEWT_SECRET --endpoint https://pangolin.example.com
# With Docker integration (auto-discovers containers, applies blueprint labels)
newt --id ... --secret ... --endpoint ... \
--docker-socket /var/run/docker.sock \
--docker-enforce-network-validation
# With a one-shot bootstrap blueprint
newt --provisioning-key spk_... --provisioning-blueprint-file ./bootstrap.yaml \
--endpoint https://pangolin.example.com
# Useful flags for debugging
--log-level DEBUG # for figuring out why a site won't connect
--health-file /tmp/newt-healthy # for Docker healthchecks
--port 51821 # fix the local UDP port for NAT-traversal stability
Pangolin SSH (Cloud/EE only):
pangolin ssh vm-01.prod.internal # interactive SSH
pangolin ssh sign vm-01.prod.internal \ # sign a key non-interactively
--key-file ~/.ssh/id_ed25519.pub
sudo pangolin auth-daemon \ # run on each non-Newt host
--pre-shared-key "$PSK"
Container CLI for Pangolin maintenance:
docker exec -it pangolin pangctl rotate-server-secret # when the secret was leaked
docker exec -it pangolin pangctl apply blueprint \ # apply YAML via CLI
--file /tmp/blueprint.yaml
Debugging triple:
docker compose logs -f pangolin # control plane, auth issues, DB errors
docker compose logs -f gerbil # WireGuard peer issues, tunnel up/down
docker compose logs -f traefik # TLS issues, route resolution, 502/504
Health checks:
curl -fsS http://localhost:3001/api/v1/ # Pangolin's own healthcheck
docker exec newt cat /tmp/newt-healthy # if --health-file was set
Backup what matters:
# On the VPS — this is the whole world
tar czf pangolin-backup-$(date +%F).tgz config/
# Newt has no important state — re-running with the ID/secret recreates everything
The notable absence is anything resembling a rich CLI for editing resources. The API exists (/v1/docs), it’s REST, blueprints are the recommended path for automation. If you find yourself writing a wrapper around the dashboard, write a blueprint instead.
10. How It Breaks
When something is wrong, work through these symptoms.
Symptom: dashboard won’t load, no HTTPS at all. Almost always Traefik can’t get a Let’s Encrypt cert. Causes: port 80 not actually open (check the cloud firewall and host iptables), DNS not propagated yet, or rate-limited by Let’s Encrypt. Diagnosis: docker compose logs -f traefik. Fix: open ports, wait for DNS, or switch to DNS-01 challenge with a wildcard cert if HTTP-01 doesn’t work in your environment.
Symptom: site is offline, Newt logs say “No newt found with that newtId.” The ID or secret is wrong, or the site was deleted in the dashboard. Less obvious: this can happen if the Newt container is on an IPv6-only network and the Pangolin endpoint resolves to IPv4 only. Diagnosis: docker compose logs -f newt and check the exact error. Fix: regenerate credentials in the dashboard if you’ve lost them; ensure network connectivity from Newt’s container to the Pangolin endpoint (a quick curl from inside the container is gold).
Symptom: site is online but the resource returns 502 in the browser. Newt can’t reach the target. Diagnosis: docker exec newt curl http://<target-host>:<port> — if that fails, the problem is on the LAN side, not Pangolin. Common causes: target service isn’t running, the target is in a different Docker network than Newt, “localhost” is being interpreted as the Newt container, firewall on the target host blocks the connection. Fix: prove Newt can reach the target first; fix the LAN-side issue.
Symptom: SSO redirect loop. Login succeeds, but you keep getting redirected to log in again. Almost always a cookie/domain mismatch. Pangolin sets the session cookie on the parent domain so subdomains can share it. Causes: dashboard is on example.com but resources are on *.different-domain.com; trust_proxy is wrong and the cookie is being set as secure but coming over what Pangolin thinks is HTTP; clock skew between VPS and client. Diagnosis: check the cookie in your browser’s devtools — confirm domain scope and Secure/SameSite flags. Fix: keep dashboard and resources under a shared parent domain, set trust_proxy correctly.
Symptom: client connects but private resource isn’t reachable. Check the order: (a) Is the user (or one of their roles) granted access to the resource? (b) Is the target port in the allowed port restrictions? (c) Can Newt at the site actually reach the destination IP/FQDN? (d) Is the alias being resolved — try the raw IP instead to isolate DNS issues. Pangolin’s request log and the Olm “active routes” view help here. The most common failure is port restrictions — granting “access” without remembering that “all ports” needs to be set or the specific port needs listing.
Symptom: real client IPs in logs are wrong. Behind Cloudflare or another proxy, you’ll see proxy IPs everywhere unless you set server.trust_proxy: 2 and have Badger 1.3.0+. Diagnosis: hit a debug resource and check what IP Pangolin records. Fix: trust the right number of proxy hops.
Symptom: relay traffic eating your VPS bandwidth. Olm clients aren’t hole-punching successfully, so all data goes through Gerbil. Diagnosis: monitor Gerbil’s outbound bandwidth; check Olm’s connection details for “direct” vs “relayed.” Fix: open the appropriate UDP outbound on the client side, ensure Newt has a stable port (--port 51821 and open that in the site firewall), check that your NAT type isn’t symmetric. Tailscale has excellent firewall docs that apply directly to Pangolin since both use the same NAT-traversal techniques.
General debugging workflow:
- Reproduce while watching
docker compose logs -f pangolin gerbil traefikon the VPS — most causes show up there. - Test reachability inside Newt’s container with
curlornc— eliminates LAN-side issues. - Check the dashboard’s Audit Logs (admin action logs) — easy way to spot recent config changes that may have broken things.
- Bisect upgrades:
docker compose pullis a common cause of new bugs; rolling back to specific image tags is the way out. - If credentials are suspect, regenerate. Newt IDs/secrets, machine client credentials, and the server secret can all be rotated — but rotating the server secret requires
pangctl rotate-server-secretto re-encrypt stored data.
11. The Downsides / Disadvantages
These are real and worth knowing before you commit.
1. The VPS is a hard single point of failure in Community Edition. Lose your VPS, lose your control plane. Cached hole-punched tunnels may keep working briefly, but new connections, certificate renewals, and any auth flow will fail. Community Edition has no clustering — you cannot run two Pangolin nodes for HA. The structural cause: the design assumes a single brain that holds the auth and config state. Mitigations exist (good backups, automation to rebuild quickly), but you cannot make the brain redundant without paying for Enterprise. For a homelab this is fine. For a business that depends on Pangolin for production SSH access, this is the dealbreaker you should weigh hardest.
2. State is heavy on the VPS, and SQLite makes backups your responsibility. All resources, users, sessions, audit logs, encrypted secrets, and certificates live in config/ on the VPS. If you don’t back it up regularly, a disk failure means rebuilding from scratch. SQLite is fast and convenient but doesn’t have native replication. The cost is the operational hygiene: scheduled tarballs of config/, offsite copies, regular restore tests. Most homelab users don’t do this until something burns down once.
3. Userspace WireGuard has a real performance ceiling on edge devices. Newt uses wireguard-go by default. On a Raspberry Pi 4, you’ll cap somewhere around 200-300 Mbps. On a modest VPS, similar story. Kernel WireGuard is multiple-x faster but requires kernel modules and root and a non-containerized path. For most homelab use this doesn’t matter. For “I want to stream 4K to my phone over Pangolin via relay” or “we route gigabit of S2S traffic through this,” it absolutely does. The structural cause: userspace tunneling pays a per-packet syscall cost that kernel WireGuard avoids. Newt offers a flag to use kernel WireGuard, but then you lose the container-friendly userspace property — pick one.
4. The Cloudflare interaction is a minefield. People constantly try to put Pangolin behind Cloudflare’s orange-cloud proxy without reading the docs. The requirements are specific (Full Strict only, wildcard DNS-01 certs only, explicit base_endpoint IP, websockets enabled, Badger ≥1.3.0, correct trust_proxy), and getting any of them wrong produces subtle failures: rate limits banning every user, websockets dropping connections, real client IPs disappearing from logs, certificates failing to renew. None of this is Pangolin’s fault per se — it’s the price of stacking two proxy layers — but it’s a real adoption tax.
5. Pricing-wise, the “free” community edition is genuinely free, but the interesting features increasingly aren’t. SSH access, private HTTP resources, wildcard public resources, clustering, per-org IdPs, maintenance pages — all Cloud/Enterprise. Fossorial is a YC-funded startup, and the historical pattern is that free-tier features tend to move “up” the pricing ladder over time. The Community Edition is AGPL-3 and they’ve publicly committed to keeping the core free, but anyone betting their company on “this OSS tool will always do X for free” should read the license and the pricing page periodically. Concrete current example: SSH-with-certificates, which is one of the genuinely best features, is not in Community.
6. The product is young and moves fast. Pangolin is on a roughly monthly major-version cadence. Schema changes happen, breaking changes happen, the docs sometimes lag the product. Users on forums frequently report “after updating to X.Y.Z, my Z stopped working.” Fossorial is responsive and the velocity is mostly a strength, but you cannot treat Pangolin as a “set and forget” piece of infrastructure the way you can treat nginx. The judgment: pin to specific image tags in production, read release notes before upgrading, don’t update at 3pm on a Friday.
7. The whole point is centralization — that’s a security tradeoff, not a security win in the abstract. Pangolin holds the keys to every resource you’ve configured. If your Pangolin VPS is compromised, an attacker can issue themselves access to everything Pangolin reaches — every site, every internal database, every SSH host. Compare to a flat WireGuard mesh where each node is its own little island. The Pangolin architecture is much more usable (one place to revoke access, one audit log, one IdP) but the blast radius of a compromise is bigger. Worth treating the VPS as a tier-0 system: minimal users, MFA on the host, locked-down SSH, regular patching, alerts on unusual outbound connections.
8. Diagnostic surface is good but not great. When something is wrong, you have logs from three or four containers, the dashboard’s audit logs, and Newt’s stderr. You don’t have, e.g., a unified “show me the path of this request” trace, or a “test this resource end-to-end” button. Most debugging is “check the obvious places in order.” Compared to a polished commercial product like Tailscale (which has excellent in-product debugging) or a mature OSS tool like nginx (where the failure modes are well-known and tooling exists for each), Pangolin’s troubleshooting is still mostly log-and-grep. Adequate, not great.
12. The Taste Test
How to spot the difference between a Pangolin install someone slapped together and one that’s been thought about.
Good vs bad: where you store the Newt credentials.
- Bad:
NEWT_ID=ix2t8x... NEWT_SECRET=z3g5...hard-coded in a Docker Compose file in the home directory. - Good: credentials in a JSON config file (
--config-file) outside of the compose, or in Docker Compose Secrets, or in a vault that injects them at runtime. Newt persists secrets to that config file after the first run, so you can use ephemeral provisioning keys for bootstrap and let the file own the rest.
Good vs bad: blueprint placement.
- Bad: a 400-line blueprint.yaml in a folder nobody can find. Resources also edited in the UI ad-hoc. Diverged.
- Good: per-application blueprint snippets co-located with each service’s docker-compose. The compose is the source of truth — Newt with
--docker-socketwatches labels, applies on container changes. New service = new compose stanza = automatic resource registration.
Good vs bad: domain layout.
- Bad: dashboard on
pangolin.example.com, resources on*.different-domain.com. SSO doesn’t carry, cookies don’t share, login loops everywhere. - Good: dashboard and resources under a shared parent (
pangolin.example.com,grafana.example.com,wiki.example.com). Or, intentionally, dashboard on one subdomain and resources on a wildcard subdomain of the same root.
Good vs bad: target hostnames.
- Bad: every target uses
localhostbecause the tutorial said so. When the Newt deployment changes, everything breaks. - Good: targets use Docker service names when possible (
grafana:3000), LAN IPs when crossing networks, FQDNs only when the FQDN is meaningful to the site. Aliases for everything users type by hand.
Good vs bad: auth posture.
- Bad: one resource set to
Platform SSO + Password + PIN + Bypass /admin + Email OTP whitelist. Layered defenses misunderstood as additive. - Good: one auth method per resource (SSO for internal users, OTP whitelist for external collaborators), and that method matches the threat model. Auth complexity matches sensitivity.
Good vs bad: backups.
- Bad: “I’ll back it up before the next upgrade.”
- Good: nightly cron tarball of
config/, copied offsite, with a documented restore procedure tested at least once. Specifically not relying on the VPS provider’s snapshots, because those often only work when the VPS provider is up.
Good vs bad: trust-proxy and rate limit posture.
- Bad: defaults. Behind Cloudflare. First time you ban an IP it’s Cloudflare’s, and now nobody can log in.
- Good:
trust_proxyset to the correct number of hops, rate limits tuned for the actual traffic profile, ban actions tested on a non-production user first.
Good vs bad: edition choice.
- Bad: small company on Community Edition, using Pangolin as the only path to production. One VPS, no failover, irreplaceable.
- Good: matched the edition to the cost of failure. Homelab? Community is right. Critical business access? Either Enterprise with clustering, or Cloud-with-remote-nodes, or a thoughtful break-glass plan involving a backup access method (out-of-band SSH key, secondary VPN, etc.).
13. Where to Go Deeper
- The official docs at
docs.pangolin.net— surprisingly good. Start with/about/how-pangolin-worksand/development/system-architecture, then read the “Understanding X” pages (understanding-sites,understanding-resources,understanding-clients). Skim everything else; come back when you need it. Thellms.txtfile gives you a clean URL list — useful for grep-based navigation. - The Fossorial GitHub org (
github.com/fosrl) —pangolin,newt, andgerbilare separate repos. The issues and discussions are where the real edge cases live (“Why doesn’t this work on Synology with DS-Lite?”, “How do I do IPv6-only sites?”). Read recent closed issues before debugging your own problem. - The Pangolin Discord and Slack (linked from
pangolin.net) — the maintainers respond, and the community is unusually civil for a homelab project. Search before posting; common issues are answered repeatedly. - leewc.com — “Self Hosted Cloudflare Tunnels or Tailscale Alternative - Pangolin” — a real practitioner’s walkthrough with the Oracle-cloud firewall details. Useful because it captures the whole journey, not just the docs’ happy path.
- The Pangolin YouTube channel and the I3fhhwptHzc walkthrough video — if you’re a watcher rather than a reader, the official videos cover the dashboard and conceptual flow well.
- Traefik’s documentation — Pangolin sits on top of Traefik and reading the Traefik routers/middlewares/services docs will pay back many times over. You will eventually need to write a custom middleware in Traefik dynamic config to do something Pangolin doesn’t expose, and Traefik is the system that’s actually executing.
- The WireGuard whitepaper by Jason Donenfeld — short, readable, explains why the protocol has the properties Pangolin relies on (cheap connection setup, no negotiation, kernel-friendly). Reading this once makes Gerbil and Newt’s behavior obvious.
- Tailscale’s “How NAT traversal works” docs (
tailscale.com/blog/how-nat-traversal-works) — Pangolin and Tailscale use the same techniques. Tailscale’s writeup is better than anything Pangolin has on the topic. - CrowdSec’s documentation — if you decide to add it, don’t trust the random homelab blogs; read the actual CrowdSec docs and treat it as its own thing that needs operating.
14. The Final Verdict
Pangolin is the most genuine threat to Cloudflare Tunnels in the self-hostable space — not because it’s identical to it, but because it solves the adjacent problem of “what if I want both the tunnel and the VPN under one identity layer, on my own VPS.” That framing is what the existing OSS options (raw WireGuard, Headscale, nginx + a homemade auth-proxy) miss. The architectural decision to not invent the network stack — to let WireGuard and Traefik do their jobs while Pangolin orchestrates them — is exactly right, and it’s why a Y-Combinator-backed team can build, ship, and improve this at the pace they have. It is very good for a v1.x product.
What it gets profoundly right: first, the outbound-only connector as the foundational reachability decision — every homelab problem with CGNAT and corporate firewalls becomes a non-problem, and the security model becomes coherent (“nothing exposed at the edge, everything at the VPS, one auth perimeter to defend”). Second, the shared identity layer for browser and tunnel access — being able to give the same person SSO-protected browser access to Grafana and WireGuard-protected SSH access to the same network, controlled by the same role assignments, is a real architectural achievement that VPN-and-proxy combinations don’t manage. Third, the Docker Compose labels blueprint pattern is one of the cleanest “infrastructure as configuration” stories in the OSS world right now — your app stack registers itself; you do not maintain a parallel dashboard config.
What it gets wrong, or what it costs you: the single-VPS architecture is the real ceiling in Community Edition. You will eventually want HA and discover it’s behind the Enterprise paywall, which is fair business-wise but constrains you. The operational maturity is still rough around the edges — diagnostics, upgrade safety, and edge-case handling all feel like they’re improving fast but aren’t where Tailscale or HAProxy are. And the dependency on a healthy commercial trajectory at Fossorial is real — if they pivot or get acquired, the OSS commits-from-them slow, the dual-licensed model gets less generous, and you’re left maintaining a 30k-LOC Go/TS project. None of that is happening today, but the calculus is different from adopting nginx in 2025.
Who should reach for Pangolin: homelabbers who want Cloudflare-Tunnel-grade convenience without Cloudflare; small companies (5-100 people) who need a coherent remote-access story without buying Zscaler or Cloudflare One; teams who are already operating Docker stacks and find Tailscale’s lack of HTTPS-front-door annoying. Who should think twice: organizations where the remote-access plane is genuinely production-critical and one VPS down means revenue lost — you want clustering or a battle-tested commercial product; teams allergic to YAML-and-Docker operational style; anyone whose primary need is “VPN our whole office network together” without the reverse-proxy half — pure Tailscale or Headscale is simpler and a better fit.
What you should now believe: believe that collapsing reverse proxy and VPN into one product is the right design — the seam between them was always artificial. Believe that the dial-out connector pattern beats inbound port forwarding for security and reachability, and that any modern remote-access tool not using it is fighting the last decade’s war. Don’t believe that “identity-based” or “zero-trust” means the system is magically more secure — Pangolin centralizes a lot of authority on one VPS, and that’s a tradeoff, not a free lunch. When you hear “Pangolin is just a Cloudflare Tunnels alternative,” what they probably mean is the public-resource half — they haven’t yet thought about the private-resource half, which is where the architectural depth lives.
The hard-won line, the one to remember: Pangolin is not a network — it is a control plane that tells WireGuard and Traefik what your network should look like. Once you internalize that, everything else — why state lives on the VPS, why Newt has no real state, why the database is the actual product, why upgrades matter, why your VPS is a tier-0 system — falls out naturally. Forget that, and you’ll spend months treating it like a black box that occasionally surprises you.
The ideas are mine. The writing is AI assisted