How to install Pangolin and expose your services without Cloudflare Tunnel
Table of contents
- Key takeaways
- What Pangolin is, and which way the connection goes
- What you need before installing Pangolin
- Deploying Pangolin on the VPS with Docker Compose
- Which values you have to change in config.yml
- Installing Newt on the machine in your network
- Publishing your first service end to end
- What authentication comes built in
- What to check when a resource will not respond
- Pangolin against Cloudflare Tunnel and against a WireGuard mesh
- Who Pangolin is for, and who it is not for
- Frequently asked questions
- Do I need to open any port on my router?
- Can I run Pangolin without tunneling, as a plain reverse proxy?
- What happens if the VPS goes down?
- Conclusion
- Sources
Pangolin is an identity-aware tunneled reverse proxy you install on a VPS with Docker Compose. The Newt connector opens the connection outward from your network to the server, so nothing at home listens for inbound traffic, and Traefik issues the Let's Encrypt certificates at the other end.
You want to reach a service running at home from the internet. You have no static IP, you are not opening a port on your router, and you would rather not hand all your traffic to Cloudflare. Pangolin answers exactly that: a small server on a VPS receives the requests, authenticates them, and sends them down a tunnel your network opened outward. This guide installs Pangolin with Docker Compose, enrols the Newt connector, and publishes a first service.
Key takeaways
- The Newt connector dials out from your network to the VPS over WebSocket and WireGuard, so nothing at home listens for inbound traffic and no static IP is needed.
- The stable release at the time of writing is Pangolin 1.22.0, published on 27 August 2026, with Newt 1.16.0 from 19 August.
- The deployment is three containers: the Pangolin server, Gerbil for the WireGuard tunnels, and Traefik v3.7 as the ingress, with the Badger plugin doing authentication.
- Traefik asks Let’s Encrypt for the certificates over an HTTP challenge, so you need ports 80 and 443 open on the VPS, plus UDP 51820 and 21820.
- You gain control and lose convenience: the VPS becomes part of your security boundary, and its bandwidth and its uptime are now yours to worry about.
What Pangolin is, and which way the connection goes
Pangolin is an identity-aware tunneled reverse proxy published by Fossorial under a dual AGPL-3 and commercial licence. The fosrl/pangolin[1] repository opened in September 2024 and, at the end of August 2026, has 22,550 stars and 769 forks. It is written in TypeScript; the connector is Go and is plain AGPL-3.
The piece that changes the equation is the direction of the connection. In a classic reverse-proxy setup you open a port on the router and wait for the world to call. Here it runs the other way: the Newt connector, installed on your network, opens two outbound connections to the server.
One is a WebSocket to the control plane, which is how it receives its configuration. The other is a WireGuard[2] tunnel to Gerbil, which is how the data travels.

