Skip to main content
Clustering is only available in Enterprise Edition.
This guide walks through deploying a minimal two-node Pangolin cluster: two Pangolin nodes behind a load balancer, sharing a PostgreSQL database and a Valkey (Redis) server. Read Understanding Clustering for the architecture and Requirements for the hosts, ports, and DNS records you need before starting.
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.
server.secret in config.yml must be identical on every node in the cluster. It’s used to encrypt sensitive data, including the certificates stored in PostgreSQL - if nodes have different secrets, they won’t be able to read each other’s data.
Throughout this guide, replace the following placeholders with your own values: 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
Change the default PostgreSQL password before running this in production. See Database Options for general PostgreSQL configuration.
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.
If Pangolin can’t reach the local Gerbil at the address in --reachableAt (a loopback issue), override it in privateConfig.yml - see Troubleshooting below.
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.
See the full configuration reference for every available option.
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.
Do not enable acme.enable_acme_client on more than one node. Multiple nodes issuing certificates simultaneously will conflict with each other.
See the private configuration reference for every available option.
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
This is also where you place the MaxMind databases referenced in 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 3000 to both nodes for your Pangolin domain. For example pangolin.example.com should resolve to the load balancer, which routes to either node’s :3000 port.
  • Route UDP 53 to both nodes for DNS. For example ns.example.com should resolve to the load balancer, which routes to either node’s :53/udp port.
  • Health-check the :80/ping endpoint 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 :3000 on 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
This is what makes the cluster appear as a single, consistent domain to users, and what drives failover when a node goes down.
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>/ping and the same for Node 2
  • Confirm DNS delegation resolves: dig @ns.example.com ns.example.com
  • Confirm the dashboard is reachable at your dashboard_url through 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