docs: add new Access Control and Logs documentation pages

- Documented Access Control features (e.g., Device Approvals, Password Rotation, 2FA, Custom Login Pages).
- Added detailed descriptions for Logs & Analytics (Access Logs, Request Logs, Action Logs).
- Included configuration instructions and feature-specific notes for Pangolin Cloud and Enterprise Edition.

Signed-off-by: Stefan Mogeritsch <stefan.mo.co@gmail.com>
This commit is contained in:
2026-03-11 11:24:24 +01:00
parent a70f132fd9
commit aa157e82f8
87 changed files with 13163 additions and 0 deletions
@@ -0,0 +1,163 @@
> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pangolin.net/llms.txt
> Use this file to discover all available pages before exploring further.
# Cloudflare Proxy
<div id="pangolin-toc-cta" className="pangolin-toc-cta-source">
<Card title="Try free on Pangolin Cloud" icon="cloud" href="https://app.pangolin.net/auth/signup" arrow="true" cta="Sign up free">
Fastest way to get started with Pangolin using the hosted control plane. No credit card required.
</Card>
</div>
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 theme={null}
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 theme={null}
curl ifconfig.io
```
</Step>
<Step title="Update configuration">
Add the IP to your `config.yml`:
```yaml theme={null}
gerbil:
base_endpoint: "104.21.16.1" # Replace with your actual IP
```
</Step>
<Step title="Restart services">
Restart Pangolin to apply the changes:
```bash theme={null}
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" theme={null}
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 theme={null}
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 like from newt or clients, ensure that websockets are enabled in Cloudflare:
<Frame>
<img src="https://mintcdn.com/fossorial/VqiOoRUR8g1Tf03J/images/cf_websocket_box.png?fit=max&auto=format&n=VqiOoRUR8g1Tf03J&q=85&s=f107b9da12af22813deca776b8f2551e" width="600" centered data-og-width="1100" data-og-height="247" data-path="images/cf_websocket_box.png" data-optimize="true" data-opv="3" srcset="https://mintcdn.com/fossorial/VqiOoRUR8g1Tf03J/images/cf_websocket_box.png?w=280&fit=max&auto=format&n=VqiOoRUR8g1Tf03J&q=85&s=adbcd090cb9d1c52973a416aec8edcfc 280w, https://mintcdn.com/fossorial/VqiOoRUR8g1Tf03J/images/cf_websocket_box.png?w=560&fit=max&auto=format&n=VqiOoRUR8g1Tf03J&q=85&s=384b68f10a1253ea2ea2b729a6ac857b 560w, https://mintcdn.com/fossorial/VqiOoRUR8g1Tf03J/images/cf_websocket_box.png?w=840&fit=max&auto=format&n=VqiOoRUR8g1Tf03J&q=85&s=c8b28b64b577908e1b8503e08660e517 840w, https://mintcdn.com/fossorial/VqiOoRUR8g1Tf03J/images/cf_websocket_box.png?w=1100&fit=max&auto=format&n=VqiOoRUR8g1Tf03J&q=85&s=f958b0f1d0850a2719c6929e8ca54093 1100w, https://mintcdn.com/fossorial/VqiOoRUR8g1Tf03J/images/cf_websocket_box.png?w=1650&fit=max&auto=format&n=VqiOoRUR8g1Tf03J&q=85&s=71eafa9f2822cec53e703071adddb595 1650w, https://mintcdn.com/fossorial/VqiOoRUR8g1Tf03J/images/cf_websocket_box.png?w=2500&fit=max&auto=format&n=VqiOoRUR8g1Tf03J&q=85&s=a88c0baa21142f8a6abeeaf2b6436199 2500w" />
</Frame>
@@ -0,0 +1,75 @@
> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pangolin.net/llms.txt
> Use this file to discover all available pages before exploring further.
# Clustering for High Availability
<div id="pangolin-toc-cta" className="pangolin-toc-cta-source">
<Card title="Try free on Pangolin Cloud" icon="cloud" href="https://app.pangolin.net/auth/signup" arrow="true" cta="Sign up free">
Fastest way to get started with Pangolin using the hosted control plane. No credit card required.
</Card>
</div>
<Note>
Clustering is only available in [Enterprise Edition](/self-host/enterprise-edition). [Please reach out to us to deploy](https://pangolin.net/talk-to-us).
</Note>
Deploy multiple Pangolin servers enterprise-grade high availability and performance in large deployments.
## Overview
For organizations requiring maximum uptime and performance, Pangolin supports clustered deployments where multiple
server instances work together as a unified system. This architecture enables regional distribution, automatic failover,
and horizontal scaling to handle demanding production workloads.
## How Clustering Works
In a clustered configuration, multiple Pangolin server instances operate together, sharing state and coordinating
through a Postgres database and Valkey server. Each instance can independently serve user requests, manage
authentication, and coordinate with multiple Gerbil instances to support thousands of sites.
### Shared Database Backend
All Pangolin instances connect to a shared PostgreSQL database that stores the system's persistent state - including
user accounts, site configurations, resources, access policies, and organizational settings. This ensures that changes
made through any server instance are immediately available across the entire cluster.
### Real-time State Synchronization
Redis or Valkey provides real-time state synchronization and pub sub between cluster nodes like active sessions,
WebSocket connections, and tunnel status. When a user authenticates or a site connector establishes a connection to one
Pangolin instance, Redis ensures other nodes are aware of these active sessions for failover.
### Tunnel Management with Gerbil
Each Pangolin instance runs alongside its own Gerbil tunnel manager, which handles WireGuard connections to site
connectors. When a site connector needs to establish a tunnel, it can connect to any available Gerbil instance in the
cluster. For public resources, Gerbil instances are aware of all of the other nodes in the network and can route
incoming requests to any other Gerbil to exit through the right site. The distributed architecture ensures that tunnel
connectivity remains available even if individual nodes fail.
## Benefits of Clustering
**High Availability**: Eliminate single points of failure. If one server instance fails, traffic automatically routes to
healthy nodes without user disruption.
**Regional Distribution**: Deploy servers closer to your users and sites across different geographic regions to minimize
latency and improve performance.
**Horizontal Scaling**: Add more server instances to handle increased load as your organization grows, without
architectural changes.
**Zero-Downtime Updates**: Perform rolling updates by taking nodes offline one at a time while others continue serving
traffic.
## Enterprise Support
Clustered deployments require careful planning around database replication, Redis configuration, network topology, and
monitoring. These advanced architectures are available as part of
Pangolin's [Enterprise Edition](/self-host/enterprise-edition) with dedicated support for design, deployment, and
ongoing operations.
For organizations interested in clustering for high availability or regional distribution,
please [contact our enterprise team](https://pangolin.net/talk-to-us) to discuss your requirements and receive
implementation guidance.
@@ -0,0 +1,859 @@
> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pangolin.net/llms.txt
> Use this file to discover all available pages before exploring further.
# Configuration File
> Configure Pangolin using the config.yml file with detailed settings for all components
<div id="pangolin-toc-cta" className="pangolin-toc-cta-source">
<Card title="Try free on Pangolin Cloud" icon="cloud" href="https://app.pangolin.net/auth/signup" arrow="true" cta="Sign up free">
Fastest way to get started with Pangolin using the hosted control plane. No credit card required.
</Card>
</div>
The `config.yml` file controls all aspects of your Pangolin deployment, including server settings, domain configuration,
email setup, and security options. This file is mounted at `config/config.yml` in your Docker container.
## Setting up your `config.yml`
To get started, create a basic configuration file with the essential settings:
Minimal Pangolin configuration:
```yaml title="config.yml" theme={null}
# To see all available options, please visit the docs:
# https://docs.pangolin.net/
gerbil:
start_port: 51820
base_endpoint: "pangolin.example.com" # REPLACE WITH YOUR DOMAIN
app:
dashboard_url: "https://pangolin.example.com" # REPLACE WITH YOUR DOMAIN
log_level: "info"
telemetry:
anonymous_usage: true
domains:
domain1:
base_domain: "example.com" # REPLACE WITH YOUR DOMAIN
cert_resolver: "letsencrypt"
server:
secret: "your-strong-secret" # REPLACE
cors:
origins: ["https://pangolin.example.com"] # REPLACE WITH YOUR DOMAIN
methods: ["GET", "POST", "PUT", "DELETE", "PATCH"]
allowed_headers: ["X-CSRF-Token", "Content-Type"]
credentials: false
flags:
require_email_verification: false
disable_signup_without_invite: true
disable_user_create_org: false
allow_raw_resources: true
```
<Warning>
Generate a strong secret for `server.secret`. Use at least 32 characters with a mix of letters, numbers, and special characters.
If you need to CHANGE the server secret after the server has been started you must use the
`pangctl rotate-server-secret` command to re-encrypt sensitive
data. [Follow docs here](/self-host/advanced/container-cli-tool#rotate-server-secret).
</Warning>
## Reference
This section contains the complete reference for all configuration options in `config.yml`.
### Application Settings
<ResponseField name="app" type="object" required>
Core application configuration including dashboard URL, logging, and general settings.
<Expandable title="App">
<ResponseField name="dashboard_url" type="string" required>
The URL where your Pangolin dashboard is hosted.
**Examples**: `https://example.com`, `https://pangolin.example.com`
This URL is used for generating links, redirects, and authentication flows. You can run Pangolin on a subdomain or root domain.
</ResponseField>
<ResponseField name="log_level" type="string">
The logging level for the application.
**Options**: `debug`, `info`, `warn`, `error`
**Default**: `info`
</ResponseField>
<ResponseField name="save_logs" type="boolean">
Whether to save logs to files in the `config/logs/` directory.
**Default**: `false`
<Note>
When enabled, logs rotate automatically:
* Max file size: 20MB
* Max files: 7 days
</Note>
</ResponseField>
<ResponseField name="log_failed_attempts" type="boolean">
Whether to log failed authentication attempts for security monitoring.
**Default**: `false`
</ResponseField>
<ResponseField name="telemetry" type="object">
Telemetry configuration settings.
<Expandable title="Telemetry">
<ResponseField name="anonymous_usage" type="boolean">
Whether to enable anonymous usage telemetry.
**Default**: `true`
</ResponseField>
</Expandable>
</ResponseField>
<ResponseField name="notifications" type="object">
Notification configuration settings.
<Expandable title="Notification">
<ResponseField name="product_updates" type="boolean">
Whether to enable showing product updates notifications on the UI.
**Default**: `true`
</ResponseField>
<ResponseField name="new_releases" type="boolean">
Whether to enable showing new releases notifications on the UI.
**Default**: `true`
</ResponseField>
</Expandable>
</ResponseField>
</Expandable>
</ResponseField>
### Server Configuration
<ResponseField name="server" type="object" required>
Server ports, networking, and authentication settings.
<Expandable title="Server">
<ResponseField name="external_port" type="integer">
The port for the front-end API that handles external requests.
**Example**: `3000`
</ResponseField>
<ResponseField name="internal_port" type="integer">
The port for the internal private-facing API.
**Example**: `3001`
</ResponseField>
<ResponseField name="next_port" type="integer">
The port for the frontend server (Next.js).
**Example**: `3002`
</ResponseField>
<ResponseField name="integration_port" type="integer">
The port for the integration API (optional).
**Example**: `3003`
</ResponseField>
<ResponseField name="internal_hostname" type="string">
The hostname of the Pangolin container for internal communication.
**Example**: `pangolin`
<Tip>
If using Docker Compose, this should match your container name.
</Tip>
</ResponseField>
<ResponseField name="session_cookie_name" type="string">
The name of the session cookie for storing authentication tokens.
**Example**: `p_session_token`
**Default**: `p_session_token`
</ResponseField>
<ResponseField name="resource_access_token_param" type="string">
Query parameter name for passing access tokens in requests.
**Example**: `p_token`
**Default**: `p_token`
</ResponseField>
<ResponseField name="resource_access_token_headers" type="object">
HTTP headers for passing access tokens in requests.
<Expandable title="Headers">
<ResponseField name="id" type="string">
Header name for access token ID.
**Example**: `P-Access-Token-Id`
</ResponseField>
<ResponseField name="token" type="string">
Header name for access token.
**Example**: `P-Access-Token`
</ResponseField>
</Expandable>
</ResponseField>
<ResponseField name="resource_session_request_param" type="string">
Query parameter for session request tokens.
**Example**: `p_session_request`
**Default**: `p_session_request`
</ResponseField>
<ResponseField name="cors" type="object">
Cross-Origin Resource Sharing (CORS) configuration.
<Expandable title="CORS">
<ResponseField name="origins" type="array of strings">
Allowed origins for cross-origin requests.
**Example**: `["https://pangolin.example.com"]`
</ResponseField>
<ResponseField name="methods" type="array of strings">
Allowed HTTP methods for CORS requests.
**Example**: `["GET", "POST", "PUT", "DELETE", "PATCH"]`
</ResponseField>
<ResponseField name="allowed_headers" type="array of strings">
Allowed HTTP headers in CORS requests.
**Example**: `["X-CSRF-Token", "Content-Type"]`
</ResponseField>
<ResponseField name="credentials" type="boolean">
Whether to allow credentials in CORS requests.
**Default**: `true`
</ResponseField>
</Expandable>
</ResponseField>
<ResponseField name="trust_proxy" type="integer">
Number of proxy headers to trust for client IP detection.
**Example**: `1`
**Default**: `1`
<Tip>
Use `1` if running behind a single reverse proxy like Traefik.
</Tip>
</ResponseField>
<ResponseField name="dashboard_session_length_hours" type="integer">
Dashboard session duration in hours.
**Example**: `720` (30 days)
**Default**: `720`
</ResponseField>
<ResponseField name="resource_session_length_hours" type="integer">
Resource session duration in hours.
**Example**: `720` (30 days)
**Default**: `720`
</ResponseField>
<ResponseField name="secret" type="string" required>
Secret key for encrypting sensitive data.
**Environment Variable**: `SERVER_SECRET`
**Minimum Length**: 8 characters
**Example**: `"d28@a2b.2HFTe2bMtZHGneNYgQFKT2X4vm4HuXUXBcq6aVyNZjdGt6Dx-_A@9b3y"`
<Warning>
Generate a strong, random secret. This is used for encrypting sensitive data and should be kept secure.
If you need to CHANGE the server secret after the server has been started you must use the `pangctl rotate-server-secret` command to re-encrypt sensitive data. [Follow docs here](/self-host/advanced/container-cli-tool#rotate-server-secret).
</Warning>
</ResponseField>
<ResponseField name="maxmind_db_path" type="string">
Path to the MaxMind GeoIP database file for geolocation features.
**Example**: `./config/GeoLite2-Country.mmdb`
<Note>
Used for IP geolocation functionality. Requires a MaxMind GeoLite2 or GeoIP2 database file.
</Note>
</ResponseField>
</Expandable>
</ResponseField>
### Domain Configuration
<ResponseField name="domains" type="object" required>
Domain settings for SSL certificates and routing.
At least one domain must be configured.
It is best to add it in the UI for ease of use or when you want the
domain to *only be present in the org it was created in*.
You should create it in the config file for permanence across installs
and if you want the domain to be present in all orgs.
<Expandable title="Domains">
<ResponseField name="<domain_key>" type="object">
Domain configuration with a unique key of your choice.
<Expandable title="Domain Settings">
<ResponseField name="base_domain" type="string" required>
The base domain for this configuration.
**Example**: `example.com`
</ResponseField>
<ResponseField name="cert_resolver" type="string" required>
The Traefik certificate resolver name.
**Example**: `letsencrypt`
<Note>
This must match the certificate resolver name in your Traefik configuration.
</Note>
</ResponseField>
<ResponseField name="prefer_wildcard_cert" type="boolean">
Whether to prefer wildcard certificates for this domain.
**Example**: `true`
<Tip>
Useful for domains with many subdomains to reduce certificate management overhead.
</Tip>
</ResponseField>
</Expandable>
</ResponseField>
</Expandable>
</ResponseField>
### Traefik Integration
<ResponseField name="traefik" type="object">
Traefik reverse proxy configuration settings.
<Expandable title="Traefik">
<ResponseField name="http_entrypoint" type="string">
The Traefik entrypoint name for HTTP traffic.
**Example**: `web`
<Note>
Must match the entrypoint name in your Traefik configuration.
</Note>
</ResponseField>
<ResponseField name="https_entrypoint" type="string">
The Traefik entrypoint name for HTTPS traffic.
**Example**: `websecure`
<Note>
Must match the entrypoint name in your Traefik configuration.
</Note>
</ResponseField>
<ResponseField name="cert_resolver" type="string">
The default certificate resolver for domains created through the UI.
**Example**: `letsencrypt`
<Note>
This only applies to domains created through the Pangolin dashboard.
</Note>
</ResponseField>
<ResponseField name="prefer_wildcard_cert" type="boolean">
Whether to prefer wildcard certificates for UI-created domains.
**Example**: `true`
<Note>
This only applies to domains created through the Pangolin dashboard.
</Note>
</ResponseField>
<ResponseField name="additional_middlewares" type="array of strings">
Additional Traefik middlewares to apply to resource routers.
**Example**: `["middleware1", "middleware2"]`
<Note>
These middlewares must be defined in your Traefik dynamic configuration.
</Note>
</ResponseField>
<ResponseField name="certificates_path" type="string">
Path where SSL certificates are stored. This is used only with managed Pangolin deployments.
**Example**: `/var/certificates`
**Default**: `/var/certificates`
</ResponseField>
<ResponseField name="monitor_interval" type="integer">
Interval in milliseconds for monitoring configuration changes.
**Example**: `5000`
**Default**: `5000`
</ResponseField>
<ResponseField name="dynamic_cert_config_path" type="string">
Path to the dynamic certificate configuration file. This is used only with managed Pangolin deployments.
**Example**: `/var/dynamic/cert_config.yml`
**Default**: `/var/dynamic/cert_config.yml`
</ResponseField>
<ResponseField name="dynamic_router_config_path" type="string">
Path to the dynamic router configuration file.
**Example**: `/var/dynamic/router_config.yml`
**Default**: `/var/dynamic/router_config.yml`
</ResponseField>
<ResponseField name="site_types" type="array of strings">
Supported site types for Traefik configuration.
**Example**: `["newt", "wireguard", "local"]`
**Default**: `["newt", "wireguard", "local"]`
</ResponseField>
<ResponseField name="file_mode" type="boolean">
Whether to use file-based configuration mode for Traefik.
**Example**: `false`
**Default**: `false`
<Note>
When enabled, uses file-based dynamic configuration instead of API-based updates.
</Note>
</ResponseField>
<ResponseField name="pp_transport_prefix" type="string">
Prefix used for transport-related configurations. References servers transport config in dynamic Traefik file.
**Example**: `pp-transport-v`
**Default**: `pp-transport-v`
</ResponseField>
</Expandable>
</ResponseField>
### Gerbil Tunnel Controller
<ResponseField name="gerbil" type="object" required>
Gerbil tunnel controller settings for WireGuard tunneling.
<Expandable title="Gerbil">
<ResponseField name="base_endpoint" type="string" required>
Domain name included in WireGuard configuration for tunnel connections.
**Example**: `pangolin.example.com`
</ResponseField>
<ResponseField name="start_port" type="integer">
Starting port for WireGuard tunnels.
**Example**: `51820`
</ResponseField>
<ResponseField name="clients_start_port" type="integer">
Starting port for client WireGuard relay and hole punch port.
**Example**: `21820`
</ResponseField>
<ResponseField name="use_subdomain" type="boolean">
Whether to assign unique subdomains to Gerbil exit nodes.
**Default**: `false`
<Warning>
Keep this set to `false` for most deployments.
</Warning>
</ResponseField>
<ResponseField name="subnet_group" type="string">
IP address CIDR range for Gerbil exit node subnets.
**Example**: `10.0.0.0/8`
</ResponseField>
<ResponseField name="block_size" type="integer">
Block size for Gerbil exit node CIDR ranges.
**Example**: `24`
</ResponseField>
<ResponseField name="site_block_size" type="integer">
Block size for site CIDR ranges connected to Gerbil.
**Example**: `26`
</ResponseField>
</Expandable>
</ResponseField>
### Organization Settings
<ResponseField name="orgs" type="object">
Organization network configuration settings.
<Expandable title="Organizations">
<ResponseField name="block_size" type="integer">
Block size for organization CIDR ranges.
**Example**: `24`
**Default**: `24`
<Note>
Determines the subnet size allocated to each organization for network isolation.
</Note>
</ResponseField>
<ResponseField name="subnet_group" type="string">
IP address CIDR range for organization subnets.
**Example**: `100.90.128.0/24`
**Default**: `100.90.128.0/24`
<Note>
Base subnet from which organization-specific subnets are allocated.
</Note>
</ResponseField>
</Expandable>
</ResponseField>
### Rate Limiting
<ResponseField name="rate_limits" type="object">
Rate limiting configuration for API requests.
<Expandable title="Rate Limits">
<ResponseField name="global" type="object">
Global rate limit settings for all external API requests.
<Expandable title="Global">
<ResponseField name="window_minutes" type="integer">
Time window for rate limiting in minutes.
**Example**: `1`
</ResponseField>
<ResponseField name="max_requests" type="integer">
Maximum number of requests allowed in the time window.
**Example**: `100`
</ResponseField>
</Expandable>
</ResponseField>
<ResponseField name="auth" type="object">
Rate limit settings specifically for authentication endpoints.
<Expandable title="Auth Rate Limits">
<ResponseField name="window_minutes" type="integer">
Time window for authentication rate limiting in minutes.
**Example**: `1`
**Default**: `1`
</ResponseField>
<ResponseField name="max_requests" type="integer">
Maximum number of authentication requests allowed in the time window.
**Example**: `10`
**Default**: `500`
<Note>
Consider setting this lower than global limits for security.
</Note>
</ResponseField>
</Expandable>
</ResponseField>
</Expandable>
</ResponseField>
### Email Configuration
<ResponseField name="email" type="object">
SMTP settings for sending transactional emails.
<Expandable title="Email">
<ResponseField name="smtp_host" type="string">
SMTP server hostname.
**Example**: `smtp.gmail.com`
</ResponseField>
<ResponseField name="smtp_port" type="integer">
SMTP server port.
**Example**: `587` (TLS) or `465` (SSL)
</ResponseField>
<ResponseField name="smtp_user" type="string">
SMTP username.
**Example**: `no-reply@example.com`
</ResponseField>
<ResponseField name="smtp_pass" type="string">
SMTP password.
**Environment Variable**: `EMAIL_SMTP_PASS`
</ResponseField>
<ResponseField name="smtp_secure" type="boolean">
Whether to use secure connection (SSL/TLS).
**Default**: `false`
<Tip>
Enable this when using port 465 (SSL).
</Tip>
</ResponseField>
<ResponseField name="no_reply" type="string">
From address for sent emails.
**Example**: `no-reply@example.com`
<Note>
Usually the same as `smtp_user`.
</Note>
</ResponseField>
<ResponseField name="smtp_tls_reject_unauthorized" type="boolean">
Whether to fail on invalid server certificates.
**Default**: `true`
</ResponseField>
</Expandable>
</ResponseField>
### Feature Flags
<ResponseField name="flags" type="object">
Feature flags to control application behavior.
<Expandable title="Flags">
<ResponseField name="require_email_verification" type="boolean">
Whether to require email verification for new users.
**Default**: `false`
<Warning>
Only enable this if you have email configuration set up.
</Warning>
</ResponseField>
<ResponseField name="disable_signup_without_invite" type="boolean">
Whether to disable public user registration.
**Default**: `false`
<Note>
Users can still sign up with valid invites when enabled.
</Note>
</ResponseField>
<ResponseField name="disable_user_create_org" type="boolean">
Whether to prevent users from creating organizations.
**Default**: `false`
<Note>
Server admins can always create organizations.
</Note>
</ResponseField>
<ResponseField name="allow_raw_resources" type="boolean">
Whether to allow raw TCP/UDP resource creation.
**Default**: `true`
<Note>
If set to `false`, users will only be able to create http/https resources.
</Note>
</ResponseField>
<ResponseField name="enable_integration_api" type="boolean">
Whether to enable the integration API.
**Default**: `false`
</ResponseField>
<ResponseField name="disable_local_sites" type="boolean">
Whether to disable local site creation and management.
**Default**: `false`
<Note>
When enabled, users cannot create sites that connect to local networks.
</Note>
</ResponseField>
<ResponseField name="disable_basic_wireguard_sites" type="boolean">
Whether to disable basic WireGuard site functionality.
**Default**: `false`
<Note>
When enabled, only advanced WireGuard configurations are allowed.
</Note>
</ResponseField>
<ResponseField name="disable_product_help_banners" type="boolean">
Whether to disable product help banners in the UI at the top of screens.
**Default**: `false`
</ResponseField>
<ResponseField name="disable_config_managed_domains" type="boolean">
Whether to disable domains managed through the configuration file.
**Default**: `false`
<Note>
When enabled, only domains created through the UI are allowed.
</Note>
</ResponseField>
<ResponseField name="disable_enterprise_features" type="boolean">
Whether to disable features that are only available in the Enterprise Edition from showing in the UI.
**Default**: `false`
<Note>
When enabled, Enterprise-only features are hidden from the UI.
</Note>
</ResponseField>
</Expandable>
</ResponseField>
### Database Configuration
<ResponseField name="postgres" type="object">
PostgreSQL database configuration (optional).
<Expandable title="PostgreSQL">
<ResponseField name="connection_string" type="string" required>
PostgreSQL connection string.
**Example**: `postgresql://user:password@host:port/database`
<Note>
See [PostgreSQL documentation](/self-host/advanced/database-options#postgresql) for setup instructions.
</Note>
</ResponseField>
<ResponseField name="replicas" type="array of objects">
Read-only replica database configurations for load balancing.
<Expandable title="Replica Configuration">
<ResponseField name="connection_string" type="string" required>
Connection string for the read replica database.
**Example**: `postgresql://user:password@replica-host:port/database`
</ResponseField>
</Expandable>
</ResponseField>
<ResponseField name="pool" type="object">
Database connection pool settings.
<Expandable title="Pool Settings">
<ResponseField name="max_connections" type="integer">
Maximum number of connections to the primary database.
**Default**: `20`
**Example**: `50`
</ResponseField>
<ResponseField name="max_replica_connections" type="integer">
Maximum number of connections to replica databases.
**Default**: `10`
**Example**: `25`
</ResponseField>
<ResponseField name="idle_timeout_ms" type="integer">
Time in milliseconds before idle connections are closed.
**Default**: `30000` (30 seconds)
**Example**: `60000`
</ResponseField>
<ResponseField name="connection_timeout_ms" type="integer">
Time in milliseconds to wait for a database connection.
**Default**: `5000` (5 seconds)
**Example**: `10000`
</ResponseField>
</Expandable>
</ResponseField>
</Expandable>
</ResponseField>
## Environment Variables
Some configuration values can be set using environment variables for enhanced security:
| Name | Variable | Config |
|------------------------------|------------------------------|------------------------------|
| Server Secret | `SERVER_SECRET` | `server.secret` |
| Email Password | `EMAIL_SMTP_PASS` | `email.smtp_pass` |
| PostgreSQL Connection String | `POSTGRES_CONNECTION_STRING` | `postgres.connection_string` |
@@ -0,0 +1,109 @@
> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pangolin.net/llms.txt
> Use this file to discover all available pages before exploring further.
# Database Options
> Configure SQLite or PostgreSQL database for Pangolin
<div id="pangolin-toc-cta" className="pangolin-toc-cta-source">
<Card title="Try free on Pangolin Cloud" icon="cloud" href="https://app.pangolin.net/auth/signup" arrow="true" cta="Sign up free">
Fastest way to get started with Pangolin using the hosted control plane. No credit card required.
</Card>
</div>
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" theme={null}
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" theme={null}
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>
@@ -0,0 +1,83 @@
> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pangolin.net/llms.txt
> Use this file to discover all available pages before exploring further.
# Enable ASN Blocking
> Configuration requirements to enable ASN blocking in Pangolin
<div id="pangolin-toc-cta" className="pangolin-toc-cta-source">
<Card title="Try free on Pangolin Cloud" icon="cloud" href="https://app.pangolin.net/auth/signup" arrow="true" cta="Sign up free">
Fastest way to get started with Pangolin using the hosted control plane. No credit card required.
</Card>
</div>
To enable ASN blocking in Pangolin Community you must download and place the Maxmind ASN database into the `config/`
directory and update the config 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>
It is possible to automate this process with a Docker container from Maxmind themself.
Have a look at this [Community guide](/self-host/community-guides/geolite2automation) on how to implement this!
</Tip>
You can use the installer to download and place the database for you, just grab the latest installer:
```bash theme={null}
curl -fsSL https://static.pangolin.net/get-installer.sh | bash
```
Then run the installer again:
```bash theme={null}
./installer
```
### Manual Installation Steps
<Steps>
<Step title="Download and extract the ASN database">
Download and extract the GeoLite2 ASN database using the following commands:
```bash theme={null}
# 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 your `config/config.yml` file to include the following entry:
```yaml theme={null}
server:
maxmind_asn_path: "./config/GeoLite2-ASN.mmdb"
```
</Step>
<Step title="Restart Pangolin">
Restart your Pangolin instance to apply the changes:
```bash theme={null}
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.
@@ -0,0 +1,83 @@
> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pangolin.net/llms.txt
> Use this file to discover all available pages before exploring further.
# Enable Geo-blocking
> Configuration requirements to enable geoblocking in Pangolin
<div id="pangolin-toc-cta" className="pangolin-toc-cta-source">
<Card title="Try free on Pangolin Cloud" icon="cloud" href="https://app.pangolin.net/auth/signup" arrow="true" cta="Sign up free">
Fastest way to get started with Pangolin using the hosted control plane. No credit card required.
</Card>
</div>
To enable geoblocking in Pangolin Community you must download and place the Maxmind geoip database into the `config/`
directory and update the config 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>
It is possible to automate this process with a Docker container from Maxmind themself.
Have a look at this [Community guide](/self-host/community-guides/geolite2automation) on how to implement this!
</Tip>
You can use the installer to download and place the database for you, just grab the latest installer:
```bash theme={null}
curl -fsSL https://static.pangolin.net/get-installer.sh | bash
```
Then run the installer again:
```bash theme={null}
./installer
```
### Manual Installation Steps
<Steps>
<Step title="Download and extract the GeoIP database">
Download and extract the GeoLite2 Country database using the following commands:
```bash theme={null}
# 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 your `config/config.yml` file to include the following entry:
```yaml theme={null}
server:
maxmind_db_path: "./config/GeoLite2-Country.mmdb"
```
</Step>
<Step title="Restart Pangolin">
Restart your Pangolin instance to apply the changes:
```bash theme={null}
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.
@@ -0,0 +1,82 @@
> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pangolin.net/llms.txt
> Use this file to discover all available pages before exploring further.
# Enable Integration API
> Enable and configure the Integration API for external access
<div id="pangolin-toc-cta" className="pangolin-toc-cta-source">
<Card title="Try free on Pangolin Cloud" icon="cloud" href="https://app.pangolin.net/auth/signup" arrow="true" cta="Sign up free">
Fastest way to get started with Pangolin using the hosted control plane. No credit card required.
</Card>
</div>
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" theme={null}
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" theme={null}
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" theme={null}
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="https://mintcdn.com/fossorial/u-2SUNWyK_LJL3sU/images/swagger.png?fit=max&auto=format&n=u-2SUNWyK_LJL3sU&q=85&s=a64ee1f3a7c40bf4f2bd19fe3cc16de9" alt="Swagger UI Preview" data-og-width="4556" width="4556" data-og-height="2692" height="2692" data-path="images/swagger.png" data-optimize="true" data-opv="3" srcset="https://mintcdn.com/fossorial/u-2SUNWyK_LJL3sU/images/swagger.png?w=280&fit=max&auto=format&n=u-2SUNWyK_LJL3sU&q=85&s=163c4b68c9f9d9ee2589898f8af8fedf 280w, https://mintcdn.com/fossorial/u-2SUNWyK_LJL3sU/images/swagger.png?w=560&fit=max&auto=format&n=u-2SUNWyK_LJL3sU&q=85&s=6a88ca1b59f96467740945db31eeb486 560w, https://mintcdn.com/fossorial/u-2SUNWyK_LJL3sU/images/swagger.png?w=840&fit=max&auto=format&n=u-2SUNWyK_LJL3sU&q=85&s=336609c2ced100ba26453265a2bcb898 840w, https://mintcdn.com/fossorial/u-2SUNWyK_LJL3sU/images/swagger.png?w=1100&fit=max&auto=format&n=u-2SUNWyK_LJL3sU&q=85&s=2f48f7afc56f2708e0e47e39c22fe14c 1100w, https://mintcdn.com/fossorial/u-2SUNWyK_LJL3sU/images/swagger.png?w=1650&fit=max&auto=format&n=u-2SUNWyK_LJL3sU&q=85&s=5477b386ed0be57412eb78a0cd813ff6 1650w, https://mintcdn.com/fossorial/u-2SUNWyK_LJL3sU/images/swagger.png?w=2500&fit=max&auto=format&n=u-2SUNWyK_LJL3sU&q=85&s=502b80fffd80cb3dc1b8b7ad0a174112 2500w" />
</Frame>
<Note>
The Integration API will be accessible at `https://api.example.com/v1` for external applications.
</Note>
@@ -0,0 +1,135 @@
> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pangolin.net/llms.txt
> Use this file to discover all available pages before exploring further.
# Internal CLI (pangctl)
> Command-line tool for managing your Pangolin instance
<div id="pangolin-toc-cta" className="pangolin-toc-cta-source">
<Card title="Try free on Pangolin Cloud" icon="cloud" href="https://app.pangolin.net/auth/signup" arrow="true" cta="Sign up free">
Fastest way to get started with Pangolin using the hosted control plane. No credit card required.
</Card>
</div>
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 theme={null}
docker exec -it pangolin pangctl <command>
```
## Available Commands
To see all available commands:
```bash theme={null}
docker exec -it pangolin pangctl --help
```
## Set Admin Credentials
Set or reset admin credentials for your Pangolin instance:
```bash theme={null}
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>
## Clear Exit Nodes
Clear all exit nodes from the database:
```bash theme={null}
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 theme={null}
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>
## 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 theme={null}
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 theme={null}
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 theme={null}
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>
@@ -0,0 +1,868 @@
> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pangolin.net/llms.txt
> Use this file to discover all available pages before exploring further.
# Metrics
> Enable and consume OpenTelemetry & vendor specific metrics
<div id="pangolin-toc-cta" className="pangolin-toc-cta-source">
<Card title="Try free on Pangolin Cloud" icon="cloud" href="https://app.pangolin.net/auth/signup" arrow="true" cta="Sign up free">
Fastest way to get started with Pangolin using the hosted control plane. No credit card required.
</Card>
</div>
We provide metrics in the **OpenTelemetry** (OTel) format and additionally support the following vendor backends:
* **Prometheus** (native scrape and via OTel Collector)
## Why Metrics & OTel
Observability enables:
1. **Incident detection** (latency spikes, reconnect storms)
2. **Capacity planning** (bytes, active sessions)
3. **Userexperience SLAs** (p95 tunnel latency, auth latency)
4. **Faster RCA** (dimensions like `error_type`, `result`)
OpenTelemetry provides a **vendorneutral** pipeline so you can change backends without retouching instrumented code.
## Availability
Newt exposes metrics starting from specific releases, but metrics are disabled in their default configuration.
* Newt: metrics implemented since Newt 1.6.0 (disabled by default)
## Open Telemetry
Push metrics and traces to an **OTel Collector** or any backend that accepts OTLP.
<Tip>
If you only enable Prometheus scrape, leave `*_METRICS_OTLP_ENABLED=false` and omit OTLP vars.
</Tip>
<Note>
The OTel Collector commonly uses port <code>4317</code> for gRPC and <code>4318</code> for HTTP. Set <code>OTEL\_EXPORTER\_OTLP\_PROTOCOL</code> to <code>http/protobuf</code> for HTTP or <code>grpc</code> for gRPC, and point <code>OTEL\_EXPORTER\_OTLP\_ENDPOINT</code> accordingly.
For further customization, see the [OTel Collector documentation](https://opentelemetry.io/docs/collector/).
</Note>
<AccordionGroup>
<Accordion title="Newt Configuration">
<Tabs>
<Tab title="Environment Variables">
```text theme={null}
NEWT_METRICS_OTLP_ENABLED=true # enable OTLP exporter
OTEL_EXPORTER_OTLP_ENDPOINT=otel-collector:4317
OTEL_EXPORTER_OTLP_INSECURE=true # or false + TLS vars
OTEL_METRIC_EXPORT_INTERVAL=15s
# Optional auth / TLS
OTEL_EXPORTER_OTLP_HEADERS=authorization=Bearer%20XYZ
OTEL_EXPORTER_OTLP_CERTIFICATE=/etc/otel/ca.pem
```
</Tab>
<Tab title="CLI Args">
```text theme={null}
newt \
--metrics-otlp-enabled=true \ # alias for otel
--otel=true \
--otel-exporter-otlp-endpoint=otel-collector:4317 \
--otel-exporter-otlp-insecure=true \
--otel-metric-export-interval=15s \
--otel-exporter-otlp-headers=authorization=Bearer%20XYZ \
--otel-exporter-otlp-certificate=/etc/otel/ca.pem
```
See the [CLI reference](../../manage/sites/configure-site) for all available flags.
</Tab>
</Tabs>
</Accordion>
<Accordion title="Newt Configuration Examples">
<Tabs>
<Tab title="CLI (gRPC)">
```bash theme={null}
# Enable OTLP exporters and point to your Collector's gRPC receiver.
export OTEL_EXPORTER_OTLP_ENDPOINT="http://localhost:4317"
export OTEL_EXPORTER_OTLP_PROTOCOL="grpc"
newt \
--otlp=true
--id saz281jfa8z37zg
--secret ssfdfsder33rrerrwe
--endpoint http://pangolin.example.com
```
</Tab>
<Tab title="Docker Compose">
```yaml title="docker-compose.metrics.yaml" theme={null}
services:
otel-collector:
image: ghcr.io/open-telemetry/opentelemetry-collector-releases/opentelemetry-collector-contrib:latest # DO NOT use 'latest' in production
command: ["--config=/etc/otel/config.yaml"]
volumes:
- ./otel-config.yaml:/etc/otel/config.yaml:ro
ports:
- "4317:4317" # gRPC
- "4318:4318" # HTTP
- "8888:8888" # Prometheus exporter (from the Collector) - Optional
newt:
image: fosrl/newt:latest # DO NOT use 'latest' in production
environment:
NEWT_METRICS_OTLP_ENABLED: "true"
OTEL_EXPORTER_OTLP_ENDPOINT: otel-collector:4317
OTEL_EXPORTER_OTLP_INSECURE: "true"
PANGOLIN_ENDPOINT: https://example.com
NEWT_ID: heresmynewtid
NEWT_SECRET: yoursupersecretkeyhere
```
```yaml title="otel-config.yaml" theme={null}
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
processors: {}
# Example exporters:
exporters:
otlp:
endpoint: otel-collector:4317
insecure: true
prometheus:
endpoint: "0.0.0.0:8889"
service:
pipelines:
metrics:
receivers: [otlp]
processors: []
exporters: [prometheus]
```
Forward to Remote Write Backend
```yaml title="otel-config-remote.yaml" theme={null}
exporters:
prometheusremotewrite:
endpoint: https://prom-remote.example.com/api/v1/write
headers:
X-Scope-OrgID: tenant-a
tls:
insecure_skip_verify: false
service:
pipelines:
metrics/remote:
receivers: [otlp]
processors: [batch]
exporters: [prometheusremotewrite]
```
<Note>
Combine exporters (e.g. local Prometheus + remote write) to retain fast local dashboards and ship longterm retention externally.
</Note>
</Tab>
</Tabs>
</Accordion>
</AccordionGroup>
## Prometheus (without OTel Collector)
<AccordionGroup>
<Accordion title="Newt Configuration">
Each service listens on an admin HTTP address (example Newt default `:2112`).
<Tabs>
<Tab title="Environment Variables">
```text theme={null}
NEWT_METRICS_PROMETHEUS_ENABLED=true # /metrics endpoint
NEWT_ADMIN_ADDR=:2112 # admin HTTP address
```
</Tab>
<Tab title="CLI Args">
```text theme={null}
newt \
--metrics-prometheus-enabled=true \ # alias for metrics
--metrics=true
--admin-addr=:2112 \
```
See the [CLI reference](../../manage/sites/configure-site) for all available flags.
</Tab>
</Tabs>
</Accordion>
<Accordion title="Newt Configuration Examples">
<Tabs>
<Tab title="CLI">
```bash theme={null}
newt \
--metrics-prometheus-enabled=true \
--admin-addr=:2112 \
--id saz281jfa8z37zg \
--secret ssfdfsder33rrerrwe \
--endpoint https://pangolin.example.com
```
</Tab>
<Tab title="Docker Compose">
```yaml title="docker-compose.metrics.yaml" theme={null}
services:
newt:
image: fosrl/newt:latest # DO NOT use 'latest' in production
environment:
NEWT_METRICS_OTLP_ENABLED: "true"
OTEL_EXPORTER_OTLP_ENDPOINT: otel-collector:4317
OTEL_EXPORTER_OTLP_INSECURE: "true"
PANGOLIN_ENDPOINT: https://example.com
NEWT_ID: saz281jfa8z37zg
NEWT_SECRET: ssfdfsder33rrerrwe
```
</Tab>
<Tab title="Prometheus Scrape Config">
```yaml title="prometheus.yml (fragment)" theme={null}
scrape_configs:
- job_name: pangolin
static_configs: [{ targets: ["pangolin:2112"] }]
```
</Tab>
</Tabs>
</Accordion>
</AccordionGroup>
## Full Metric Reference
**Version 1.0.0 from 2025-10-28**
Below are currently implemented metrics for **Newt**.
* **Metric**: exact metric name
* **Instrument & unit**: OTel instrument type and canonical unit
* **Purpose**: what the metric conveys / recommended use
* **Emission path**: subsystem responsible (for troubleshooting missing data)
* **Example series**: representative sample including labels
<Warning>
Names/labels can change between major versions. Avoid hardcoding full label sets in alerts; prefer existence checks and aggregate functions.
</Warning>
### Newt metrics
<Tabs>
<Tab title="OpenTelemetry (OTel)">
<ResponseField name="newt">
OpenTelemetry metric instruments exposed by Newt. Expand each section to see individual metrics with labels, units, emission points, and examples.
<Expandable title="Site & Build">
<ResponseField name="newt_site_registrations_total" type="Counter">
Counts Pangolin registration attempts keyed by result.
<Expandable title="Details">
**Unit:** 1\
**Labels:** `result` (`success`|`failure`), `site_id`\
**Emission path:** `telemetry.IncSiteRegistration`\
**Example:** `newt_site_registrations_total{result="success",site_id="abc"} 1`
</Expandable>
</ResponseField>
<ResponseField name="newt_site_online" type="ObservableGauge">
0/1 heartbeat for the active site.
<Expandable title="Details">
**Unit:** 1\
**Labels:** `site_id`\
**Emission path:** `state.TelemetryView` (callback)\
**Example:** `newt_site_online{site_id="self"} 1`
</Expandable>
</ResponseField>
<ResponseField name="newt_site_last_heartbeat_seconds" type="ObservableGauge">
Seconds since last Pangolin heartbeat.
<Expandable title="Details">
**Unit:** seconds\
**Labels:** `site_id`\
**Emission path:** `TouchHeartbeat` (callback)\
**Example:** `newt_site_last_heartbeat_seconds{site_id="self"} 3.2`
</Expandable>
</ResponseField>
<ResponseField name="newt_build_info" type="ObservableGauge">
Constant 1 with build metadata labels.
<Expandable title="Details">
**Unit:** 1\
**Labels:** `version`, `commit`\
**Emission path:** Build info registration\
**Example:** `newt_build_info{version="1.2.3",commit="abc123"} 1`
</Expandable>
</ResponseField>
<ResponseField name="newt_restart_count_total" type="Counter">
Process boot indicator (increments once per process start).
<Expandable title="Details">
**Unit:** 1\
**Labels:** —\
**Emission path:** `RegisterBuildInfo`\
**Example:** `newt_restart_count_total 1`
</Expandable>
</ResponseField>
<ResponseField name="newt_cert_rotation_total" type="Counter">
Certificate rotation events keyed by result.
<Expandable title="Details">
**Unit:** 1\
**Labels:** `result`\
**Emission path:** `IncCertRotation`\
**Example:** `newt_cert_rotation_total{result="success"} 1`
</Expandable>
</ResponseField>
<ResponseField name="newt_config_reloads_total" type="Counter">
Config reload attempts keyed by result.
<Expandable title="Details">
**Unit:** 1\
**Labels:** `result`\
**Emission path:** `telemetry.IncConfigReload`\
**Example:** `newt_config_reloads_total{result="success"} 1`
</Expandable>
</ResponseField>
<ResponseField name="newt_config_apply_seconds" type="Histogram (s)">
Duration per config-apply phase keyed by `phase` and `result`.
<Expandable title="Details">
**Unit:** seconds\
**Labels:** `phase`, `result`\
**Emission path:** `telemetry.ObserveConfigApply`\
**Example:** `newt_config_apply_seconds_bucket{phase="peer",result="success",le="0.1"} 3`
</Expandable>
</ResponseField>
</Expandable>
<Expandable title="Tunnel">
<ResponseField name="newt_tunnel_sessions" type="ObservableGauge">
Active sessions per tunnel (or collapsed).
<Expandable title="Details">
**Unit:** 1\
**Labels:** `site_id`, `tunnel_id`\
**Emission path:** `RegisterStateView`\
**Example:** `newt_tunnel_sessions{site_id="self",tunnel_id="wgpub"} 2`
</Expandable>
</ResponseField>
<ResponseField name="newt_tunnel_bytes_total" type="Counter (bytes)">
Traffic per tunnel, direction, and protocol.
<Expandable title="Details">
**Unit:** bytes\
**Labels:** `tunnel_id`, `direction` (`ingress`|`egress`), `protocol` (`tcp`|`udp`)\
**Emission path:** Proxy manager\
**Example:** `newt_tunnel_bytes_total{direction="egress",protocol="tcp",tunnel_id="wgpub"} 8192`
</Expandable>
</ResponseField>
<ResponseField name="newt_tunnel_latency_seconds" type="Histogram (s)">
RTT samples per tunnel/transport.
<Expandable title="Details">
**Unit:** seconds\
**Labels:** `tunnel_id`, `transport`\
**Emission path:** Health checks\
**Example:** `newt_tunnel_latency_seconds_bucket{transport="wireguard",le="0.05",tunnel_id="wgpub"} 4`
</Expandable>
</ResponseField>
<ResponseField name="newt_tunnel_reconnects_total" type="Counter">
Reconnect attempts keyed by initiator & reason.
<Expandable title="Details">
**Unit:** 1\
**Labels:** `tunnel_id`, `initiator` (`client`|`server`), `reason`\
**Emission path:** `telemetry.IncReconnect`\
**Example:** `newt_tunnel_reconnects_total{initiator="client",reason="timeout",tunnel_id="wgpub"} 3`
</Expandable>
</ResponseField>
</Expandable>
<Expandable title="Connection & Auth">
<ResponseField name="newt_connection_attempts_total" type="Counter">
Auth/WebSocket connection attempts keyed by transport & result.
<Expandable title="Details">
**Unit:** 1\
**Labels:** `transport`, `result`\
**Emission path:** `telemetry.IncConnAttempt`\
**Example:** `newt_connection_attempts_total{transport="websocket",result="failure"} 2`
</Expandable>
</ResponseField>
<ResponseField name="newt_connection_errors_total" type="Counter">
Connection errors keyed by transport and type.
<Expandable title="Details">
**Unit:** 1\
**Labels:** `transport`, `error_type`\
**Emission path:** `telemetry.IncConnError`\
**Example:** `newt_connection_errors_total{transport="auth",error_type="auth_failed"} 1`
</Expandable>
</ResponseField>
</Expandable>
<Expandable title="WebSocket">
<ResponseField name="newt_websocket_connect_latency_seconds" type="Histogram (s)">
Dial latency for Pangolin WebSocket.
<Expandable title="Details">
**Unit:** seconds\
**Labels:** `result`, `transport`\
**Emission path:** `ObserveWSConnectLatency`\
**Example:** `newt_websocket_connect_latency_seconds_bucket{result="success",transport="websocket",le="0.5"} 1`
</Expandable>
</ResponseField>
<ResponseField name="newt_websocket_disconnects_total" type="Counter">
WebSocket disconnects keyed by reason.
<Expandable title="Details">
**Unit:** 1\
**Labels:** `reason`, `tunnel_id`\
**Emission path:** `IncWSDisconnect`\
**Example:** `newt_websocket_disconnects_total{reason="remote_close",tunnel_id="wgpub"} 2`
</Expandable>
</ResponseField>
<ResponseField name="newt_websocket_keepalive_failures_total" type="Counter">
Ping/Pong failures observed by keepalive.
<Expandable title="Details">
**Unit:** 1\
**Labels:** `reason` (e.g., `ping_write`, `pong_timeout`)\
**Emission path:** `telemetry.IncWSKeepaliveFailure(ctx, "ping_write")`\
**Example:** `newt_websocket_keepalive_failures_total{reason="ping_write"} 1`
</Expandable>
</ResponseField>
<ResponseField name="newt_websocket_session_duration_seconds" type="Histogram (s)">
Duration of established WS sessions keyed by result.
<Expandable title="Details">
**Unit:** seconds\
**Labels:** `result` (`success`|`error`)\
**Emission path:** `telemetry.ObserveWSSessionDuration(ctx, time.Since(start).Seconds(), "error")`\
**Example:** `newt_websocket_session_duration_seconds_bucket{result="error",le="60"} 3`
</Expandable>
</ResponseField>
<ResponseField name="newt_websocket_connected" type="ObservableGauge">
Current WS connection state (0/1).
<Expandable title="Details">
**Unit:** 1\
**Labels:** —\
**Emission path:** `telemetry.SetWSConnectionState(true|false)`\
**Example:** `newt_websocket_connected 1`
</Expandable>
</ResponseField>
<ResponseField name="newt_websocket_reconnects_total" type="Counter">
WebSocket reconnect attempts keyed by reason.
<Expandable title="Details">
**Unit:** 1\
**Labels:** `reason`\
**Emission path:** `telemetry.IncWSReconnect(ctx, "ping_write")`\
**Example:** `newt_websocket_reconnects_total{reason="ping_write"} 1`
</Expandable>
</ResponseField>
<ResponseField name="newt_websocket_messages_total" type="Counter">
In/out WS messages keyed by direction & type.
<Expandable title="Details">
**Unit:** 1\
**Labels:** `direction` (`in`|`out`), `msg_type` (`ping`|`pong`|`text`|...)\
**Emission path:** `IncWSMessage`\
**Example:** `newt_websocket_messages_total{direction="out",msg_type="ping"} 4`
</Expandable>
</ResponseField>
</Expandable>
<Expandable title="Proxy">
<ResponseField name="newt_proxy_active_connections" type="ObservableGauge">
Active TCP/UDP proxy connections per tunnel/protocol.
<Expandable title="Details">
**Unit:** 1\
**Labels:** `protocol`, `tunnel_id`\
**Emission path:** Proxy callback\
**Example:** `newt_proxy_active_connections{protocol="tcp",tunnel_id="wgpub"} 3`
</Expandable>
</ResponseField>
<ResponseField name="newt_proxy_buffer_bytes" type="ObservableGauge (bytes)">
Proxy buffer pool size.
<Expandable title="Details">
**Unit:** bytes\
**Labels:** `protocol`, `tunnel_id`\
**Emission path:** Proxy callback\
**Example:** `newt_proxy_buffer_bytes{protocol="tcp",tunnel_id="wgpub"} 10240`
</Expandable>
</ResponseField>
<ResponseField name="newt_proxy_async_backlog_bytes" type="ObservableGauge (bytes)">
Unflushed async byte backlog.
<Expandable title="Details">
**Unit:** bytes\
**Labels:** `protocol`, `tunnel_id`\
**Emission path:** Proxy callback\
**Example:** `newt_proxy_async_backlog_bytes{protocol="udp",tunnel_id="wgpub"} 4096`
</Expandable>
</ResponseField>
<ResponseField name="newt_proxy_drops_total" type="Counter">
Proxy write drops keyed by protocol/tunnel.
<Expandable title="Details">
**Unit:** 1\
**Labels:** `protocol`, `tunnel_id`\
**Emission path:** `IncProxyDrops`\
**Example:** `newt_proxy_drops_total{protocol="udp",tunnel_id="wgpub"} 2`
</Expandable>
</ResponseField>
<ResponseField name="newt_proxy_accept_total" type="Counter">
Proxy accept events keyed by result/reason.
<Expandable title="Details">
**Unit:** 1\
**Labels:** `tunnel_id`, `protocol`, `result`, `reason`\
**Emission path:** `telemetry.IncProxyAccept(ctx, tunnelID, "tcp", "failure", "timeout")`\
**Example:** `newt_proxy_accept_total{protocol="tcp",result="failure",reason="timeout"} 1`
</Expandable>
</ResponseField>
<ResponseField name="newt_proxy_connections_total" type="Counter">
Lifecycle events (opened/closed) per connection.
<Expandable title="Details">
**Unit:** 1\
**Labels:** `tunnel_id`, `protocol`, `event` (`opened`|`closed`)\
**Emission path:** `telemetry.IncProxyConnectionEvent(ctx, tunnelID, "tcp", telemetry.ProxyConnectionOpened)`\
**Example:** `newt_proxy_connections_total{protocol="tcp",event="opened"} 1`
</Expandable>
</ResponseField>
<ResponseField name="newt_proxy_connection_duration_seconds" type="Histogram (s)">
Duration of completed proxy connections.
<Expandable title="Details">
**Unit:** seconds\
**Labels:** `tunnel_id`, `protocol`, `result`\
**Emission path:** `telemetry.ObserveProxyConnectionDuration(ctx, tunnelID, "tcp", "success", seconds)`\
**Example:** `newt_proxy_connection_duration_seconds_bucket{protocol="tcp",result="success",le="1"} 3`
</Expandable>
</ResponseField>
</Expandable>
</ResponseField>
</Tab>
<Tab title="Prometheus">
<ResponseField name="newt">
Prometheus-style series for the same Newt metrics. Names, labels, and examples mirror the OTel tab.
<Expandable title="Site & Build">
<ResponseField name="newt_site_registrations_total" type="counter">
Counts Pangolin registration attempts keyed by result.
<Expandable title="Details">
**Labels:** `result`, `site_id` • **Unit:** 1 • **Path:** `telemetry.IncSiteRegistration`\
**Example:** `newt_site_registrations_total{result="success",site_id="abc"} 1`
</Expandable>
</ResponseField>
<ResponseField name="newt_site_online" type="gauge">
0/1 heartbeat for the active site.
<Expandable title="Details">
**Labels:** `site_id` • **Unit:** 1 • **Path:** `state.TelemetryView`\
**Example:** `newt_site_online{site_id="self"} 1`
</Expandable>
</ResponseField>
<ResponseField name="newt_site_last_heartbeat_seconds" type="gauge">
Seconds since last Pangolin heartbeat.
<Expandable title="Details">
**Labels:** `site_id` • **Unit:** seconds • **Path:** `TouchHeartbeat`\
**Example:** `newt_site_last_heartbeat_seconds{site_id="self"} 3.2`
</Expandable>
</ResponseField>
<ResponseField name="newt_build_info" type="gauge">
Constant 1 with build metadata labels.
<Expandable title="Details">
**Labels:** `version`, `commit` • **Unit:** 1 • **Path:** Build info registration\
**Example:** `newt_build_info{version="1.2.3",commit="abc123"} 1`
</Expandable>
</ResponseField>
<ResponseField name="newt_restart_count_total" type="counter">
Process boot indicator (increments once).
<Expandable title="Details">
**Labels:** — • **Unit:** 1 • **Path:** `RegisterBuildInfo`\
**Example:** `newt_restart_count_total 1`
</Expandable>
</ResponseField>
<ResponseField name="newt_cert_rotation_total" type="counter">
Certificate rotation events keyed by result.
<Expandable title="Details">
**Labels:** `result` • **Unit:** 1 • **Path:** `IncCertRotation`\
**Example:** `newt_cert_rotation_total{result="success"} 1`
</Expandable>
</ResponseField>
<ResponseField name="newt_config_reloads_total" type="counter">
Config reload attempts keyed by result.
<Expandable title="Details">
**Labels:** `result` • **Unit:** 1 • **Path:** `telemetry.IncConfigReload`\
**Example:** `newt_config_reloads_total{result="success"} 1`
</Expandable>
</ResponseField>
<ResponseField name="newt_config_apply_seconds" type="histogram">
Duration per config-apply phase & result.
<Expandable title="Details">
**Labels:** `phase`, `result` • **Unit:** seconds • **Path:** `telemetry.ObserveConfigApply`\
**Example:** `newt_config_apply_seconds_bucket{phase="peer",result="success",le="0.1"} 3`
</Expandable>
</ResponseField>
</Expandable>
<Expandable title="Tunnel">
<ResponseField name="newt_tunnel_sessions" type="gauge">
Active sessions per tunnel (or collapsed).
<Expandable title="Details">
**Labels:** `site_id`, `tunnel_id` • **Unit:** 1 • **Path:** `RegisterStateView`\
**Example:** `newt_tunnel_sessions{site_id="self",tunnel_id="wgpub"} 2`
</Expandable>
</ResponseField>
<ResponseField name="newt_tunnel_bytes_total" type="counter">
Traffic per tunnel/direction/protocol.
<Expandable title="Details">
**Labels:** `tunnel_id`, `direction`, `protocol` • **Unit:** bytes • **Path:** Proxy manager\
**Example:** `newt_tunnel_bytes_total{direction="egress",protocol="tcp",tunnel_id="wgpub"} 8192`
</Expandable>
</ResponseField>
<ResponseField name="newt_tunnel_latency_seconds" type="histogram">
RTT samples per tunnel/transport.
<Expandable title="Details">
**Labels:** `tunnel_id`, `transport` • **Unit:** seconds • **Path:** Health checks\
**Example:** `newt_tunnel_latency_seconds_bucket{transport="wireguard",le="0.05",tunnel_id="wgpub"} 4`
</Expandable>
</ResponseField>
<ResponseField name="newt_tunnel_reconnects_total" type="counter">
Reconnect attempts by initiator & reason.
<Expandable title="Details">
**Labels:** `tunnel_id`, `initiator`, `reason` • **Unit:** 1 • **Path:** `telemetry.IncReconnect`\
**Example:** `newt_tunnel_reconnects_total{initiator="client",reason="timeout",tunnel_id="wgpub"} 3`
</Expandable>
</ResponseField>
</Expandable>
<Expandable title="Connection & Auth">
<ResponseField name="newt_connection_attempts_total" type="counter">
Auth/WebSocket attempts by transport & result.
<Expandable title="Details">
**Labels:** `transport`, `result` • **Unit:** 1 • **Path:** `telemetry.IncConnAttempt`\
**Example:** `newt_connection_attempts_total{transport="websocket",result="failure"} 2`
</Expandable>
</ResponseField>
<ResponseField name="newt_connection_errors_total" type="counter">
Connection errors by transport and type.
<Expandable title="Details">
**Labels:** `transport`, `error_type` • **Unit:** 1 • **Path:** `telemetry.IncConnError`\
**Example:** `newt_connection_errors_total{transport="auth",error_type="auth_failed"} 1`
</Expandable>
</ResponseField>
</Expandable>
<Expandable title="WebSocket">
<ResponseField name="newt_websocket_connect_latency_seconds" type="histogram">
Dial latency for Pangolin WebSocket.
<Expandable title="Details">
**Labels:** `result`, `transport` • **Unit:** seconds • **Path:** `ObserveWSConnectLatency`\
**Example:** `newt_websocket_connect_latency_seconds_bucket{result="success",transport="websocket",le="0.5"} 1`
</Expandable>
</ResponseField>
<ResponseField name="newt_websocket_disconnects_total" type="counter">
WS disconnects by reason.
<Expandable title="Details">
**Labels:** `reason`, `tunnel_id` • **Unit:** 1 • **Path:** `IncWSDisconnect`\
**Example:** `newt_websocket_disconnects_total{reason="remote_close",tunnel_id="wgpub"} 2`
</Expandable>
</ResponseField>
<ResponseField name="newt_websocket_keepalive_failures_total" type="counter">
Keepalive Ping/Pong failures.
<Expandable title="Details">
**Labels:** `reason` • **Unit:** 1 • **Path:** `telemetry.IncWSKeepaliveFailure(ctx, "ping_write")`\
**Example:** `newt_websocket_keepalive_failures_total{reason="ping_write"} 1`
</Expandable>
</ResponseField>
<ResponseField name="newt_websocket_session_duration_seconds" type="histogram">
Duration of established WebSocket sessions by result.
<Expandable title="Details">
**Labels:** `result` • **Unit:** seconds • **Path:** `telemetry.ObserveWSSessionDuration(...)`\
**Example:** `newt_websocket_session_duration_seconds_bucket{result="error",le="60"} 3`
</Expandable>
</ResponseField>
<ResponseField name="newt_websocket_connected" type="gauge">
Current WS connection status (0/1).
<Expandable title="Details">
**Labels:** — • **Unit:** 1 • **Path:** `telemetry.SetWSConnectionState(true|false)`\
**Example:** `newt_websocket_connected 1`
</Expandable>
</ResponseField>
<ResponseField name="newt_websocket_reconnects_total" type="counter">
Reconnect attempts by reason.
<Expandable title="Details">
**Labels:** `reason` • **Unit:** 1 • **Path:** `telemetry.IncWSReconnect(ctx, "ping_write")`\
**Example:** `newt_websocket_reconnects_total{reason="ping_write"} 1`
</Expandable>
</ResponseField>
<ResponseField name="newt_websocket_messages_total" type="counter">
In/out WS messages by direction & type.
<Expandable title="Details">
**Labels:** `direction`, `msg_type` • **Unit:** 1 • **Path:** `IncWSMessage`\
**Example:** `newt_websocket_messages_total{direction="out",msg_type="ping"} 4`
</Expandable>
</ResponseField>
</Expandable>
<Expandable title="Proxy">
<ResponseField name="newt_proxy_active_connections" type="gauge">
Active TCP/UDP proxy connections per tunnel/protocol.
<Expandable title="Details">
**Labels:** `protocol`, `tunnel_id` • **Unit:** 1 • **Path:** Proxy callback\
**Example:** `newt_proxy_active_connections{protocol="tcp",tunnel_id="wgpub"} 3`
</Expandable>
</ResponseField>
<ResponseField name="newt_proxy_buffer_bytes" type="gauge">
Proxy buffer pool size.
<Expandable title="Details">
**Labels:** `protocol`, `tunnel_id` • **Unit:** bytes • **Path:** Proxy callback\
**Example:** `newt_proxy_buffer_bytes{protocol="tcp",tunnel_id="wgpub"} 10240`
</Expandable>
</ResponseField>
<ResponseField name="newt_proxy_async_backlog_bytes" type="gauge">
Unflushed async byte backlog.
<Expandable title="Details">
**Labels:** `protocol`, `tunnel_id` • **Unit:** bytes • **Path:** Proxy callback\
**Example:** `newt_proxy_async_backlog_bytes{protocol="udp",tunnel_id="wgpub"} 4096`
</Expandable>
</ResponseField>
<ResponseField name="newt_proxy_drops_total" type="counter">
Proxy write drops per protocol/tunnel.
<Expandable title="Details">
**Labels:** `protocol`, `tunnel_id` • **Unit:** 1 • **Path:** `IncProxyDrops`\
**Example:** `newt_proxy_drops_total{protocol="udp",tunnel_id="wgpub"} 2`
</Expandable>
</ResponseField>
<ResponseField name="newt_proxy_accept_total" type="counter">
Proxy accept events by result/reason.
<Expandable title="Details">
**Labels:** `tunnel_id`, `protocol`, `result`, `reason` • **Unit:** 1 • **Path:** `telemetry.IncProxyAccept(...)`\
**Example:** `newt_proxy_accept_total{protocol="tcp",result="failure",reason="timeout"} 1`
</Expandable>
</ResponseField>
<ResponseField name="newt_proxy_connections_total" type="counter">
Connection lifecycle events (opened/closed).
<Expandable title="Details">
**Labels:** `tunnel_id`, `protocol`, `event` • **Unit:** 1 • **Path:** `telemetry.IncProxyConnectionEvent(...)`\
**Example:** `newt_proxy_connections_total{protocol="tcp",event="opened"} 1`
</Expandable>
</ResponseField>
<ResponseField name="newt_proxy_connection_duration_seconds" type="histogram">
Duration of completed proxy connections.
<Expandable title="Details">
**Labels:** `tunnel_id`, `protocol`, `result` • **Unit:** seconds • **Path:** `telemetry.ObserveProxyConnectionDuration(...)`\
**Example:** `newt_proxy_connection_duration_seconds_bucket{protocol="tcp",result="success",le="1"} 3`
</Expandable>
</ResponseField>
</Expandable>
</ResponseField>
</Tab>
</Tabs>
***
## References
* <Link href="https://opentelemetry.io/docs/">OpenTelemetry Documentation</Link>
* <Link href="https://prometheus.io/docs/introduction/overview/">Prometheus Documentation</Link>
<Tip>
Have improvements or a missing metric? Open an issue or PR referencing this page.
</Tip>
@@ -0,0 +1,240 @@
> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pangolin.net/llms.txt
> Use this file to discover all available pages before exploring further.
# Private Configuration File
> Configure advanced Pangolin settings using the privateConfig.yml file for enterprise features
<div id="pangolin-toc-cta" className="pangolin-toc-cta-source">
<Card title="Try free on Pangolin Cloud" icon="cloud" href="https://app.pangolin.net/auth/signup" arrow="true" cta="Sign up free">
Fastest way to get started with Pangolin using the hosted control plane. No credit card required.
</Card>
</div>
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" theme={null}
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 theme={null}
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 theme={null}
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 theme={null}
server:
encryption_key_path: "./config/encryption.pem"
```
<Warning>
The `encryption_key_path` must point to a valid RSA key file. Generate one using:
```bash theme={null}
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 theme={null}
redis:
host: "redis.example.com"
```
</ResponseField>
<ResponseField name="port" type="number" required>
Redis server port (1-65535).
```yaml theme={null}
redis:
port: 6379
```
</ResponseField>
<ResponseField name="password" type="string">
Redis authentication password.
```yaml theme={null}
redis:
password: "your-secure-password"
```
</ResponseField>
<ResponseField name="db" type="number" default="0">
Redis database number (0-15 typically).
```yaml theme={null}
redis:
db: 0
```
</ResponseField>
<ResponseField name="replicas" type="array">
Array of read replica configurations for high-availability deployments.
```yaml theme={null}
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>
### 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 theme={null}
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 theme={null}
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 theme={null}
flags:
enable_redis: true
```
</ResponseField>
<ResponseField name="use_pangolin_dns" type="boolean" default="false">
Use Pangolin DNS servers for client connections instead of external DNS servers for DNS delegation and CNAME setups. Used for clustering Pangolin nodes. REQUIRES EXTERNAL COMPONENTS. PLEASE CONTACT SUPPORT TO OBTAIN ACCESS BEFORE ENABLING.
```yaml theme={null}
flags:
use_pangolin_dns: true
```
</ResponseField>
</Expandable>
</ResponseField>
### Branding Configuration
Please refer to the [branding configuration documentation](/manage/branding).
@@ -0,0 +1,55 @@
> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pangolin.net/llms.txt
> Use this file to discover all available pages before exploring further.
# Telemetry
> Understanding Pangolin's anonymous usage data collection
<div id="pangolin-toc-cta" className="pangolin-toc-cta-source">
<Card title="Try free on Pangolin Cloud" icon="cloud" href="https://app.pangolin.net/auth/signup" arrow="true" cta="Sign up free">
Fastest way to get started with Pangolin using the hosted control plane. No credit card required.
</Card>
</div>
Pangolin collects anonymous usage telemetry to help us understand how the software is used and guide future improvements
and feature development.
## What We Collect
The telemetry system collects **anonymous, aggregated data** about your Pangolin deployment. For example:
* **System metrics**: Number of sites, users, resources, and clients
* **Usage patterns**: Resource types, protocols, and SSO configurations
* **Performance data**: Site traffic volumes and online status
* **Deployment info**: App version and installation timestamp
## Privacy & Anonymity
**No personal information is ever collected or transmitted.** All data is:
* **Anonymized**: Identifying info is hashed using SHA-256
* **Non-identifying**: Cannot be used to identify specific users or organizations
## Configuration
You can control telemetry collection in your `config.yml`:
```yaml theme={null}
app:
telemetry:
anonymous_usage: true # Set to false to disable
```
## What This Helps
Anonymous usage data helps us:
* Identify popular features and usage patterns
* Prioritize development efforts
* Improve performance and reliability
* Make Pangolin better for everyone
If you have concerns about telemetry collection, you can disable it entirely by setting `anonymous_usage: false` in your
configuration.
@@ -0,0 +1,286 @@
> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pangolin.net/llms.txt
> Use this file to discover all available pages before exploring further.
# Wildcard Domains
> Configure wildcard SSL certificates for automatic subdomain security with DNS-01 challenge
<div id="pangolin-toc-cta" className="pangolin-toc-cta-source">
<Card title="Try free on Pangolin Cloud" icon="cloud" href="https://app.pangolin.net/auth/signup" arrow="true" cta="Sign up free">
Fastest way to get started with Pangolin using the hosted control plane. No credit card required.
</Card>
</div>
Wildcard certificates allow you to secure unlimited subdomains with a single SSL certificate, eliminating the need to
generate individual certificates for each subdomain. Pangolin uses Traefik's built-in Let's Encrypt integration to
automatically manage these certificates.
<Warning>
Before setting up wildcard certificates, you must have a domain that you own and control. You must also have access to the DNS records for this domain.
</Warning>
<Info>
Since Pangolin uses Traefik as a reverse proxy, it has built-in support for Let's Encrypt certificates. This allows you to easily secure your Pangolin instance and all proxied resources with HTTPS. Let's Encrypt provides free SSL certificates, which are automatically renewed.
</Info>
If you used the default settings during installation, your Traefik instance should be set up to use `HTTP-01` challenge
for certificate generation. This challenge is the easiest to configure and requires that the Traefik instance be
accessible from the internet on port 80.
<Note>
It is highly recommended that you read the [official Traefik documentation](https://doc.traefik.io/traefik/https/acme/) on ACME and Let's Encrypt before proceeding.
</Note>
## Benefits of Wildcard Certificates
<CardGroup cols={3}>
<Card title="Single Certificate" icon="certificate">
Secure unlimited subdomains with one certificate, reducing management overhead.
</Card>
<Card title="Instant Subdomains" icon="bolt">
Add new subdomains without waiting for certificate generation (up to a few minutes).
</Card>
<Card title="Rate Limit Friendly" icon="shield">
Reduce Let's Encrypt rate limit impact by using fewer certificate requests.
</Card>
</CardGroup>
### Examples
* A wildcard cert `*.example.com` could protect:
* `api.example.com`
* `blog.example.com`
* `dashboard.example.com`
* Another wildcard `*.subdomain.example.com` could protect:
* `api.subdomain.example.com`
* `blog.subdomain.example.com`
<Info>
The [rate limits](https://letsencrypt.org/docs/rate-limits/) for Let's Encrypt are per domain. Using a wildcard certificate reduces the number of domains you have, which can help you avoid hitting these limits.
</Info>
## Setting Up Wildcard Certificates
<Steps>
<Step title="Stop the stack">
Make sure the stack is not running before making configuration changes.
</Step>
<Step title="Update Traefik configuration">
Update the Traefik configuration to use the DNS-01 challenge instead of the HTTP-01 challenge. This tells Traefik to use your DNS provider to create the DNS records needed for the challenge.
</Step>
<Step title="Configure Pangolin">
Set the `prefer_wildcard_cert` flag to `true` in the Pangolin configuration file for your domain. This is also configurable in the Pangolin dashboard (once restarted).
```yaml title="config.yml" highlight={4} theme={null}
domains:
domain1:
base_domain: "example.com"
prefer_wildcard_cert: true
```
</Step>
</Steps>
<Note>
This setting will try to encourage Traefik to request one wildcard certificate for each level of the domain used by your existing resources.
**Example**: If you have two resources `blog.example.com` and `blog.subdomain.example.com`, Traefik should try to
request a wildcard certificate for `*.example.com` and `*.subdomain.example.com` automatically for you.
</Note>
## Traefik Configuration
### Default Config for HTTP-01 Challenge
This is the default config generated by the installer. This is shown here for reference to compare with the wildcard
config below.
<AccordionGroup>
<Accordion title="1. HTTP Challenge Configuration">
Tell Traefik to use the `web` entrypoint for the HTTP challenge.
```yaml title="traefik_config.yml" highlight={4,5} theme={null}
certificatesResolvers:
letsencrypt:
acme:
httpChallenge:
entryPoint: web
email: admin@example.com
storage: "/letsencrypt/acme.json"
caServer: "https://acme-v02.api.letsencrypt.org/directory"
```
</Accordion>
<Accordion title="2. Dynamic Configuration">
Set the cert resolver to `letsencrypt` and the entrypoint to `websecure` in the dynamic config.
```yaml title="dynamic_config.yml" theme={null}
next-router:
rule: "Host(`pangolin.example.com`) && !PathPrefix(`/api/v1`)"
service: next-service
entryPoints:
- websecure
tls:
certResolver: letsencrypt
```
</Accordion>
</AccordionGroup>
### Wildcard Config for DNS-01 Challenge
<Steps>
<Step title="1. Configure DNS Challenge">
Tell Traefik to use your DNS provider for the DNS challenge. In this example, we are using Cloudflare.
```yaml title="traefik_config.yml" highlight={4,5} theme={null}
certificatesResolvers:
letsencrypt:
acme:
dnsChallenge:
provider: "cloudflare" # your DNS provider
# 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"
```
</Step>
<Step title="2. Add Wildcard Domains">
Add the domain and wildcard domain to the domains section of the next (front end) router in the dynamic config. This tells Traefik to generate a wildcard certificate for the base domain and all subdomains.
```yaml title="dynamic_config.yml" highlight={8-12} theme={null}
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"
```
</Step>
<Step title="3. Add Environment Variables">
Add the environment variables for your DNS provider to the Traefik service in the docker compose file. This allows Traefik to authenticate with your DNS provider to create the DNS records needed for the challenge.
```yaml title="docker-compose.yml" highlight={11-13} theme={null}
traefik:
image: traefik:v3.4.0
container_name: traefik
restart: unless-stopped
network_mode: service:gerbil
depends_on:
pangolin:
condition: service_healthy
command:
- --configFile=/etc/traefik/traefik_config.yml
# Add the environment variables for your DNS provider.
environment:
CLOUDFLARE_DNS_API_TOKEN: "your-cloudflare-api-token"
volumes:
- ./config/traefik:/etc/traefik:ro
- ./config/letsencrypt:/letsencrypt
```
</Step>
</Steps>
<Warning>
If you're using Cloudflare, make sure your API token has the permissions Zone/Zone/Read and Zone/DNS/Edit and make sure it applies to all zones.
</Warning>
<Info>
Traefik supports most DNS providers. You can find a full list of supported providers and how to configure them in the [Traefik documentation on providers](https://doc.traefik.io/traefik/https/acme/#providers).
</Info>
## Verify it Works
<Tips>
<Tip title="Clear Old Certificates">
You can ensure Traefik doesn't try to use the old certs by deleting the previously used `acme.json` file. This will force Traefik to generate a new certificate on the next start.
</Tip>
</Tips>
<Steps>
<Step title="Start the stack">
Start the stack and watch the logs. You should notice that Traefik is making calls to your DNS provider to create the necessary records to complete the challenge.
</Step>
<Step title="Check logs">
For debugging purposes, you may find it useful to set the log level of Traefik to `debug` in the `traefik_config.yml` file.
</Step>
<Step title="Test new resource">
After Traefik is done waiting for the cert to verify, try to create a new resource with an unused subdomain. Traefik should not try to generate a new certificate, but instead use the wildcard certificate. The domain should also be secured immediately instead of waiting for a new certificate to be generated.
</Step>
<Step title="Verify certificate">
You can also check the volume (in the example above at `config/letsencrypt/`) for the correct certificates. In the `acme.json` file you should see something similar to the following. Note the `*.` in the domain.
</Step>
</Steps>
```json highlight={5} theme={null}
{
"Certificates": [
{
"domain": {
"main": "example.com",
"sans": [
"*.example.com"
]
},
"certificate": "...",
"key": "...",
"Store": "default"
}
]
}
```
## Troubleshooting
<AccordionGroup>
<Accordion title="Certificate not generating">
**Problem**: Wildcard certificate not being created.
**Solutions**:
* Verify DNS provider credentials are correct
* Check that API token has proper permissions
* Ensure domain ownership and DNS access
* Review Traefik logs for specific error messages
</Accordion>
<Accordion title="DNS challenge failing">
**Problem**: DNS-01 challenge not completing.
**Solutions**:
* Verify DNS provider is supported by Traefik
* Check API token permissions and scope
* Ensure DNS propagation has completed
* Review provider-specific configuration
</Accordion>
<Accordion title="Old certificates still being used">
**Problem**: Traefik using old HTTP-01 certificates.
**Solution**: Delete the `acme.json` file to force new certificate generation.
</Accordion>
</AccordionGroup>
@@ -0,0 +1,44 @@
> ## Documentation Index
> Fetch the complete documentation index at: https://docs.pangolin.net/llms.txt
> Use this file to discover all available pages before exploring further.
# Without Tunneling
> Use Pangolin as a local reverse proxy without Gerbil tunneling
<div id="pangolin-toc-cta" className="pangolin-toc-cta-source">
<Card title="Try free on Pangolin Cloud" icon="cloud" href="https://app.pangolin.net/auth/signup" arrow="true" cta="Sign up free">
Fastest way to get started with Pangolin using the hosted control plane. No credit card required.
</Card>
</div>
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>