port mintlify to fumadocs

This commit is contained in:
miloschwartz
2026-09-25 15:31:51 -04:00
parent dc54fb1017
commit 63199a588c
373 changed files with 13533 additions and 4577 deletions
@@ -0,0 +1,140 @@
---
title: "Cloudflare Proxy"
---
Pangolin works with Cloudflare proxy (orange cloud) enabled, but requires specific configuration:
<Warning>
**Terms of Service**: Enabling Cloudflare proxy binds you to Cloudflare's terms of service as traffic routes through their network.
</Warning>
### SSL Configuration
**Recommended setup:**
1. **Use wildcard certificates** with DNS-01 challenge
2. **Set SSL/TLS mode to Full (Strict)**
3. **Disable port 80** (not needed with wildcard certs)
<Info>
Pangolin will **not work** with Cloudflare's Full or Automatic SSL/TLS modes. Only Full (Strict) mode is supported.
</Info>
### WireGuard Configuration
Since Cloudflare proxy obscures the destination IP, you must explicitly set your VPS IP in the [config file](/self-host/advanced/config-file):
```yaml
gerbil:
base_endpoint: "YOUR_VPS_IP_ADDRESS" # Required with Cloudflare proxy
```
<Steps>
<Step title="Get your VPS IP">
Find your VPS public IP address:
```bash
curl ifconfig.io
```
</Step>
<Step title="Update configuration">
Add the IP to your `config.yml`:
```yaml
gerbil:
base_endpoint: "104.21.16.1" # Replace with your actual IP
```
</Step>
<Step title="Restart services">
Restart Pangolin to apply the changes:
```bash
docker-compose restart
```
</Step>
</Steps>
### Getting the Real Client IP
Pangolin needs to know the original client IP address for features like rate limiting and logging. When Cloudflare proxy is enabled, the API server sees Cloudflare's IP instead of the real client IP.
**Badger**, Pangolin's middleware for Traefik, automatically handles Cloudflare proxy IP extraction. Badger versions 1.3.0 and later automatically:
- Trust Cloudflare IP ranges
- Extract the real client IP from the `CF-Connecting-IP` header
- Set `X-Real-IP` and `X-Forwarded-For` headers for downstream services
<Info>
**Automatic Configuration**: Pangolin installer versions 1.14.0 and greater automatically add Badger to all Pangolin routes in Traefik. If you're using a newer installer, no manual configuration is needed.
</Info>
#### Manual Configuration
If you're using an older installer or need to manually configure Badger, add it to your Traefik configuration. Badger must be applied to all routers that handle Pangolin traffic (API, dashboard, and WebSocket routes):
```yaml title="dynamic_config.yml"
http:
middlewares:
badger:
plugin:
badger:
disableForwardAuth: true
routers:
# Next.js router (handles dashboard)
next-router:
rule: "Host(`pangolin.example.com`) && !PathPrefix(`/api/v1`)"
service: next-service
entryPoints:
- websecure
middlewares:
- badger
tls:
certResolver: letsencrypt
# API router (handles /api/v1 paths)
api-router:
rule: "Host(`pangolin.example.com`) && PathPrefix(`/api/v1`)"
service: api-service
entryPoints:
- websecure
middlewares:
- badger
tls:
certResolver: letsencrypt
# WebSocket router
ws-router:
rule: "Host(`pangolin.example.com`)"
service: api-service
entryPoints:
- websecure
middlewares:
- badger
tls:
certResolver: letsencrypt
```
**Why Badger is needed**: When `disableForwardAuth: true` is set, Badger extracts the real client IP from Cloudflare proxy headers without performing authentication. This is necessary because forward authentication is only needed for resources controlled by Pangolin, not for the main application routes. However, the main Pangolin containers and APIs still need the real client IP for proper rate limiting and IP tracking.
#### Pangolin Configuration
Set `trust_proxy: 2` in your Pangolin config file. This tells Pangolin to trust the second-level proxy (Traefik is proxy 1, Cloudflare is proxy 2):
```yaml
server:
trust_proxy: 2
```
<Warning>
**Update Badger**: Ensure you're running Badger version 1.3.0 or later to get real IP addresses in logs for Public resources. Update Badger if you're using an older version.
</Warning>
After making these changes, restart both Traefik and Pangolin for the configuration to take effect.
### Troubleshooting
If websockets are not connecting from sites or clients, ensure that websockets are enabled in Cloudflare:
<Frame>
<img src="/images/cf_websocket_box.png" alt="Cloudflare dashboard WebSockets setting toggled on" width="600"/>
</Frame>
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,184 @@
---
title: "Internal CLI (pangctl)"
description: "Command-line tool for managing your Pangolin instance"
---
The Pangolin container includes a CLI tool called `pangctl` that provides commands to help you manage your Pangolin instance.
## Accessing the CLI
Run the following command on the host where the Pangolin container is running:
```bash
docker exec -it pangolin pangctl <command>
```
## Available Commands
To see all available commands:
```bash
docker exec -it pangolin pangctl --help
```
## Set Admin Credentials
Set or reset admin credentials for your Pangolin instance:
```bash
docker exec -it pangolin pangctl set-admin-credentials --email "admin@example.com" --password "Password123!"
```
<Warning>
Use a strong password and keep your admin credentials secure.
</Warning>
## Set Server Admin
Add or remove server admin status for a user by email address:
```bash
docker exec -it pangolin pangctl set-server-admin --email "admin@example.com"
```
To remove server admin status:
```bash
docker exec -it pangolin pangctl set-server-admin --email "admin@example.com" --remove
```
### Options
- `--email` (required): User email address
- `--remove` (optional, default: `false`): Remove server admin status from the user
<Warning>
At least one server admin must always exist. The command fails if you try to remove server admin status from the last remaining server admin.
</Warning>
## Clear Exit Nodes
Clear all exit nodes from the database:
```bash
docker exec -it pangolin pangctl clear-exit-nodes
```
<Warning>
This command permanently deletes all exit nodes from the database. This action cannot be undone.
</Warning>
## Reset User Security Keys
Reset a user's security keys (passkeys) by deleting all their webauthn credentials:
```bash
docker exec -it pangolin pangctl reset-user-security-keys --email "user@example.com"
```
<Warning>
This command permanently deletes all security keys for the specified user. The user will need to re-register their security keys to use passkey authentication again.
</Warning>
## Disable User 2FA
Disable two-factor authentication for a user by email address. Sets `twoFactorEnabled` to false and clears the user's 2FA secret:
```bash
docker exec -it pangolin pangctl disable-user-2fa --email "user@example.com"
```
### Options
- `--email` (required): User email address
<Warning>
This command disables 2FA for the specified user and clears their stored 2FA secret. The user can re-enable 2FA from their account settings after signing in.
</Warning>
## Rotate Server Secret
Rotate the server secret by decrypting all encrypted values with the old secret and re-encrypting with a new secret. This command updates OIDC IdP configurations and license keys in the database, as well as the config file.
```bash
docker exec -it pangolin pangctl rotate-server-secret --old-secret "current-secret" --new-secret "new-secret"
```
### Options
- `--old-secret` (required): The current server secret (for verification)
- `--new-secret` (required): The new server secret to use (must be at least 8 characters long)
- `--force` (optional): Force rotation even if the old secret doesn't match the config file. Use this if you know the old secret is correct but the config file is out of sync.
<Warning>
This command performs a critical operation that affects all encrypted data in your database. Ensure you have a backup before running this command.
**Important considerations:**
- The new secret must be at least 8 characters long
- The new secret must be different from the old secret
- The command verifies the old secret matches the config file (unless `--force` is used)
- After rotation, you must restart the server for the new secret to take effect
- Using `--force` with an incorrect old secret will cause the rotation to fail or corrupt encrypted data
</Warning>
## Clear License Keys
Clear all license keys from the database:
```bash
docker exec -it pangolin pangctl clear-license-keys
```
<Warning>
This command permanently deletes all license keys from the database. This action cannot be undone.
</Warning>
## Delete Client
Delete a client and all associated data (OLMs, current fingerprint, userClients, approvals). Snapshots are preserved.
```bash
docker exec -it pangolin pangctl delete-client --orgId "org-123" --niceId "client-identifier"
```
### Options
- `--orgId` (required): The organization ID
- `--niceId` (required): The client niceId (identifier)
<Warning>
This command permanently deletes the client and its associated data:
- All OLMs (One-time Login Mechanisms) associated with the client
- Current fingerprint entries
- Approval records
- UserClient associations
**Note:** Snapshots are preserved and will not be deleted.
This action cannot be undone. Ensure you have backups if needed.
</Warning>
## Generate Org CA Keys
Generate an SSH CA public/private key pair for an organization and store them in the database. The private key is encrypted with the server secret.
```bash
docker exec -it pangolin pangctl generate-org-ca-keys --orgId "org-123"
```
## Clear Certificates
Clear all certificates from the database to be reinserted by the server when syncing from acme.json files or using Pangolin DNS.
```bash
docker exec -it pangolin pangctl clear-certificates
```
### Options
- `--orgId` (required): The organization ID
- `--secret` (optional): Server secret used to encrypt the CA private key. If omitted, the secret is read from the config file (`config.yml` or `config.yaml` in the config directory).
- `--force` (optional, default: `false`): Overwrite existing CA keys for the organization if they already exist
<Warning>
If the organization already has CA keys, the command fails unless you pass `--force`. Using `--force` overwrites the existing keys; ensure you have a backup or understand the impact before overwriting.
</Warning>
@@ -0,0 +1,143 @@
---
title: "Database Options"
description: "Configure SQLite or PostgreSQL database for Pangolin"
---
Pangolin supports two database options: SQLite for simplicity and PostgreSQL for production deployments.
<CardGroup cols={2}>
<Card title="SQLite (Default)" icon="database">
- No configuration required
- Easy to use and portable
- Built into the main image
- Perfect for development
</Card>
<Card title="PostgreSQL" icon="database">
- Production-ready database
- Better performance at scale
- Requires separate image
- Advanced configuration options
</Card>
</CardGroup>
## SQLite
By default, Pangolin uses SQLite for its ease of use and portability.
**Docker Image**: `fosrl/pangolin:<version>`
<Note>
No configuration is required to use SQLite with Pangolin.
</Note>
## PostgreSQL
You can optionally use PostgreSQL for production deployments.
**Docker Image**: `fosrl/pangolin:postgresql-<version>`
### Configuration
Add the following section to your Pangolin configuration file:
```yaml title="config.yml"
postgres:
connection_string: postgresql://<user>:<password>@<host>:<port>/<database>
```
<Warning>
Replace the placeholders with your actual PostgreSQL connection details.
</Warning>
### Docker Compose Example
This example sets up PostgreSQL with health checks to ensure the database is ready before Pangolin starts:
```yaml title="docker-compose.yml"
name: pangolin
services:
pangolin:
image: fosrl/pangolin:postgresql-latest # Don't use latest in production
container_name: pangolin
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
volumes:
- ./config:/app/config
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3001/api/v1/"]
interval: "10s"
timeout: "10s"
retries: 15
# ... other services ...
postgres:
image: postgres:17
container_name: postgres
restart: unless-stopped
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
volumes:
- ./config/postgres:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
```
<Warning>
This example is not necessarily production-ready. Adjust the configuration according to your needs and security requirements.
</Warning>
<Note>
Do not use `latest` tags in production. Use specific version tags for stability.
</Note>
### Read Replicas
Pangolin can distribute read queries across one or more PostgreSQL read replicas while always sending writes to the primary database. This is useful for scaling read-heavy workloads.
<Note>
Replicas are chosen at random for each read query (not round-robin). Writes (`insert`, `update`, `delete`) always go to the primary database. A small number of time-sensitive reads (where the app must see its own recent writes) are also routed directly to the primary database regardless of replicas being configured.
</Note>
#### Using the Configuration File
Add a `replicas` array under `postgres` in your `config.yml`, with one entry per replica:
```yaml title="config.yml"
postgres:
connection_string: postgresql://<user>:<password>@<primary-host>:<port>/<database>
replicas:
- connection_string: postgresql://<user>:<password>@<replica-host-1>:<port>/<database>
- connection_string: postgresql://<user>:<password>@<replica-host-2>:<port>/<database>
```
#### Using Environment Variables
You can instead provide replica connection strings with the `POSTGRES_REPLICA_CONNECTION_STRINGS` environment variable, as a comma-separated list. This must be used together with `POSTGRES_CONNECTION_STRING` for the primary database - the two env vars replace the entire `postgres.connection_string` / `postgres.replicas` config as a unit.
```bash title=".env"
POSTGRES_CONNECTION_STRING=postgresql://<user>:<password>@<primary-host>:<port>/<database>
POSTGRES_REPLICA_CONNECTION_STRINGS=postgresql://<user>:<password>@<replica-host-1>:<port>/<database>,postgresql://<user>:<password>@<replica-host-2>:<port>/<database>
```
```yaml title="docker-compose.yml"
services:
pangolin:
image: fosrl/pangolin:postgresql-latest # Don't use latest in production
environment:
POSTGRES_CONNECTION_STRING: postgresql://<user>:<password>@<primary-host>:<port>/<database>
POSTGRES_REPLICA_CONNECTION_STRINGS: "postgresql://<user>:<password>@<replica-host-1>:<port>/<database>,postgresql://<user>:<password>@<replica-host-2>:<port>/<database>"
```
<Note>
The same pattern applies to the optional dedicated logs database: `postgres_logs.replicas` in the config file, or the `POSTGRES_LOGS_REPLICA_CONNECTION_STRINGS` environment variable (comma-separated) alongside `POSTGRES_LOGS_CONNECTION_STRING`.
</Note>
See the [`postgres.replicas` reference](/self-host/advanced/config-file#database-configuration) for the full config schema, and the [Environment Variables reference](/self-host/advanced/config-file#environment-variables) for all supported variables.
@@ -0,0 +1,73 @@
---
title: "Enable ASN Lookup"
description: "Configuration requirements to enable ASN lookup features in Pangolin"
---
Pangolin uses an ASN database to map request IP addresses to autonomous systems, such as cloud providers, residential ISPs, VPN providers, and corporate networks. That ASN data powers ASN access rules and ASN blocking patterns.
To enable ASN lookup features in Pangolin self-hosted, download the MaxMind ASN database, place it in the `config/` directory, and point Pangolin at the database file. This can be done for free.
<Tip>
Remember to keep the ASN database updated regularly, as ASN assignments and network mappings can change over time. You can just repeat the download and extraction steps periodically to ensure your database is current.
</Tip>
<Tip>
You can automate this process with a MaxMind Docker container. See the [GeoLite2 Automation community guide](/self-host/community-guides/geolite2automation) for an example.
</Tip>
## Install with the Installer
You can use the installer to download and place the database for you. Download the latest installer:
```bash
curl -fsSL https://static.pangolin.net/get-installer.sh | bash
```
Then run the installer again:
```bash
./installer
```
## Manual Installation
<Steps>
<Step title="Download and extract the ASN database">
Download and extract the GeoLite2 ASN database using the following commands:
```bash
# Download the GeoLite2 ASN database
curl -L -o GeoLite2-ASN.tar.gz https://github.com/GitSquared/node-geolite2-redist/raw/refs/heads/master/redist/GeoLite2-ASN.tar.gz
# Extract the database
tar -xzf GeoLite2-ASN.tar.gz
# Move the .mmdb file to the config directory
mv GeoLite2-ASN_*/GeoLite2-ASN.mmdb config/
# Clean up the downloaded files
rm -rf GeoLite2-ASN.tar.gz GeoLite2-ASN_*
```
</Step>
<Step title="Update the Pangolin config file">
Update your Pangolin configuration to point to the new ASN database file. Edit `config/config.yml` to include the following entry:
```yaml
server:
maxmind_asn_path: "./config/GeoLite2-ASN.mmdb"
```
</Step>
<Step title="Restart Pangolin">
Restart your Pangolin instance to apply the changes:
```bash
docker compose restart pangolin
```
</Step>
</Steps>
Alternatively, you can create an account at [MaxMind](https://www.maxmind.com/en/geolite2/signup) to get a license key and download the database directly from them.
<Note>
After the ASN lookup database is enabled, use [access control rules](/manage/access-control/rules) or the [ASN Blocking](/manage/asnblocking) guide to create blocking or allow rules.
</Note>
@@ -0,0 +1,73 @@
---
title: "Enable Geo-location"
description: "Configuration requirements to enable geolocation features in Pangolin"
---
Pangolin uses a GeoIP database to map request IP addresses to approximate locations. That location data powers multiple features, including country and region access rules, geo-blocking patterns, and analytics.
To enable geo-location features in Pangolin self-hosted, download a MaxMind GeoIP database, place it in the `config/` directory, and point Pangolin at the database file. This can be done for free.
<Tip>
Remember to keep the GeoIP database updated regularly, as IP-to-country mappings can change over time. You can just repeat the download and extraction steps periodically to ensure your database is current.
</Tip>
<Tip>
You can automate this process with a MaxMind Docker container. See the [GeoLite2 Automation community guide](/self-host/community-guides/geolite2automation) for an example.
</Tip>
## Install with the Installer
You can use the installer to download and place the database for you. Download the latest installer:
```bash
curl -fsSL https://static.pangolin.net/get-installer.sh | bash
```
Then run the installer again:
```bash
./installer
```
## Manual Installation
<Steps>
<Step title="Download and extract the GeoIP database">
Download and extract the GeoLite2 Country database using the following commands:
```bash
# Download the GeoLite2 Country database
curl -L -o GeoLite2-Country.tar.gz https://github.com/GitSquared/node-geolite2-redist/raw/refs/heads/master/redist/GeoLite2-Country.tar.gz
# Extract the database
tar -xzf GeoLite2-Country.tar.gz
# Move the .mmdb file to the config directory
mv GeoLite2-Country_*/GeoLite2-Country.mmdb config/
# Clean up the downloaded files
rm -rf GeoLite2-Country.tar.gz GeoLite2-Country_*
```
</Step>
<Step title="Update the Pangolin config file">
Update your Pangolin configuration to point to the new GeoIP database file. Edit `config/config.yml` to include the following entry:
```yaml
server:
maxmind_db_path: "./config/GeoLite2-Country.mmdb"
```
</Step>
<Step title="Restart Pangolin">
Restart your Pangolin instance to apply the changes:
```bash
docker compose restart pangolin
```
</Step>
</Steps>
Alternatively, you can create an account at [MaxMind](https://www.maxmind.com/en/geolite2/signup) to get a license key and download the database directly from them.
<Note>
After the geo-location database is enabled, use [access control rules](/manage/access-control/rules) or the [Geo-blocking](/manage/geoblocking) guide to create blocking or allow rules.
</Note>
@@ -0,0 +1,85 @@
---
title: "Increasing Site and Client Capacity"
description: "Understand the Gerbil subnet hierarchy and how to raise the number of sites and clients an exit node can serve"
---
Every Gerbil exit node hands out its own persistent WireGuard subnet, and every site or client that connects to that exit node gets its own smaller subnet carved out of it. By default this hierarchy only leaves room for a limited number of sites and clients per exit node. If you're running a large deployment and are hitting that ceiling, you can raise it by changing three related settings in `config.yml`.
## How the subnet hierarchy works
There are three [`gerbil`](/self-host/advanced/config-file#gerbil-tunnel-controller) settings that work together, each nested inside the one before it:
1. **`subnet_group`** - The overall CIDR range that exit node subnets are carved from. This is the outermost container.
2. **`block_size`** - The size of the subnet an exit node reserves for itself out of `subnet_group` when it registers. This is the persistent subnet for that exit node.
3. **`site_block_size`** - The size of the subnet each site or client reserves for itself out of its exit node's `block_size` block when it connects.
In other words: `subnet_group` must be large enough to contain many `block_size` blocks (one per exit node), and each `block_size` block must be large enough to contain many `site_block_size` blocks (one per site or client).
With the defaults, this looks like:
```yaml title="config.yml"
gerbil:
subnet_group: "100.89.137.0/20" # 4,096 addresses total
block_size: 24 # 256 addresses per exit node
site_block_size: 32 # 1 address per site/client
```
- A `/20` `subnet_group` holds 16 non-overlapping `/24` blocks, so it can support up to **16 exit nodes**.
- A `/24` `block_size` holds 64 non-overlapping `/30` blocks, so each exit node can support up to **64 sites and clients**.
<Note>
Smaller numbers after the slash mean *more* addresses (a `/22` is bigger than a `/24`). Increasing a block size means moving to a smaller number, and it always shrinks how many of the next-larger container it can fit into - which is why growing `block_size` usually means you also need to grow `subnet_group`.
</Note>
## Increasing the number of sites and clients per exit node
If your exit nodes are running out of room for sites and clients, increase `block_size` so each exit node reserves a bigger subnet. Because a bigger `block_size` block takes up more of `subnet_group`, you should also grow `subnet_group` at the same time so it can still fit as many exit nodes as you need.
For example, to go from 64 sites/clients per exit node to 1,024, and keep room for 16 exit nodes:
```yaml title="config.yml"
gerbil:
subnet_group: "100.64.0.0/16" # widened to fit more /22 blocks
block_size: 22 # 1,024 addresses per exit node (256 sites/clients * 4)
site_block_size: 32 # unchanged - 1 address per site/client
```
<Tip>
Pick CGNAT range addresses (`100.64.0.0/10`) for `subnet_group` to avoid conflicting with typical private networks, the same as the default.
</Tip>
You can also raise `site_block_size` (e.g. from `/30` to `/29` or `/28`) if individual sites need more addresses for heavy WireGuard usage, but doing so reduces how many sites/clients fit in each exit node's block, so weigh that trade-off against your capacity needs.
## Applying the change
Changing any of `subnet_group`, `block_size`, or `site_block_size` changes the addressing scheme for every exit node, site, and client, so existing exit node records need to be cleared out and re-created against the new ranges.
<Steps>
<Step title="Update config.yml">
Edit the `gerbil.subnet_group`, `gerbil.block_size`, and/or `gerbil.site_block_size` values on every node in your deployment (they must match everywhere).
</Step>
<Step title="Clear exit nodes from the database">
Use `pangctl` to remove existing exit node records so they get re-created using the new ranges:
```bash
docker exec -it pangolin pangctl clear-exit-nodes
```
See [Clear Exit Nodes](/self-host/advanced/container-cli-tool#clear-exit-nodes) for details.
</Step>
<Step title="Restart the full stack">
Restart every container in your Pangolin stack (Pangolin, Gerbil, Traefik, etc.) so the exit node re-registers with the new subnet settings.
</Step>
<Step title="Restart sites and clients">
Existing Newt sites and Olm clients may need to be restarted to pick up new addresses from their exit node and reconnect.
</Step>
</Steps>
<Warning>
Clearing exit nodes and changing the subnet hierarchy re-addresses every site and client connected through them. Plan for a maintenance window, since sites and clients will disconnect until they reconnect with their new address.
</Warning>
@@ -0,0 +1,69 @@
---
title: "Enable Integration API"
description: "Enable and configure the Integration API for external access"
---
The Integration API provides programmatic access to Pangolin functionality. It includes OpenAPI documentation via Swagger UI.
## Enable Integration API
Update your Pangolin configuration file:
```yaml title="config.yml"
flags:
enable_integration_api: true
```
If you want to specify a port other than the default `3003`, you can do so in the config as well:
```yaml title="config.yml"
server:
integration_port: 3003 # Specify different port
```
## Configure Traefik Routing
Add the following configuration to your `config/traefik/dynamic_config.yml` to expose the Integration API at `https://api.example.com/v1`:
```yaml title="dynamic_config.yml"
routers:
# Add the following two routers
int-api-router-redirect:
rule: "Host(`api.example.com`)"
service: int-api-service
entryPoints:
- web
middlewares:
- redirect-to-https
- badger # If you have Badger >=1.3.0 and it's enabled in the middlewares section of the dynamic config
int-api-router:
rule: "Host(`api.example.com`)"
service: int-api-service
entryPoints:
- websecure
tls:
certResolver: letsencrypt
services:
# Add the following service
int-api-service:
loadBalancer:
servers:
- url: "http://pangolin:3003"
```
## Access Documentation
Once configured, access the Swagger UI documentation at:
```
https://api.example.com/v1/docs
```
<Frame caption="Swagger UI documentation interface">
<img src="/images/swagger.png" alt="Swagger UI Preview"/>
</Frame>
<Note>
The Integration API will be accessible at `https://api.example.com/v1` for external applications.
</Note>
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,100 @@
---
title: "Database Options"
description: "Configure SQLite or PostgreSQL database for Pangolin"
---
## Overview
> Choose between SQLite (default) or PostgreSQL for your database
Pangolin supports two database options: SQLite for simplicity and PostgreSQL for production deployments.
<CardGroup cols={2}>
<Card title="SQLite (Default)" icon="database">
- No configuration required
- Easy to use and portable
- Built into the main image
- Perfect for development
</Card>
<Card title="PostgreSQL" icon="postgres">
- Production-ready database
- Better performance at scale
- Requires separate image
- Advanced configuration options
</Card>
</CardGroup>
## SQLite
By default, Pangolin uses SQLite for its ease of use and portability.
**Docker Image**: `fosrl/pangolin:<version>`
<Note>
No configuration is required to use SQLite with Pangolin.
</Note>
## PostgreSQL
You can optionally use PostgreSQL for production deployments.
**Docker Image**: `fosrl/pangolin:postgresql-<version>`
### Configuration
Add the following section to your Pangolin configuration file:
```yaml title="config.yml"
postgres:
connection_string: postgresql://<user>:<password>@<host>:<port>/<database>
```
<Warning>
Replace the placeholders with your actual PostgreSQL connection details.
</Warning>
### Docker Compose Example
This example sets up PostgreSQL with health checks to ensure the database is ready before Pangolin starts:
```yaml title="docker-compose.yml"
name: pangolin
services:
pangolin:
image: fosrl/pangolin:postgresql-latest
container_name: pangolin
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
volumes:
- ./config:/app/config
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3001/api/v1/"]
interval: "10s"
timeout: "10s"
retries: 15
postgres:
image: postgres:17
container_name: postgres
restart: unless-stopped
environment:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
volumes:
- ./config/postgres:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
```
<Warning>
This example is not necessarily production-ready. Adjust the configuration according to your needs and security requirements.
</Warning>
<Note>
Do not use `latest` tags in production. Use specific version tags for stability.
</Note>
@@ -0,0 +1,508 @@
---
title: "Private Configuration File"
description: "Configure advanced Pangolin settings using the privateConfig.yml file for enterprise features"
---
The `privateConfig.yml` file provides advanced configuration options for enterprise deployments. This file is mounted at `config/privateConfig.yml` in your Docker container.
<Note>
The private configuration file is only used on enterprise deployments. If you're using Pangolin Community, refer to the [main configuration file documentation](/self-host/advanced/config-file) instead. The private config file is not required.
</Note>
## Setting up your `privateConfig.yml`
Here's a basic example with common settings:
```yaml title="private-config.yml"
app:
identity_provider_mode: "org"
branding:
app_name: "My Company Portal"
hide_auth_layout_footer: false
```
## Reference
This section contains the complete reference for all configuration options in `private-config.yml`.
### Application Settings
<ResponseField name="app" type="object">
Regional and base domain configuration for multi-region deployments.
<Expandable title="properties">
<ResponseField name="identity_provider_mode" type="string" default="global">
Set the identity provider (IdP) mode for authentication. By default both global and org pages will show until set. See the [Identity Providers documentation](/manage/identity-providers/add-an-idp#identity-provider-types) for more details on how this affects authentication and user management.
Possible values:
- `global`: (default) Both global and organization-level IdP login pages are available. Users can authenticate using either global or organization-specific identity providers.
- `org`: Only organization-level IdP login pages are available. Users must authenticate using identity providers defined at the organization
```yaml
app:
identity_provider_mode: "org"
```
</ResponseField>
<ResponseField name="region" type="string" default="default">
The region identifier for this Pangolin instance. Used for multi-region deployments.
```yaml
app:
region: "us-east-1"
```
</ResponseField>
</Expandable>
</ResponseField>
### Server Configuration
<ResponseField name="server" type="object">
Advanced server configuration including encryption keys and API integrations.
<Expandable title="properties">
<ResponseField name="encryption_key" type="string" default="./config/encryption.pem" required>
Path to the RSA private key used for encrypting sensitive data. Must be at least 8 characters long. THIS IS ONLY USED WITH pangolin_dns FEATURE FLAG ENABLED AND REQUIRES EXTERNAL COMPONENTS.
```yaml
server:
encryption_key_path: "./config/encryption.pem"
```
<Warning>
The `encryption_key_path` must point to a valid RSA key file. Generate one using:
```bash
openssl genrsa -out encryption.pem 4096
```
Keep this key secure and backed up - it encrypts sensitive data in your database.
</Warning>
</ResponseField>
</Expandable>
</ResponseField>
### Redis Configuration
<ResponseField name="redis" type="object">
Redis connection settings for caching, sessions, and rate limiting. Useful for clustering Pangolin nodes.
<Expandable title="properties">
<ResponseField name="host" type="string" required>
Redis server hostname or IP address.
```yaml
redis:
host: "redis.example.com"
```
</ResponseField>
<ResponseField name="port" type="number" required>
Redis server port (1-65535).
```yaml
redis:
port: 6379
```
</ResponseField>
<ResponseField name="password" type="string">
Redis authentication password.
**Environment Variable**: `REDIS_PASSWORD` (or `REDIS_PASSWORD_FILE` to read the value from a file — see [Reading secrets from a file](/self-host/advanced/config-file#reading-secrets-from-a-file-_file-suffix))
```yaml
redis:
password: "your-secure-password"
```
</ResponseField>
<ResponseField name="db" type="number" default="0">
Redis database number (0-15 typically).
```yaml
redis:
db: 0
```
</ResponseField>
<ResponseField name="replicas" type="array">
Array of read replica configurations for high-availability deployments.
```yaml
redis:
host: "redis-primary"
port: 6379
replicas:
- host: "redis-replica-1"
port: 6379
password: "replica-password"
db: 0
- host: "redis-replica-2"
port: 6379
password: "replica-password"
db: 0
```
<Expandable title="replica properties">
<ResponseField name="host" type="string" required>
Replica server hostname.
</ResponseField>
<ResponseField name="port" type="number" required>
Replica server port.
</ResponseField>
<ResponseField name="password" type="string">
Replica authentication password.
</ResponseField>
<ResponseField name="db" type="number" default="0">
Database number on replica.
</ResponseField>
</Expandable>
</ResponseField>
</Expandable>
</ResponseField>
### DNS Server Configuration
<ResponseField name="dns" type="object">
Configuration for Pangolin's built-in authoritative DNS nameserver. This lets Pangolin answer DNS queries directly for domains delegated to it (via CNAME or NS delegation), issue ACME DNS-01 challenge responses, and resolve site tunnel subnet addresses.
<Expandable title="properties">
<ResponseField name="enabled" type="boolean" default="false">
Enables the authoritative DNS server. When `false` or omitted, no DNS listener is started.
```yaml
dns:
enabled: true
```
</ResponseField>
<ResponseField name="listen_port" type="number" default="53">
UDP port the authoritative DNS server listens on.
```yaml
dns:
listen_port: 53
```
</ResponseField>
<ResponseField name="nameserver_name" type="string" required>
The FQDN Pangolin advertises as itself when acting as a nameserver. Used as the `mname` in SOA responses and included in the NS record set returned for zones it is authoritative for. This is the hostname you point your domain's nameservers at when using NS-based domain delegation (e.g. `ns1.pangolin-ns.net`).
```yaml
dns:
nameserver_name: "ns1.pangolin-ns.net"
```
</ResponseField>
<ResponseField name="cname_extension" type="string" required>
The domain suffix used for single-domain CNAME delegation. When an org adds a domain using the CNAME delegation type, Pangolin generates `{domainId}.{cname_extension}` and `_acme-challenge.{domainId}.{cname_extension}` targets to point your records at.
```yaml
dns:
cname_extension: "cname.pangolin.net"
```
</ResponseField>
<ResponseField name="site_extension" type="string">
Suffix used to resolve a site's WireGuard tunnel subnet address by DNS. A query for `{newtId}.{site_extension}` resolves to the tunnel subnet IP of the site running the Newt agent with that ID. This is used for site-to-cloud networking.
```yaml
dns:
site_extension: "site.pangolin.net"
```
</ResponseField>
<ResponseField name="cname_alternate_extensions" type="array" default="[]">
Additional CNAME suffixes (besides `cname_extension`) that are treated the same way. Useful when Pangolin should respond to more than one CNAME delegation domain, such as for white-label/multi-brand deployments.
```yaml
dns:
cname_alternate_extensions:
- "cname.example.com"
```
</ResponseField>
<ResponseField name="alternate_nameservers" type="array" default="[]">
Additional nameserver hostnames appended after `nameserver_name` in the NS record set Pangolin returns for a zone, and in the NS records shown when using NS-based domain delegation (e.g. `ns2.pangolin-ns.net`, `ns3.pangolin-ns.net`).
```yaml
dns:
alternate_nameservers:
- "ns2.pangolin-ns.net"
- "ns3.pangolin-ns.net"
```
</ResponseField>
<ResponseField name="rate_limit" type="object">
Per-source-IP rate limiting for DNS queries. Queries exceeding these limits are refused.
```yaml
dns:
rate_limit:
enabled: true
window_ms: 60000
max_requests: 1200
max_requests_per_query_type: 600
```
<Expandable title="properties">
<ResponseField name="enabled" type="boolean" default="true">
Enables DNS query rate limiting.
</ResponseField>
<ResponseField name="window_ms" type="number" default="60000">
The time window, in milliseconds, over which query counts are measured.
</ResponseField>
<ResponseField name="max_requests" type="number" default="1200">
Maximum total DNS queries allowed per source IP within `window_ms`.
</ResponseField>
<ResponseField name="max_requests_per_query_type" type="number" default="600">
Maximum queries allowed per source IP, per DNS record type (A, TXT, NS, etc.), within `window_ms`.
</ResponseField>
</Expandable>
</ResponseField>
<ResponseField name="static_records" type="array" default="[]">
Hardcoded DNS answers served by the authoritative DNS server, checked before database-backed lookups. Useful for fixed entries such as domain verification TXT records.
```yaml
dns:
static_records:
- domain: "example.com"
type: "TXT"
value: "v=spf1 include:_spf.example.com ~all"
ttl: 300
```
<Expandable title="properties">
<ResponseField name="domain" type="string" required>
The domain name to match (case-insensitive).
</ResponseField>
<ResponseField name="type" type="string" required>
The DNS record type. One of `TXT`, `CNAME`, `A`, or `NS`.
</ResponseField>
<ResponseField name="value" type="string" required>
The value returned for this record.
</ResponseField>
<ResponseField name="ttl" type="number" default="300">
Time-to-live, in seconds, for the returned record.
</ResponseField>
</Expandable>
</ResponseField>
</Expandable>
</ResponseField>
### Gerbil Tunnel Configuration
<ResponseField name="gerbil" type="object">
Configuration for the Gerbil tunnel exit node integration.
<Expandable title="properties">
<ResponseField name="local_exit_node_reachable_at" type="string" default="http://gerbil:3004">
URL where the local Gerbil exit node can be reached by Pangolin. Useful when clustering multiple pangolin nodes. Overrides the value stored in the database. Useful when using Docker and address the local gerbil container using the host's address.
```yaml
gerbil:
local_exit_node_reachable_at: "http://gerbil:3004"
```
</ResponseField>
</Expandable>
</ResponseField>
### Feature Flags
<ResponseField name="flags" type="object">
Feature toggles for advanced functionality.
<Expandable title="properties">
<ResponseField name="use_org_only_idp" type="boolean" default="false">
**DEPRECATED**! See `app.identity_provider_mode: "org"` instead.
Restrict identity provider (IdP) authentication to organization-level only.
```yaml
flags:
use_org_only_idp: true
```
</ResponseField>
<ResponseField name="enable_redis" type="boolean" default="false">
Enable Redis for caching and session management. Requires `redis` configuration.
```yaml
flags:
enable_redis: true
```
</ResponseField>
<ResponseField name="use_pangolin_dns" type="boolean" default="false">
Allow creating domains using CNAME and NS.
```yaml
flags:
use_pangolin_dns: true
```
</ResponseField>
</Expandable>
</ResponseField>
### ACME Certificate Configuration
<ResponseField name="acme" type="object">
Configuration for Pangolin's self-hosted ACME client, which issues and renews TLS certificates directly using DNS-01 challenges served by Pangolin's own [authoritative DNS server](#dns-server-configuration), as an alternative to relying on Traefik's built-in ACME resolver.
<Expandable title="properties">
<ResponseField name="cert_mode" type="string" default="traefik">
Controls who is responsible for obtaining and renewing TLS certificates.
Possible values:
- `traefik`: (default) Traefik's built-in ACME resolver handles certificate issuance and renewal itself.
- `pangolin`: Pangolin issues and manages certificates itself via its self-hosted ACME client, using DNS-01 challenges. Requires `dns.enabled` and `acme.enable_acme_client` to both be `true`. ENSURE ONLY ONE NODE IN A CLUSTER HAS THE `acme.enable_acme_client` FLAG ENABLED, OTHERWISE MULTIPLE NODES WILL TRY TO ISSUE CERTIFICATES SIMULTANEOUSLY.
```yaml
acme:
cert_mode: "pangolin"
```
</ResponseField>
<ResponseField name="enable_acme_client" type="boolean" default="false">
Enable the self-hosted ACME client and its certificate issuance/renewal jobs. Must be `true`, along with `cert_mode: "pangolin"`, for the certificate manager to start.
```yaml
acme:
enable_acme_client: true
```
</ResponseField>
<ResponseField name="contact_email" type="string" required>
Email address registered with the ACME account, used by the CA for expiry and policy notices.
```yaml
acme:
contact_email: "admin@example.com"
```
</ResponseField>
<ResponseField name="acme_directory_url" type="string" default="https://acme-v02.api.letsencrypt.org/directory">
The ACME server directory URL Pangolin's client talks to.
```yaml
acme:
acme_directory_url: "https://acme-staging-v02.api.letsencrypt.org/directory"
```
</ResponseField>
<ResponseField name="acme_account_key_path" type="string" default="./config/account.key">
Filesystem path where the ACME account's private key is stored. Generated automatically on first run if it doesn't exist.
```yaml
acme:
acme_account_key_path: "./config/account.key"
```
</ResponseField>
<ResponseField name="challenge_ttl_ms" type="number" default="300000">
How long, in milliseconds, a DNS-01 challenge TXT record is considered valid and served by the authoritative DNS server before expiring.
```yaml
acme:
challenge_ttl_ms: 300000
```
</ResponseField>
<ResponseField name="renewal_check_interval_ms" type="number" default="3600000">
How often, in milliseconds, Pangolin checks for certificates approaching expiry and renews them.
```yaml
acme:
renewal_check_interval_ms: 3600000
```
</ResponseField>
<ResponseField name="new_cert_check_interval_ms" type="number" default="5000">
How often, in milliseconds, Pangolin checks for newly-needed certificates and issues them. Set much lower than `renewal_check_interval_ms` since new certificates are user-facing and blocking.
```yaml
acme:
new_cert_check_interval_ms: 5000
```
</ResponseField>
<ResponseField name="acme_requests_per_second" type="number" default="15">
Shared rate limit on outbound calls Pangolin's client makes directly to the ACME server (create order, get authorizations, verify challenge, finalize, get certificate). Kept under Let's Encrypt's ~20 req/s limit.
```yaml
acme:
acme_requests_per_second: 15
```
</ResponseField>
<ResponseField name="dns_check_interval_ms" type="number" default="60000">
How often, in milliseconds, Pangolin checks pending (not-yet-verified) domains' DNS records against live DNS to flip them to verified once the records are in place.
```yaml
acme:
dns_check_interval_ms: 60000
```
</ResponseField>
<ResponseField name="domain_reverification_interval_ms" type="number" default="3600000">
How often, in milliseconds, Pangolin runs a periodic pass re-checking already-verified domains, to catch removing or changing their DNS records after the fact.
```yaml
acme:
domain_reverification_interval_ms: 3600000
```
</ResponseField>
<ResponseField name="domain_reverification_window_ms" type="number" default="259200000">
Minimum age, in milliseconds, a verified domain's last check must have before it becomes eligible for reverification. A given domain is reverified at most roughly this often, not on every reverification pass.
```yaml
acme:
domain_reverification_window_ms: 259200000
```
</ResponseField>
<ResponseField name="domain_reverification_batch_size" type="number" default="20">
Maximum number of verified domains reverified per reverification pass, to bound database and DNS load per tick.
```yaml
acme:
domain_reverification_batch_size: 20
```
</ResponseField>
<ResponseField name="dns_resolvers" type="array" default='["8.8.8.8", "1.1.1.1", "9.9.9.9", "208.67.222.222"]'>
Upstream public DNS resolvers used to perform live lookups against real DNS (not Pangolin's own authoritative server) when validating or reverifying domain records. Resolvers are rotated across on each attempt so a single resolver's cache or propagation lag doesn't wrongly fail a check.
```yaml
acme:
dns_resolvers:
- "8.8.8.8"
- "1.1.1.1"
```
</ResponseField>
</Expandable>
</ResponseField>
### Branding Configuration
Please refer to the [branding configuration documentation](/manage/branding).
## Environment Variables
Some configuration values can be set using environment variables for enhanced security:
| Name | Variable | Config | Supports `_FILE` |
|------|----------|--------|:---:|
| Redis Password | `REDIS_PASSWORD` | `redis.password` | YES |
Any variable marked "Supports `_FILE`" can also be set as `<VARIABLE>_FILE`, pointing to a file on disk whose (trimmed) contents are used as the value instead — see [Reading secrets from a file](/self-host/advanced/config-file#reading-secrets-from-a-file-_file-suffix) in the main configuration file docs for details and a Docker Compose example.
@@ -0,0 +1,41 @@
---
title: "Container Timezone"
description: "Configure the timezone for Pangolin, Gerbil, and Traefik containers to match your local time."
---
By default, Docker containers report logs and timestamps in **UTC**. If you want the containers and their log output to use your local timezone, you need to set the timezone in both the container environment and mount the host timezone files.
## Updating your `docker-compose.yml`
Add the following to your `pangolin`, `gerbil`, and `traefik` services in `docker-compose.yml`:
```yaml title="docker-compose.yml"
services:
pangolin:
environment:
- TZ=America/New_York # Set your local timezone
volumes:
- /etc/localtime:/etc/localtime:ro # Sync host timezone
- /etc/timezone:/etc/timezone:ro # Optional: some apps read this file
gerbil:
volumes:
- /etc/localtime:/etc/localtime:ro
- /etc/timezone:/etc/timezone:ro
traefik:
environment:
- TZ=America/New_York
volumes:
- /etc/localtime:/etc/localtime:ro
- /etc/timezone:/etc/timezone:ro
```
### Notes
- **Environment variable `TZ`** ensures most applications inside the container use the correct local timezone.
- **`/etc/localtime` volume** ensures that system utilities (e.g., `date`) inside the container show the correct time.
- **`/etc/timezone` volume** is optional, but some scripts and apps on Debian-based images read it to determine the timezone.
- Logs generated by the containers (including Traefik and Gerbil) will now reflect your local time instead of UTC.
<Warning>
Make sure that the host system has the correct timezone configured, as the containers will reference these host files.
</Warning>
@@ -0,0 +1,119 @@
---
title: "Traefik Access Log Rotation"
description: "How to manage and rotate Traefik access logs when CrowdSec is installed"
---
When CrowdSec is installed, Traefik access logging is enabled automatically so CrowdSec can analyze traffic. This means `config/traefik/logs/access.log` will grow indefinitely without log rotation in place.
<Note>
The default Pangolin install (without CrowdSec) does not enable access
logging, so this only applies if you have CrowdSec installed.
</Note>
## How it works
The CrowdSec installer enables Traefik's `accessLog` block and mounts `./config/traefik/logs/` into the container at `/var/log/traefik/`. CrowdSec reads that log via its `acquis.d/traefik.yaml` acquisition config.
Without rotation, that file grows forever. The fix is `logrotate` with `copytruncate` — it copies the log file and truncates the original in place, so Traefik never needs to be restarted or sent a signal.
## Automatic setup (installer v1.x+)
If you installed CrowdSec using a recent version of the Pangolin installer, logrotate is configured automatically at `/etc/logrotate.d/pangolin-traefik`. You can verify it's there:
```bash
cat /etc/logrotate.d/pangolin-traefik
```
You should see something like:
```
/opt/pangolin/config/traefik/logs/access.log {
daily
rotate 7
compress
delaycompress
missingok
notifempty
copytruncate
}
```
## Manual setup
If you installed CrowdSec before automatic log rotation was added, set it up manually:
<Steps>
<Step title="Create the logrotate config">
Replace `/opt/pangolin` with your actual Pangolin install directory if it differs.
```bash
sudo tee /etc/logrotate.d/pangolin-traefik > /dev/null <<'EOF'
/opt/pangolin/config/traefik/logs/access.log {
daily
rotate 7
compress
delaycompress
missingok
notifempty
copytruncate
}
EOF
```
</Step>
<Step title="Test the configuration">
Do a dry run to confirm logrotate picks it up without errors:
```bash
sudo logrotate --debug /etc/logrotate.d/pangolin-traefik
```
No errors means you're good. You can also force a rotation immediately to verify end-to-end:
```bash
sudo logrotate --force /etc/logrotate.d/pangolin-traefik
```
</Step>
</Steps>
## Customizing retention
The defaults (daily rotation, 7 compressed copies) work for most setups. To adjust:
| Option | What it does |
| --------------- | -------------------------------------------------------------------------------------- |
| `daily` | Rotate once per day. Use `weekly` or `monthly` if preferred. |
| `rotate 7` | Keep 7 rotated files before deleting the oldest. |
| `compress` | Gzip rotated files to save disk space. |
| `delaycompress` | Skip compressing the most recent rotated file (useful if something still has it open). |
For example, to keep 30 days of compressed weekly logs:
```
/opt/pangolin/config/traefik/logs/access.log {
weekly
rotate 30
compress
delaycompress
missingok
notifempty
copytruncate
}
```
## Verifying rotation is working
Check that rotated files are appearing in the logs directory:
```bash
ls -lh /opt/pangolin/config/traefik/logs/
```
After the first rotation you should see files like `access.log.1` and `access.log.2.gz` alongside the active `access.log`.
To see when logrotate last ran and whether it succeeded:
```bash
cat /var/lib/logrotate/status | grep pangolin
```
@@ -0,0 +1,274 @@
---
title: "Wildcard Domains"
description: "Configure wildcard TLS certificates with Traefik DNS-01 challenges"
---
Wildcard certificates let one certificate cover every first-level subdomain of a domain, such as `*.example.com`. They are useful when you create many resources under the same base domain because Traefik does not need to request a new certificate for every resource hostname.
Traefik is the reverse proxy in the self-hosted Pangolin stack. It receives HTTPS traffic, requests certificates from Let's Encrypt, and routes requests to Pangolin resources. A Traefik certificate resolver is the named block of Traefik configuration that tells Traefik how to request certificates.
<Warning>
Wildcard certificates require a DNS-01 challenge. You must control the domain's DNS records and have API credentials for a DNS provider supported by Traefik.
</Warning>
<Note>
Let's Encrypt only issues wildcard certificates through DNS-01 challenges. See the [Traefik ACME documentation](https://doc.traefik.io/traefik/https/acme/) and [Lego DNS provider list](https://go-acme.github.io/lego/dns/) for provider-specific options.
</Note>
## How Wildcards Work
- `*.example.com` covers `app.example.com`, `api.example.com`, and `blog.example.com`.
- `*.example.com` does not cover `app.internal.example.com`; that needs `*.internal.example.com`.
- A wildcard certificate can reduce Let's Encrypt rate limit pressure because many resource hostnames can reuse the same certificate.
Pangolin can prefer wildcard certificates when it generates Traefik router configuration. For example, if you have resources at `blog.example.com` and `api.example.com`, Pangolin can ask Traefik to request `*.example.com` instead of separate certificates for each hostname.
## Benefits
<CardGroup cols={3}>
<Card title="Single Certificate" icon="certificate">
Secure many subdomains under the same base domain with one certificate.
</Card>
<Card title="Instant Subdomains" icon="bolt">
New resource subdomains can use the existing wildcard certificate instead of waiting for a new certificate request.
</Card>
<Card title="Rate Limit Friendly" icon="shield">
Fewer certificate requests can help reduce the chance of hitting Let's Encrypt rate limits.
</Card>
</CardGroup>
## Choose a Resolver Strategy
Most installs start with one Traefik certificate resolver named `letsencrypt` that uses HTTP-01:
```yaml title="traefik_config.yml"
certificatesResolvers:
letsencrypt:
acme:
httpChallenge:
entryPoint: web
email: "admin@example.com"
storage: "/letsencrypt/acme.json"
caServer: "https://acme-v02.api.letsencrypt.org/directory"
```
HTTP-01 proves domain ownership by serving a challenge over port `80`. DNS-01 proves domain ownership by creating a temporary DNS record through your DNS provider. Wildcard certificates require DNS-01.
For wildcard certificates, you have two good options. Most users should replace the existing `letsencrypt` resolver with DNS-01; add a second resolver only if you know you need both HTTP-01 and DNS-01.
| Strategy | When to use it |
| --- | --- |
| Replace `letsencrypt` with DNS-01 | Simplest option. Use this if all certificates can be issued through your DNS provider. |
| Add a second resolver | Use this if you want to keep HTTP-01 for some routers and use DNS-01 only for wildcard domains. |
Traefik does not automatically apply a resolver just because it exists. Each router must reference the resolver with `tls.certResolver`, and Pangolin's `cert_resolver` setting must match the Traefik resolver name.
<Note>
In the Pangolin dashboard, `default` uses Pangolin's configured `traefik.cert_resolver` value. In a standard install, that default value is `letsencrypt`.
</Note>
## Configure DNS-01 Wildcards
<Steps>
<Step title="Stop the stack">
Stop Pangolin before editing Traefik and Pangolin configuration.
```bash
sudo docker compose down
```
</Step>
<Step title="Update the Traefik resolver">
Replace the default HTTP-01 resolver with a DNS-01 resolver. This example uses Cloudflare.
```yaml title="config/traefik/traefik_config.yml" {4-6}
certificatesResolvers:
letsencrypt:
acme:
dnsChallenge:
provider: "cloudflare"
# See https://doc.traefik.io/traefik/https/acme/#providers
email: "admin@example.com"
storage: "/letsencrypt/acme.json"
caServer: "https://acme-v02.api.letsencrypt.org/directory"
```
<Note>
The resolver name is the key under `certificatesResolvers`. In this example it is `letsencrypt`, so Pangolin's `cert_resolver` and any Traefik `tls.certResolver` values must also use `letsencrypt`.
</Note>
</Step>
<Step title="Add DNS provider credentials">
Add the environment variables required by your DNS provider to the `traefik` service. Cloudflare requires an API token with `Zone:Read` and `DNS:Edit` permissions for every zone Traefik needs to solve challenges for.
```yaml title="docker-compose.yml" {11-12}
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
environment:
CLOUDFLARE_DNS_API_TOKEN: "your-cloudflare-api-token" # REPLACE
volumes:
- ./config/traefik:/etc/traefik:ro
- ./config/letsencrypt:/letsencrypt
```
</Step>
<Step title="Tell Pangolin to prefer wildcard certificates">
Set `prefer_wildcard_cert: true` for the domain in `config/config.yml`.
```yaml title="config/config.yml" {4}
domains:
domain1:
base_domain: "example.com"
prefer_wildcard_cert: true
cert_resolver: "letsencrypt"
```
If you manage domains through the Pangolin dashboard instead, restart Pangolin and enable wildcard preference on the domain there. The dashboard also lets you set the domain's certificate resolver; it must match the resolver name in Traefik.
</Step>
<Step title="Restart the stack">
Start the stack and watch Traefik logs. You should see Traefik create DNS challenge records through your provider.
```bash
sudo docker compose up -d
sudo docker compose logs -f traefik
```
</Step>
</Steps>
## Multiple Certificate Resolvers
You can define more than one Traefik certificate resolver. This is useful when you want to keep HTTP-01 available as the default resolver, but use a DNS-01 resolver for wildcard domains.
```yaml title="config/traefik/traefik_config.yml"
certificatesResolvers:
letsencrypt:
acme:
httpChallenge:
entryPoint: web
email: "admin@example.com"
storage: "/letsencrypt/acme-http.json"
caServer: "https://acme-v02.api.letsencrypt.org/directory"
letsencrypt-dns:
acme:
dnsChallenge:
provider: "cloudflare"
email: "admin@example.com"
storage: "/letsencrypt/acme-dns.json"
caServer: "https://acme-v02.api.letsencrypt.org/directory"
```
Then point the wildcard domain at the DNS resolver. You can do this in `config/config.yml` for file-managed domains:
```yaml title="config/config.yml" {5-6}
domains:
domain1:
base_domain: "example.com"
prefer_wildcard_cert: true
cert_resolver: "letsencrypt-dns"
```
If you want UI-created domains to use the DNS resolver by default, set the Traefik defaults too:
```yaml title="config/config.yml"
traefik:
cert_resolver: "letsencrypt-dns"
prefer_wildcard_cert: true
```
For dashboard-managed domains, open the domain settings in Pangolin and set the certificate resolver to the custom Traefik resolver name, such as `letsencrypt-dns`. Enable wildcard preference on the same domain if you want Pangolin to request wildcard certificates for resources under that domain.
<Note>
If you split ACME storage across multiple files, configure Pangolin's private `acme.acme_json_path` setting as the directory that contains them, for example `config/letsencrypt`. Pangolin will scan the directory for ACME JSON files, including nested files. See [ACME configuration](/self-host/advanced/private-config-file#acme-configuration).
</Note>
## Dashboard Certificate
The `prefer_wildcard_cert` setting affects resource routers generated by Pangolin. If you also want Traefik to request a wildcard certificate for the Pangolin dashboard router, add the wildcard domain to the dashboard router's `tls.domains` list in `config/traefik/dynamic_config.yml`.
```yaml title="config/traefik/dynamic_config.yml" {8-12}
next-router:
rule: "Host(`pangolin.example.com`) && !PathPrefix(`/api/v1`)"
service: next-service
entryPoints:
- websecure
tls:
certResolver: letsencrypt
domains:
- main: "example.com"
sans:
- "*.example.com"
```
If you use a second resolver, set `certResolver` to that resolver name, such as `letsencrypt-dns`.
## Verify It Works
<Tip>
If Traefik already issued certificates with the old resolver, clear the old certificates before testing so Traefik requests them again. Remove the relevant ACME storage file, or use `pangctl clear-certs` if Pangolin has already synced stale certificates.
</Tip>
<Steps>
<Step title="Create or open a resource">
Create a resource on an unused subdomain such as `test.example.com`, or open an existing resource under the same base domain.
</Step>
<Step title="Check Traefik logs">
Traefik should use the DNS-01 resolver and should not need a separate certificate for every resource hostname after the wildcard certificate exists.
```bash
sudo docker compose logs traefik
```
</Step>
<Step title="Inspect ACME storage">
Check the ACME storage file in `config/letsencrypt`. The certificate domain should include a wildcard SAN such as `*.example.com`.
</Step>
</Steps>
```json {6}
{
"Certificates": [
{
"domain": {
"main": "example.com",
"sans": ["*.example.com"]
},
"certificate": "...",
"key": "...",
"Store": "default"
}
]
}
```
## Troubleshooting
<AccordionGroup>
<Accordion title="Wildcard certificate is not created">
Confirm the DNS provider is correct, the provider environment variables are present on the `traefik` service, and the API token has permission to edit DNS records for the zone.
</Accordion>
<Accordion title="Traefik still uses HTTP-01">
Check the resolver name. The router's `tls.certResolver` and Pangolin's `cert_resolver` must match the DNS-01 resolver name exactly.
</Accordion>
<Accordion title="Old certificates are still served">
Clear old certificates so Traefik can request them again. You can remove the relevant ACME storage file, or use `pangctl clear-certs` if Pangolin has already synced stale certificates.
</Accordion>
<Accordion title="DNS challenge times out">
Review Traefik debug logs, confirm DNS propagation is working, and check whether your DNS provider requires additional propagation delay or custom resolvers. If your DNS provider has a firewall, make sure it allows DNS traffic, typically UDP on port `53`.
</Accordion>
</AccordionGroup>
@@ -0,0 +1,29 @@
---
title: "Without Tunneling"
description: "Use Pangolin as a local reverse proxy without Gerbil tunneling"
---
Use Pangolin as a local reverse proxy and authentication manager
You can use Pangolin without Gerbil and tunneling. In this configuration, Pangolin acts as a normal reverse proxy and authentication manager that can be deployed on your local network to provide access to resources.
<Note>
You can also use "local" sites to expose resources on the same VPS as Pangolin in addition to remote sites.
</Note>
## Setup
### Using the Installer
When asked if you want to install Gerbil for tunneling, select **No**. Gerbil will be removed from the Docker Compose configuration.
### Manual Installation
Follow the [manual install steps](/self-host/manual/docker-compose), but **Gerbil is not required**. Your Docker Compose should not include the Gerbil container.
## How It Works
When Gerbil starts up, it registers itself with Pangolin. By not installing Gerbil, you will only have the option to choose the "Local" connection method. This means Traefik will use the local network to reach your resources.
<Warning>
All setup remains the same, except Pangolin and Traefik must now be on the same network as the resources you want to proxy to.
</Warning>