Every Pangolin node runs three named processes. Traefik terminates TLS and routes; Badger is a Traefik plugin that applies authentication before anything gets through; Gerbil maintains the WireGuard peers and acts as a relay when NAT hole punching fails. If you already have Traefik running with Docker Compose, most of the configuration file will look familiar.
One guarantee worth internalising early: installing a connector exposes nothing. The documentation puts it plainly: "Sites are software-defined proxies and deny traffic by default". Until you define a resource and grant somebody access, that tunnel leads nowhere.
What you need before installing Pangolin
Four things, none of them optional.
A Linux server with a public IP. The project asks for 1 vCPU, 2 GB of memory and 8 GB of disk as a floor, and suggests 2 vCPU with 20 GB for normal use. Below 1 GB of memory you will need swap so the install does not choke.
A domain of your own, with access to its DNS zone. You need a wildcard A record pointing at the VPS, because every service you publish becomes a separate subdomain:
Type: A Name: * Value: YOUR_VPS_IP TTL: 300
Type: A Name: @ Value: YOUR_VPS_IP TTL: 300
Four ports open on the VPS firewall: 80 and 443 on TCP, UDP 51820 for the site tunnels and UDP 21820 for clients. That last one is only needed if you plan to use private resources with the desktop client.
And a real email address, which is what Let’s Encrypt attaches to your certificates and what you will use for the first admin account.
Deploying Pangolin on the VPS with Docker Compose
There is an automated installer (curl -fsSL https://static.pangolin.net/get-installer.sh | bash) that asks questions and writes the files for you. We are taking the manual path here, which produces the same result with the advantage that you understand what you put there. Four steps:
- Create the project directory tree.
- Write the three configuration files with your domain in them.
- Bring the stack up and wait for the containers to report healthy.
- Open the initial setup page with the token Pangolin prints to its log.
The tree is fixed and worth respecting, because the compose mounts depend on it:
mkdir -p config/db config/letsencrypt config/traefik/logs
The docker-compose.yml file starts the three pieces. Notice that Traefik publishes no ports: it shares Gerbil’s network stack, and Gerbil is the one exposing them. That network_mode: service:gerbil line is the one people delete because it looks odd, and without it TLS stops working:
name: pangolin
services:
pangolin:
image: docker.io/fosrl/pangolin:latest
container_name: pangolin
restart: unless-stopped
volumes:
- ./config:/app/config
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3001/api/v1/"]
interval: "10s"
timeout: "10s"
retries: 15
gerbil:
image: docker.io/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
- 80:80
traefik:
image: docker.io/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
- ./config/traefik/logs:/var/log/traefik
networks:
default:
driver: bridge
name: pangolin
With the files written, starting up is one command and one check:
sudo docker compose up -d
sudo docker compose ps
sudo docker compose logs pangolin | grep -i token
On first boot Pangolin creates config/db/db.sqlite, Gerbil writes its key to config/key, and the server prints a single-use setup token to the console. With that token you visit https://pangolin.yourdomain.com/auth/initial-setup and create the admin account. If the browser complains about the certificate during the first minute, that is expected: Let’s Encrypt is still validating.
Which values you have to change in config.yml
The application file is short, and everything it ships as an example has to be replaced. These are the values you cannot leave as they come:
gerbil:
start_port: 51820
base_endpoint: "pangolin.yourdomain.com"
app:
dashboard_url: "https://pangolin.yourdomain.com"
log_level: "info"
domains:
domain1:
base_domain: "yourdomain.com"
server:
secret: "generate-one-with-openssl-rand-hex-32"
cors:
origins: ["https://pangolin.yourdomain.com"]
flags:
require_email_verification: false
disable_signup_without_invite: true
allow_raw_resources: true
The server secret is generated with openssl rand -hex 32 and reused from nowhere else. If you ever need to rotate it, the internal pangctl rotate-server-secret tool does it without touching the database by hand.
Two networking settings are decided now or never. Gerbil hands out addresses inside 100.89.137.0/20, a CGNAT range chosen so it does not collide with the usual private networks, and gives each site a /30, which is four addresses. If that range overlaps yours, change it before you register the first Gerbil; afterwards you cannot.
The Traefik file needs two more substitutions: your email in the letsencrypt resolver, which uses an HTTP challenge against the web entry point, and the Badger plugin version, pinned at v1.4.0 in the official template.
Installing Newt on the machine in your network
You create the site in the dashboard first, and the dashboard hands you three values: an ID, a secret and the endpoint. With those, installing the connector on the home machine is direct:
curl -fsSL https://static.pangolin.net/get-newt.sh | bash
newt
--id 31frd0uzbjvp721
--secret h51mmlknrvrwv8s4r1i210azhumt6isgbpyavxodibx1k2d6
--endpoint https://pangolin.yourdomain.com
That is fine for a test. To leave it running, the project documents a systemd service reading credentials from /etc/newt/newt.env with 600 permissions, so the secret never appears in the process list or in your shell history.
If you prefer a container, the image is fosrl/newt and the three variables are PANGOLIN_ENDPOINT, NEWT_ID and NEWT_SECRET. The documentation also proposes a cleaner variant: put the configuration in a JSON file and pass it as a Compose secret with CONFIG_FILE=/run/secrets/newt-config.
services:
newt:
image: fosrl/newt
container_name: newt
restart: unless-stopped
environment:
- PANGOLIN_ENDPOINT=https://pangolin.yourdomain.com
- NEWT_ID=2ix2t8xk22ubpfy
- NEWT_SECRET=nnisrfsdfc7prqsp9ewo1dvtvci50j5uiqotez00dgap0ii2
Two other site types exist, and it is worth knowing about them mainly to rule them out. A local site exposes services living on the VPS itself, with no tunnel. A basic WireGuard site uses a raw connection with no control channel, requires NAT to reach other machines on the network, and gives up private resources, health checks and Docker socket scanning. Newt is the recommended path and the only one with the full feature set.
Publishing your first service end to end
A public HTTP resource is the case this section covers. You assign it a fully qualified name inside the domain you registered, add one or more targets with their address and port on the remote network, and pick the site it is reached through. Pangolin terminates TLS, applies the access rules, and only then puts the request into the tunnel.
With more than one target on different sites you get round-robin load balancing and automatic failover when a health check fails. With a single one you have what you need to publish your notes server or your metrics dashboard.
The certificate needs no action from you: when the resource is created, Traefik requests one from Let’s Encrypt for the new subdomain and stores it in config/letsencrypt/acme.json. That is why port 80 has to stay open even when you serve nothing unencrypted.
What authentication comes built in
Every public resource is born with Pangolin’s own single sign-on enabled. From there the options stack, and they can be shared across resources through policies:
| Method | What it is for |
|---|---|
| Pangolin platform SSO | Account with username, password and a second factor inside the dashboard |
| External identity provider | Google, Azure Entra ID, Okta or any generic OIDC provider |
| Users and roles | Access per person or per group, resource by resource |
| PIN or passcode | A simple barrier for something that does not deserve an account |
| Header auth | Machine-to-machine requests with a username and password |
| Email one-time passcode | Domain whitelist, in the style of *@yourcompany.com |
| Shareable links | One-off access with an expiry, revoked by deleting the link |
| Ranked rules | Allow or deny by IP, by country or by path |
The email-domain whitelist is what solves the most common household case: giving the family access without creating accounts. Shareable links carry a token that travels as a p_token query parameter or in the P-Access-Token-Id and P-Access-Token headers, and it must be sent on every request, not just the first. One detail that gets missed: link access does not forward the user’s identity headers to the backend application, so it is no good for wiring the session into the app behind it. For that, your own identity provider such as Pocket ID with passkeys fits better.
What to check when a resource will not respond
In order of likelihood:
- DNS is not resolving. The wildcard takes time to propagate. Check the specific subdomain, not just the root domain.
- The certificate is not issued. Almost always port 80 closed, or the example email left unchanged in the Traefik file.
- The site shows as disconnected. Look at the Newt log on your network. If it retries over and over, UDP 51820 is blocked on the VPS.
- The tunnel is fine and the resource returns a 502. The target points at an address Newt cannot reach. Remember it resolves from your network, not from the VPS.
- Everything works except the desktop client. UDP 21820 is missing, which is what the relay uses when hole punching does not succeed.
- Overlapping addresses. If your network uses the 100.64.0.0/10 range, Gerbil’s default block collides and cannot be changed without redoing the registration.
Pangolin against Cloudflare Tunnel and against a WireGuard mesh
All three avoid opening a port. They differ in who sees your traffic and in what it costs you.
| Pangolin | Cloudflare Tunnel | WireGuard mesh | |
|---|---|---|---|
| Outbound connection from your network | Yes | Yes | Yes |
| Infrastructure you maintain | One VPS | None | A coordinating node |
| Who decrypts the traffic | Your VPS | Cloudflare | Nobody in between |
| Access from somebody else’s browser | Yes | Yes | No, a client is required |
| Per-resource identity and rules | Built in | With Cloudflare Access | Out of scope |
| Upload limit | Whatever your VPS allows | 100 MB on the Free and Pro plans | Whatever your link allows |
| Monthly cost | The VPS | Zero | Zero or the node |
That upload limit is documented by Cloudflare itself: 100 MB on the Free and Pro plans[3], 200 MB on Business and 500 MB or more on Enterprise. If you publish an Immich or a Nextcloud, you will meet it on day one.
Against the mesh, the comparison is different. Headscale or wg-easy give you private access between your own machines, encrypted end to end, with no intermediary decrypting anything. What they do not give you is an address you can email to somebody who will never install a VPN client. Pangolin covers both cases, with public resources through the browser and private resources through a client, which is why its repository now describes itself as a VPN and a reverse proxy at once.
Now the uncomfortable part, and the Pangolin documentation writes it without decoration: "By tunneling out to the VPS, you are effectively including the VPS in your security boundary, so you must secure it as part of your overall network strategy". That server stops being a rented box and becomes part of your network. Its patching, its uptime and its bandwidth bill are your responsibility now. With Cloudflare somebody else carries that; the price is that somebody else sees your requests in the clear.
Who Pangolin is for, and who it is not for
It makes sense if you publish more than one service, want one login screen in front of all of them, and dislike a third party terminating your TLS. It does not if you publish a single service and do not mind Cloudflare seeing it, because you would be paying for a VPS for nothing. Nor if your requirement is that nobody outside can even attempt to get in: a closed mesh such as the free alternative to Tailscale does that better.
It is also worth knowing where the free edition ends. Community Edition is AGPL-3 and, since version 1.22.0, includes browser-based SSH, RDP and VNC resources plus private HTTP and SSH. Enterprise, with node clustering for high availability, ships under the commercial licence: free below 100,000 dollars of annual revenue, paid above it. A single node remains a single point of failure.
Frequently asked questions
Do I need to open any port on my router?
No. The Newt connector starts both connections towards the VPS, one over WebSocket and one over WireGuard. The ports that need opening are on the VPS, not at your house. You do not need a static IP or dynamic DNS either.
Can I run Pangolin without tunneling, as a plain reverse proxy?
Yes. The installer asks whether you want Gerbil, and there is a documented no-tunnel mode where you drop that container and Traefik publishes the ports directly. In that case you lose the connector half and are left with an authenticating reverse proxy.
What happens if the VPS goes down?
Everything you were publishing goes with it. The free edition runs on a single node with no clustering, so the VPS is a single point of failure. High availability across more than one node is an Enterprise feature.
Conclusion
Installing Pangolin is four files, three containers and one connector. What you buy with that work is an entry point you control, with identity built in and automatic certificates, without opening a port at home. What you pay is a VPS that is now part of your perimeter and has to be looked after as such.
If those numbers add up for you, it is the best answer available today for publishing home services without depending on anybody. The Spanish version of this guide is at Cómo instalar Pangolin.
Sources
Source code
Access all the source code for this post on GitHub.
View on GitHub