Clustering is only available in Enterprise Edition.
The complete, working set of files used in this guide lives in the Pangolin repository at
config/ha-reference. Clone it as a starting point instead of assembling files by hand.
You need a domain for the Pangolin UI and API (
pangolin.example.com in this guide), pointed at your load balancer. The load balancer is responsible for TLS on this domain - terminate HTTPS there and forward plain HTTP to the nodes’ dashboard port. The nodes’ built-in ACME client only issues certificates for resource domains under the delegated nameserver zone, not for the dashboard domain itself. See Requirements.
1
Provision the shared database
Stand up a PostgreSQL server and a Redis-compatible server that both nodes can reach. They don’t need to run together, or even on a dedicated third host - use whatever you already run, including managed cloud offerings. The only hard requirement is that the Redis-compatible server supports pub/sub.For a simple self-hosted starting point:
docker-compose.yml
2
Lay out each node's config directory
On each of Node 1 and Node 2, create the following directory structure:
config/certificates and config/dynamic are shared volumes between the pangolin and traefik containers - Pangolin writes router configuration and certificates there for Traefik to read, since Traefik can only load certificates from files, not from the Pangolin API. This is traefik.file_mode in config.yml, covered below.3
Write docker-compose.yml
Both nodes run the same three containers:
pangolin, gerbil, and traefik. Gerbil owns the host networking (WireGuard, relay, DNS, resource ports), and Traefik joins its network namespace so its ports appear alongside Gerbil’s.Each node’s --reachableAt flag must point at that node’s own internal address, and --trusted-upstreams lists the external IPs of every node in the cluster so Gerbil accepts proxied connections from them.4
Write config.yml
config.yml holds the settings that legitimately differ per node - gerbil.base_endpoint and gerbil.exit_node_name - alongside the shared PostgreSQL connection and site-type restrictions. In clustered deployments, only Newt sites are supported, so local and basic WireGuard sites are disabled.Set app.dashboard_url and server.cors.origins to your dashboard domain (the one pointed at your load balancer, not at either node) - both must match on every node.5
Write privateConfig.yml
privateConfig.yml enables Redis-backed cluster sync and Pangolin’s built-in DNS and ACME client. Only one node - Node 1 in this example - should have acme.enable_acme_client set to true. That node issues and renews certificates via DNS-01 challenges and stores them encrypted in PostgreSQL; every other node reads the same certificates from the database.6
Write the Traefik configuration
traefik/traefik_config.yml and dynamic/dynamic_config.yml are identical on every node - copy them as-is. Traefik loads router and certificate configuration from the shared dynamic volume (file_mode) instead of Pangolin’s API, and exposes a :53/udp DNS entry point that forwards to Pangolin’s built-in DNS server.config/traefik/traefik_config.yml
config/dynamic/dynamic_config.yml
config.yml - download GeoLite2-Country.mmdb and GeoLite2-ASN.mmdb into each node’s config/ directory. See Enable Geo-location and Enable ASN Lookup.7
Point your load balancer at both nodes
Configure your load balancer - a cloud load balancer or a self-hosted one such as Traefik - to:
- Route TCP
3000to both nodes for your Pangolin domain. For examplepangolin.example.comshould resolve to the load balancer, which routes to either node’s:3000port. - Route UDP
53to both nodes for DNS. For examplens.example.comshould resolve to the load balancer, which routes to either node’s:53/udpport. - Health-check the
:80/pingendpoint on each node, and stop routing to a node that fails it.:3000/api/v1/can also be monitored for the Pangolin UI and API - Terminate TLS for the dashboard domain (
pangolin.example.com) at the load balancer, then forward plain HTTP to:3000on the nodes. Obtain and renew that certificate through the load balancer itself (a cloud provider’s managed certificate, its own ACME client, etc.) - the nodes’ built-in ACME client only covers resource domains, not the dashboard domain
8
Start the cluster
Bring the database up first, then start one Pangolin node - it initializes the database and prints an init token to its logs.Use the init token from the logs to visit the dashboard and create the first user. Once Node 1 is healthy and you’ve logged in, bring up Node 2:Repeat the Node 2 steps for any additional nodes, incrementing the
exit_node_name and IP placeholders for each.9
Verify the cluster
- Confirm both nodes report healthy:
curl http://<NODE1_EXTERNAL_IP>/pingand the same for Node 2 - Confirm DNS delegation resolves:
dig @ns.example.com ns.example.com - Confirm the dashboard is reachable at your
dashboard_urlthrough the load balancer - Confirm you can create a site and it will report connected
- Create a resource, ensure the certificate generates, and is accessible
Troubleshooting
Gerbil loopback addressing
If Pangolin can’t reach the local Gerbil instance at the IP configured in--reachableAt, force it to address the Docker container directly instead:
privateConfig.yml
Reference Configuration
A complete, working two-node reference configuration on GitHub - including both nodes’ Docker Compose and config files - that you can clone and adapt: github.com/fosrl/pangolin/tree/main/config/ha-reference

