Skip to content

Pangolin

TL;DR / when to use this: Pangolin tunnels self-hosted apps through a small VPS so you get public HTTPS, CrowdSec protection, and no port forwarding on your home network. The Gerbil tunnel connects the VPS to your home server. This covers install, the cscli day-to-day, and the two flavors of passthrough — raw TCP (encrypted end-to-end, no real IP) and proxy protocol (encrypted with real IP restored at Nginx).

Quick install

Run the official installer — it sets up the Docker project for you. Open the ports you'll use on the VPS.

Day-to-day CrowdSec commands

Pangolin's Traefik runs as a CrowdSec bouncer, so most operations are cscli against the CrowdSec container:

docker exec -it crowdsec cscli bouncers list      # current bouncers
docker exec crowdsec cscli metrics                # parsing metrics
docker exec crowdsec cscli decisions list         # current bans
docker exec crowdsec cscli alerts list
docker exec crowdsec cscli scenarios list
docker exec crowdsec cscli collections list
docker exec crowdsec cscli scenarios inspect crowdsecurity/traefik-bf

# test a ban on yourself, then remove it
sudo docker exec crowdsec cscli decisions add --ip <YOUR IP> --duration 1m --reason "test"
sudo docker exec crowdsec cscli decisions delete --ip <YOUR IP>

Metrics dashboard

Two options:

  1. CrowdSec Console — enroll with docker exec -it crowdsec cscli console enroll -e context <cmd>, accept in app.crowdsec.net, restart the container.
  2. Self-hosted — the example compose with Prometheus + Metabase (default credentials in that repo's README).

Raw TCP passthrough (end-to-end encrypted)

If you don't want the VPS to decrypt traffic, use a Raw TCP/UDP Resource instead of an HTTPS Resource — Pangolin always terminates HTTP(S) on HTTPS resources, even when tunneling to your own Nginx.

Example (port 4433 → Immich):

  1. docker-compose.yml: add - 4433:4433 to the port mappings.
  2. traefik_config.yml:

    tcp-4433:
      address: ":4433/tcp"
    
  3. Open 4433 on the VPS.

  4. In Pangolin, create a Raw TCP/UDP resource with protocol TCP, port 4433, target = your server's local IP on its HTTPS port (443).

Connect via https://immich.example.com:4433.

Downsides:

  • No real client IP at the home reverse proxy — you only see the Gerbil Docker IP. Traefik logs show the real IP for the initial connection only, not per-request access logs.
  • No Traefik middleware benefits (CrowdSec, GeoIP blocking) on that resource.

These downsides outweigh the encryption benefit unless you can restore the real IP — which the proxy protocol below does.

Proxy protocol passthrough (encrypted + real IP)

The fix for the raw-TCP logging problem: Nginx on the home server accepts the PROXY protocol header from Pangolin and restores the real client IP.

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    listen 4443 ssl proxy_protocol;
    listen [::]:4443 ssl proxy_protocol;

    server_name immich.example.com;

    include /config/nginx/ssl.conf;
    include /config/nginx/proxy.conf;

    # 1. Trust the internal range Pangolin connects from
    set_real_ip_from 172.16.0.0/12;   # adjust to your Docker network
    set_real_ip_from 10.0.0.0/8;

    # 2. Read the real IP from the PROXY protocol header
    real_ip_header proxy_protocol;

    # 3. Conditional override for reliability
    set $real_client_ip $remote_addr;
    if ($proxy_protocol_addr != "") {
        set $real_client_ip $proxy_protocol_addr;
    }
    proxy_set_header X-Forwarded-For $real_client_ip;
    proxy_set_header X-Real-IP $real_client_ip;

    location / {
        proxy_pass http://127.0.0.1:2283;
    }
}

See also