Pangolin is an open-source, self-hosted tunneling server and reverse proxy that enables secure exposure of local services to the internet without requiring static IPs or complex firewall configuration. It provides encrypted tunnels similar to Cloudflare Tunnels, ngrok, and frp, with full control over infrastructure and data flow.
# Pull the official Pangolin Docker image
docker pull pangolin:latest
# Run Pangolin server
docker run -d \
--name pangolin-server \
-p 8080:8080 \
-p 443:443 \
-v /etc/pangolin:/etc/pangolin \
pangolin:latest
# Run with environment variables
docker run -d \
--name pangolin-server \
-p 8080:8080 \
-p 443:443 \
-e PANGOLIN_PORT=8080 \
-e PANGOLIN_TLS=true \
-v /etc/pangolin:/etc/pangolin \
pangolin:latest
# Download latest release
wget https://github.com/pangolin/releases/download/v1.x.x/pangolin-linux-amd64.tar.gz
# Extract
tar -xzf pangolin-linux-amd64.tar.gz
# Make executable
chmod +x pangolin
# Install to system path
sudo mv pangolin /usr/local/bin/
# Verify installation
pangolin --version
# Clone repository
git clone https://github.com/pangolin/pangolin.git
cd pangolin
# Build using Go
go build -o pangolin ./cmd/server
# Run
./pangolin --config config.yaml
# config.yaml
server:
# Listen address and port
bind_addr: "0.0.0.0"
listen_port: 8080
# TLS/SSL configuration
tls:
enabled: true
cert_file: "/etc/pangolin/certs/server.crt"
key_file: "/etc/pangolin/certs/server.key"
# Domain configuration
domain: "tunnel.example.com"
# Token authentication
token: "your-secure-token-here"
# Logging configuration
logging:
level: info
format: json
output: stdout
# Database (optional)
database:
type: sqlite
path: /var/lib/pangolin/pangolin.db
server:
bind_addr: "0.0.0.0"
listen_port: 8080
# Max concurrent connections
max_connections: 10000
# Connection timeout (seconds)
connection_timeout: 300
# TLS minimum version
tls_min_version: "1.2"
# Enable compression
compression: true
# Rate limiting
rate_limit:
enabled: true
requests_per_second: 1000
burst_size: 2000
# Using environment variables
export PANGOLIN_BIND_ADDR="0.0.0.0"
export PANGOLIN_PORT="8080"
export PANGOLIN_DOMAIN="tunnel.example.com"
export PANGOLIN_TOKEN="secure-token"
export PANGOLIN_TLS_ENABLED="true"
export PANGOLIN_TLS_CERT="/etc/pangolin/certs/server.crt"
export PANGOLIN_TLS_KEY="/etc/pangolin/certs/server.key"
pangolin
# Install Pangolin client on local machine
# macOS
brew install pangolin
# Linux
sudo apt-get install pangolin
# Or download binary
wget https://github.com/pangolin/releases/download/v1.x.x/pangolin-client-linux-amd64
# Or using Go
go install github.com/pangolin/pangolin/cmd/client@latest
# Start a simple HTTP tunnel
pangolin-client --server tunnel.example.com:8080 \
--token your-token \
--local-port 3000 \
--tunnel-name myapp
# Create tunnel with custom remote port
pangolin-client --server tunnel.example.com:8080 \
--token your-token \
--local-port 3000 \
--remote-port 8443
# List active tunnels
pangolin-client --server tunnel.example.com:8080 \
--token your-token \
--list-tunnels
# ~/.pangolin/client-config.yaml
server:
address: "tunnel.example.com"
port: 8080
token: "your-secure-token"
tls: true
tunnels:
- name: "web-app"
local_addr: "127.0.0.1"
local_port: 3000
remote_hostname: "web-app.tunnel.example.com"
type: "http"
- name: "api-server"
local_addr: "127.0.0.1"
local_port: 5000
remote_hostname: "api.tunnel.example.com"
type: "http"
- name: "ssh"
local_addr: "127.0.0.1"
local_port: 22
remote_port: 2222
type: "tcp"
pangolin-client --config ~/.pangolin/client-config.yaml
# Expose local HTTP service
pangolin-client --server tunnel.example.com:8080 \
--token mytoken \
--local-addr 127.0.0.1 \
--local-port 8000 \
--service-type http \
--tunnel-name website
# Access via
# http://website.tunnel.example.com
# Expose HTTPS service
pangolin-client --server tunnel.example.com:8080 \
--token mytoken \
--local-addr 127.0.0.1 \
--local-port 8443 \
--service-type https \
--tunnel-name secure-app
# Server automatically handles HTTPS
# Access via https://secure-app.tunnel.example.com
# Configure multiple services per domain
tunnels:
- name: "app1"
local_port: 3000
domain_routes:
- path: "/"
target: "127.0.0.1:3000"
- name: "app2"
local_port: 3001
domain_routes:
- path: "/api"
target: "127.0.0.1:3001"
# Create TCP tunnel for database
pangolin-client --server tunnel.example.com:8080 \
--token mytoken \
--local-port 5432 \
--remote-port 5432 \
--protocol tcp \
--tunnel-name postgres
# Create TCP tunnel for SSH
pangolin-client --server tunnel.example.com:8080 \
--token mytoken \
--local-port 22 \
--remote-port 2222 \
--protocol tcp \
--tunnel-name ssh-access
# Connect via tunnel
ssh -p 2222 user@tunnel.example.com
psql -h tunnel.example.com -p 5432
# Create UDP tunnel
pangolin-client --server tunnel.example.com:8080 \
--token mytoken \
--local-port 53 \
--remote-port 53 \
--protocol udp \
--tunnel-name dns
# Create UDP tunnel for gaming/media
pangolin-client --server tunnel.example.com:8080 \
--token mytoken \
--local-port 5005 \
--remote-port 5005 \
--protocol udp \
--tunnel-name game-server
tunnels:
- name: "database"
local_port: 5432
remote_port: 5432
protocol: tcp
- name: "dns"
local_port: 53
remote_port: 53
protocol: udp
- name: "web"
local_port: 80
remote_port: 80
protocol: tcp
# Generate private key
openssl genrsa -out server.key 2048
# Generate certificate signing request
openssl req -new -key server.key -out server.csr \
-subj "/CN=tunnel.example.com"
# Generate self-signed certificate
openssl x509 -req -days 365 -in server.csr \
-signkey server.key -out server.crt
# Verify certificate
openssl x509 -in server.crt -text -noout
# Install certbot
sudo apt-get install certbot
# Obtain certificate
sudo certbot certonly --standalone \
-d tunnel.example.com
# Configure in Pangolin
server:
tls:
enabled: true
cert_file: "/etc/letsencrypt/live/tunnel.example.com/fullchain.pem"
key_file: "/etc/letsencrypt/live/tunnel.example.com/privkey.pem"
# Auto-renewal script
cert_auto_renew: true
cert_check_interval: 86400 # 24 hours
server:
tls:
enabled: true
cert_file: "/etc/pangolin/certs/server.crt"
key_file: "/etc/pangolin/certs/server.key"
# Minimum TLS version
min_version: "1.2"
# Cipher suites
cipher_suites:
- "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256"
- "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384"
# Client certificates (mTLS)
client_auth: "verify_if_given"
client_cert_file: "/etc/pangolin/certs/ca.crt"
# Generate secure token
openssl rand -hex 32
# Use token with client
pangolin-client --server tunnel.example.com:8080 \
--token abc123def456... \
--local-port 3000 \
--tunnel-name app
# Multiple tokens
pangolin-client --server tunnel.example.com:8080 \
--tokens token1,token2,token3 \
--local-port 3000
# Server config with API keys
auth:
type: "api_key"
api_keys:
- key: "pk_live_1234567890abcdef"
name: "production"
rate_limit: 10000
allowed_tunnels:
- "web-app"
- "api-server"
- key: "pk_test_0987654321fedcba"
name: "staging"
allowed_tunnels: "*"
# Client with API key
pangolin-client --server tunnel.example.com:8080 \
--api-key pk_live_1234567890abcdef \
--local-port 3000
# Server config with IP restrictions
access_control:
enabled: true
ip_whitelist:
- "203.0.113.0/24" # Office network
- "198.51.100.50" # Specific IP
- "2001:db8::/32" # IPv6 range
ip_blacklist:
- "192.0.2.5" # Blacklisted IP
# Add wildcard DNS record to your domain
# Using dig to verify
dig *.tunnel.example.com
# DNS A record
*.tunnel.example.com A 203.0.113.10
# DNS AAAA record (IPv6)
*.tunnel.example.com AAAA 2001:db8::1
server:
# If server is on residential network
ddns:
enabled: false
# Or use reverse DNS
reverse_dns:
enabled: true
default_domain: "tunnel.example.com"
# Map custom domains to tunnels
domain_mappings:
- domain: "myapp.com"
tunnel: "web-app"
- domain: "api.myapp.com"
tunnel: "api-server"
- domain: "admin.internal.com"
tunnel: "admin-panel"
access_control: "token_required"
# Server A (primary)
server:
listen_port: 8080
cluster:
enabled: true
node_id: "node-1"
peers:
- "tunnel-server-2.example.com:8080"
- "tunnel-server-3.example.com:8080"
# Server B (standby)
server:
listen_port: 8080
cluster:
enabled: true
node_id: "node-2"
peers:
- "tunnel-server-1.example.com:8080"
- "tunnel-server-3.example.com:8080"
tunnels:
- name: "web-app-lb"
type: "http"
load_balance:
enabled: true
algorithm: "round-robin" # round-robin, least-connections, random
targets:
- "127.0.0.1:3000"
- "127.0.0.1:3001"
- "127.0.0.1:3002"
server:
health_checks:
enabled: true
interval: 30 # seconds
timeout: 5
tunnels:
- name: "api"
health_check:
enabled: true
path: "/health"
interval: 10
expected_status: 200
logging:
level: "debug" # debug, info, warn, error
format: "json" # json or text
output: "file"
file: "/var/log/pangolin/server.log"
# Log rotation
max_size_mb: 100
max_backups: 10
max_age_days: 30
# Enable metrics endpoint
pangolin --metrics-enable \
--metrics-port 9090 \
--config config.yaml
# Access metrics
curl http://localhost:9090/metrics
# Prometheus scrape config
scrape_configs:
- job_name: 'pangolin'
static_configs:
- targets: ['localhost:9090']
# List active tunnels and connections
pangolin-client --server tunnel.example.com:8080 \
--token mytoken \
--stats
# Server-side connection info
pangolin --config config.yaml --stats-interval 10
| Feature | Pangolin | Cloudflare Tunnels |
|---|
| Self-hosted | Yes | No (SaaS) |
| Cost | Free (OSS) | Paid plans |
| Data ownership | 100% | Cloudflare controls |
| Setup complexity | Medium | Low |
| Custom certificates | Yes | Limited |
| Enterprise support | Community | Yes |
| Multi-region | Complex | Built-in |
| Feature | Pangolin | ngrok |
|---|
| Self-hosted option | Yes | No |
| Free tier | Yes | Yes |
| TCP tunneling | Full | Limited |
| Custom domains | Yes | Paid |
| SSL certificates | Own certs | ngrok managed |
| Bandwidth limits | None | Yes (free tier) |
| Open source | Yes | No |
| Feature | Pangolin | frp |
|---|
| Written in | Go | Go |
| Configuration | YAML | INI |
| Web UI | Available | Limited |
| Dashboard | Yes | Basic |
| Community | Growing | Established |
| Container ready | Yes | Yes |
| Plugin system | Yes | Yes |
server:
# Restrict to internal network
bind_addr: "10.0.0.5"
# Use strong token
token: "$(openssl rand -hex 32)"
# Enable TLS
tls:
enabled: true
min_version: "1.3"
# Disable dangerous features
allow_file_download: false
# Restrict tunnels
max_tunnels_per_client: 10
# Rate limiting
rate_limit:
enabled: true
requests_per_second: 500
# Store token in environment variable
export PANGOLIN_TOKEN=$(cat ~/.pangolin/token)
# Use token from file (with restricted permissions)
chmod 600 ~/.pangolin/token
pangolin-client --token-file ~/.pangolin/token \
--server tunnel.example.com:8080 \
--local-port 3000
# Rotate tokens regularly
pangolin-server --rotate-token pk_old_token pk_new_token
server:
# Firewall rules
firewall:
enabled: true
inbound_rules:
- port: 8080
protocol: tcp
allow_from: "10.0.0.0/8"
# Disable IP spoofing
check_client_ip: true
# Log all access attempts
audit_log:
enabled: true
file: "/var/log/pangolin/audit.log"
client:
tls:
# Pin server certificate
pin_certificate: true
cert_fingerprint: "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="
| Issue | Solution |
|---|
| Connection timeout | Check firewall rules, server address, token |
| TLS handshake failed | Verify certificates, check TLS version compatibility |
| High latency | Enable compression, check network, use closer server |
| Token invalid | Regenerate token, verify token hasn’t expired |
| Tunnel not accessible | Check DNS records, firewall, client still running |
# Run server in debug mode
PANGOLIN_LOG_LEVEL=debug pangolin --config config.yaml
# Run client in debug mode
PANGOLIN_LOG_LEVEL=debug pangolin-client \
--server tunnel.example.com:8080 \
--token mytoken \
--local-port 3000 \
--debug