
In short. Pangolin is a potential Cloudflare Tunnel alternative or Tailscale alternative. Self-hosting Pangolin means running the Pangolin VPN server on a VPS with a public IP, deploying the Newt client on your private network, and using WireGuard tunnels to route traffic securely. No port forwarding is required on your home router. The setup takes under 30 minutes with Docker Compose. Pangolin handles SSL certificates, reverse proxying, and identity-aware access control from one dashboard.
Prerequisites
Before you start with the Pangolin install, make sure you have:
- A VPS with a public IP. An entry-level Contabo VPS handles the full stack comfortably, with room to run additional services alongside it.
- Ubuntu 22.04, 24.04 or 26.04 (fresh install recommended).
- Docker and Docker Compose plugin installed.
- A domain or subdomain with an A record pointed at the VPS IP. You must have access to the DNS zone management for your domain.
- Ports 22/TCP, 80/TCP, 443/TCP, 51820/UDP, and 21820/UDP open in your firewall. You can easily set this up with a Contabo Firewall.
On the private-network side – your home server, office machine, or any remote device – you need Docker or the Newt binary. No open ports, no port forwarding, no public IP required on that machine.
Step 1: Prepare the VPS
SSH in as root and update the system before anything else.
- Connect: `ssh root@<your-vps-ip>`
- Update: `apt update && apt upgrade -y`
- Install Docker and the Compose plugin from the official Docker apt repository:
apt install -y ca-certificates curl gnupg
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg \
-o /etc/apt/keyrings/docker.ascah
chmod a+r /etc/apt/keyrings/docker.asc
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] \
https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" \
| tee /etc/apt/sources.list.d/docker.list > /dev/null
apt update
apt install -y docker-ce docker-ce-cli containerd.io \wai
docker-buildx-plugin docker-compose-pluginVerify with `docker compose version`. You should see a 2.x version string.
Next, open the ports Pangolin needs. Every Contabo VPS and VDS comes with a free built-in firewall you can manage directly from the Customer Control Panel, with no extra software to install. Open the Contabo Firewall guide and add inbound allow rules for: 22/TCP, 80/TCP, 443/TCP, 51820/UDP, and 21820/UDP
Step 2: Install Pangolin with Docker Compose
Pangolin’s server stack runs as three containers. Pangolin handles the dashboard, API, and access control. Gerbil manages the WireGuard interface on the VPS side and owns the exposed ports. Traefik runs inside Gerbil’s network namespace rather than directly on the host. Traefik terminates TLS, provisions Let’s Encrypt certificates automatically, and routes HTTP traffic to the right destination. CrowdSec can be added later as an optional Traefik plugin for reputation-based blocking.
Create the project directory and required subdirectories:
mkdir -p /opt/pangolin/config/db \
/opt/pangolin/config/letsencrypt \
/opt/pangolin/config/traefik/logs
cd /opt/pangolinGenerate a secret for Pangolin’s session signing. You will need the output in `config.yml`:
openssl rand -hex 32Create the four configuration files below. Write each one to the path shown before moving on to the next.
`docker-compose.yml`
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
- 22:22
traefik:
image: docker.io/traefik:v3.6
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`config/traefik/traefik_config.yml` – replace `[email protected]` with your real email address:
api:
insecure: true
dashboard: true
providers:
http:
endpoint: "http://pangolin:3001/api/v1/traefik-config"
pollInterval: "5s"
file:
filename: "/etc/traefik/dynamic_config.yml"
experimental:
plugins:
badger:
moduleName: "github.com/fosrl/badger"
version: "v1.4.0"
log:
level: "INFO"
format: "common"
maxSize: 100
maxBackups: 3
maxAge: 3
compress: true
certificatesResolvers:
letsencrypt:
acme:
httpChallenge:
entryPoint: web
email: "[email protected]"
storage: "/letsencrypt/acme.json"
caServer: "https://acme-v02.api.letsencrypt.org/directory"
entryPoints:
web:
address: ":80"
websecure:
address: ":443"
transport:
respondingTimeouts:
readTimeout: "30m"
http:
tls:
certResolver: "letsencrypt"
encodedCharacters:
allowEncodedSlash: true
allowEncodedQuestionMark: true
serversTransport:
insecureSkipVerify: true
ping:
entryPoint: "web"`config/traefik/dynamic_config.yml` – replace every instance of `pangolin.example.com` with your dashboard hostname:
http:
middlewares:
badger:
plugin:
badger:
disableForwardAuth: true
redirect-to-https:
redirectScheme:
scheme: https
routers:
main-app-router-redirect:
rule: "Host(`pangolin.example.com`)"
service: next-service
entryPoints:
- web
middlewares:
- redirect-to-https
- badger
next-router:
rule: "Host(`pangolin.example.com`) && !PathPrefix(`/api/v1`)"
service: next-service
entryPoints:
- websecure
middlewares:
- badger
tls:
certResolver: letsencrypt
api-router:
rule: "Host(`pangolin.example.com`) && PathPrefix(`/api/v1`)"
service: api-service
entryPoints:
- websecure
middlewares:
- badger
tls:
certResolver: letsencrypt
ws-router:
rule: "Host(`pangolin.example.com`)"
service: api-service
entryPoints:
- websecure
middlewares:
- badger
tls:
certResolver: letsencrypt
services:
next-service:
loadBalancer:
servers:
- url: "http://pangolin:3002"
api-service:
loadBalancer:
servers:
- url: "http://pangolin:3000"
tcp:
serversTransports:
pp-transport-v1:
proxyProtocol:
version: 1
pp-transport-v2:
proxyProtocol:
version: 2`config/config.yml` – replace all three placeholder domains and paste the secret you generated with `openssl`:
gerbil:
start_port: 51820
base_endpoint: "pangolin.example.com"
app:
dashboard_url: "https://pangolin.example.com"
log_level: "info"
telemetry:
anonymous_usage: true
domains:
domain1:
base_domain: "example.com"
server:
secret: "paste-your-openssl-output-here"
cors:
origins: ["https://pangolin.example.com"]
methods: ["GET", "POST", "PUT", "DELETE", "PATCH"]
allowed_headers: ["X-CSRF-Token", "Content-Type"]
credentials: false
flags:
require_email_verification: false
disable_signup_without_invite: false
disable_user_create_org: false
allow_raw_resources: trueStep 3: Start Pangolin and Access the Dashboard
- Start the stack:
docker compose up -d - Check all three containers are running:
docker compose ps. Gerbil and Traefik wait for Pangolin’s healthcheck to pass before starting, so give it about 30 seconds. - Retrieve the one-time setup token from the Pangolin logs:
docker compose logs pangolinLook for the block that reads === SETUP TOKEN GENERATED === and copy the token.
4. Open https://pangolin.example.com/auth/initial-setup in a browser. The first Let’s Encrypt certificate request can take up to 60 seconds. If you see a certificate warning on first load, wait a minute and refresh.
5. Paste the setup token, set your admin email and password, then create your first organization.
If Traefik cannot obtain a certificate, confirm your DNS A record is propagated and nothing else is bound to ports 80 or 443 on the host.
Step 4: Connect a Private Site with Newt over WireGuard
Newt is Pangolin’s site connector agent. It runs on your private network, makes an outbound WireGuard connection to the Pangolin hub, and proxies traffic to your local services. Because Pangolin Newt initiates the connection outbound, your home router needs no open ports, no port forwarding, and no public IP.
In the Pangolin dashboard, go to Sites, click Add Site, choose Newt Tunnel, and give the site a name. Pangolin shows you three credentials: the endpoint URL, the Newt ID, and the Newt secret. Copy all three. They are shown only once.
On your home server, deploy Newt with Docker Compose:
services:
newt:
image: fosrl/newt
container_name: newt
restart: unless-stopped
environment:
- PANGOLIN_ENDPOINT=https://pangolin.example.com
- NEWT_ID=<your-newt-id>
- NEWT_SECRET=<your-newt-secret>Start it with docker compose up -d. Within seconds the site indicator in the dashboard of your self-hosted Pangolin turns green and shows Online.
Step 5: Expose Your First Service via Reverse Proxy
With the Newt tunnel online, exposing a service requires no config file edits. Everything is done from the Pangolin dashboard:
- Open your site and click Add Resource.
- Give the resource a name and choose a subdomain.
- Under Target Configuration, enter the internal address of the service as Newt sees it. For a service at 192.168.1.10:3000 on your home network, enter http://192.168.1.10:3000. For a service on the same Docker network as Newt, use its container name and port.
- Enable HTTPS. Traefik provisions the certificate automatically.
- Set an access policy: public, password-protected, or restricted to specific users or email domains.
The resource is live at its subdomain within seconds. If you add a wildcard A record (*.pangolin.example.com pointing at the VPS IP), each new resource is reachable immediately without a separate DNS entry per service.
Step 6: Add Browser-Based SSH / RDP Access (v1.19+)
Pangolin 1.19 added SSH, RDP, and VNC as first-class public resource types alongside HTTP. A user opens the resource URL, authenticates through Pangolin, and gets a full interactive session in the browser. No separate SSH client, remote desktop app, or VNC viewer is required.
To enable browser SSH for VPS, click Add Resource, select SSH as the protocol, and choose Pangolin SSH mode. This mode requires only that Newt runs as root on the target machine. No changes to sshd_config and no PAM configuration are needed. Assign a subdomain and an access policy and the terminal is live in the browser.
For RDP, select RDP as the protocol, point the target at the Windows host’s IP and port 3389, and assign a domain. The session renders in the browser with full clipboard and file transfer support.
Browser-based SSH, RDP, and VNC require Newt 1.13.0 or later. Run docker compose pull && docker compose up -d on your home server before enabling these resource types.
Hardening Checklist
Before exposing anything sensitive, work through this list:
- Set a strong admin password and change it immediately if you used a weak one during initial setup.
- Restrict dashboard access to known IP addresses via a Pangolin access policy.
- Keep Pangolin, Gerbil, and Traefik images updated. Run `docker compose pull && docker compose up -d` from `/opt/pangolin` regularly. Pangolin 1.19 added optional automatic Newt updates you can enable per-site from the dashboard.
- Confirm your Contabo Firewall rules allow only the ports you need.
- Enable CrowdSec as an optional Traefik plugin if your resources face public traffic. It blocks known-bad IPs at the edge before requests reach your services.
- Back up the `config/` directory on the VPS regularly. It holds the SQLite database, TLS certificates, and all resource configuration.
FAQ: Self-Hosting Pangolin
The Pangolin, Gerbil, and Traefik containers together idle at a few hundred MB of RAM. A VPS with 4 GB RAM covers the stack comfortably with room for several active tunnels. If you plan to run additional services on the same VPS – a monitoring stack, a wiki, or a database – step up to 8 GB or more so the services do not compete under load.
Not with the standard setup. Traefik uses the Let’s Encrypt HTTP-01 challenge to provision certificates, which requires a domain with a valid A record pointing at the VPS. A domain costs a few euros per year and is worth it for any persistent remote access setup. Pangolin Cloud is an alternative if you want the control plane managed for you without running a VPS at all.
Newt is Pangolin’s site connector: a user-space WireGuard tunnel client that runs on your private network and establishes an outbound encrypted connection to the Pangolin server. Because Newt initiates the connection outbound, your home router needs no open ports, not even WireGuard’s default UDP 51820. Newt also acts as a TCP/UDP proxy, forwarding traffic from the Pangolin reverse proxy to services anywhere on your local network.
From the `/opt/pangolin` directory, run `docker compose pull && docker compose up -d`. Always back up the `config/` directory first. It contains the SQLite database and TLS certificates, and there is no easy downgrade path once migrations have run. For Newt on your home server, the same `docker compose pull && docker compose up -d` applies. Pangolin 1.19 introduced optional automatic Newt updates: enable them in the dashboard at the organization or per-site level to keep edge devices current without manual intervention.