How to Self-Host Firezone: WireGuard VPN 2026
How to Self-Host Firezone: WireGuard VPN and Zero-Trust Access in 2026
TL;DR
Tailscale charges $6/user/month and routes your device metadata through their coordination servers. Cloudflare Tunnel works only for HTTP/S services — you can't tunnel arbitrary TCP or UDP traffic. Firezone is the open source alternative built on WireGuard: a self-hosted VPN gateway with SSO integration, user self-service portal, per-user access policies, and the same kernel-level WireGuard performance (3-4x faster than OpenVPN, sub-10ms latency). For teams who want full network control, data sovereignty, and no per-seat fees, Firezone is the most capable self-hosted zero-trust option available. This guide covers a production-ready deployment using the stable self-hosted architecture.
Key Takeaways
- WireGuard-based: Runs on kernel WireGuard — the same protocol Tailscale, Mullvad, and most modern VPNs use under the hood
- 3-4x faster than OpenVPN: Sub-10ms latency overhead vs 60-80ms typical for OpenVPN
- 6,000+ GitHub stars: YC W22-backed, actively maintained with enterprise and cloud offerings
- SSO/OIDC built-in: Connect to Google Workspace, Okta, Azure AD, or any OIDC provider
- User self-service portal: Employees generate their own WireGuard configs — no admin intervention per device
- Tailscale comparison: Firezone gives you full infrastructure control; Tailscale is easier to set up but routes coordination traffic through their servers
- License nuance: Core is Apache 2.0; Elixir backend is Elastic License 2.0 — self-hosting is permitted but not officially supported in production by the Firezone team
Why Teams Self-Host Firezone
The VPN market has consolidated around two approaches: traditional VPNs (OpenVPN, IPsec) that require manual peer management, and modern zero-trust tools (Tailscale, Cloudflare Tunnel) that abstract away the complexity but introduce vendor dependency.
Firezone sits in the middle: WireGuard performance with zero-trust access control, but self-hosted and fully under your control.
The case for self-hosting vs Tailscale:
| Consideration | Firezone (Self-Hosted) | Tailscale |
|---|---|---|
| Coordination server | Your servers | Tailscale's servers |
| Per-user cost | ~$0 (VPS share) | $6/user/month |
| 25-user cost | ~$12/mo (VPS) | $150/mo |
| SSO/OIDC | ✅ Any OIDC provider | ✅ Any OIDC provider |
| Self-service portal | ✅ | ✅ |
| Mesh networking | ❌ Hub-and-spoke | ✅ Peer-to-peer |
| Setup complexity | Medium-High | Low |
| Audit logs | ✅ | ✅ (paid) |
| Data sovereignty | ✅ Full | ❌ Metadata on Tailscale |
| Open source | ✅ (partial) | ❌ Tailscale client only |
Tailscale wins on setup simplicity and mesh networking (direct peer-to-peer connections). Firezone wins on cost at scale, data sovereignty, and full infrastructure control.
Important licensing note: Firezone's self-hosted architecture is split across two license regimes — the core gateway/client code uses Apache 2.0, while the Elixir-based web portal/backend uses Elastic License 2.0. Both permit self-hosting. The Firezone team focuses on their cloud product and enterprise tier; community-maintained guides cover the self-hosted deployment.
System Requirements
Firezone runs lighter than most self-hosted apps:
- CPU: 1 vCPU minimum (2 vCPU recommended)
- RAM: 1GB minimum (2GB recommended)
- Storage: 10GB SSD
- OS: Ubuntu 22.04+, Debian 12+, or any Docker-compatible Linux
- Docker: v24.0+
- Docker Compose: v2.x plugin
- Ports:
51820/udp— WireGuard tunnel (must be accessible from internet)80/tcp— Redirect to HTTPS443/tcp— Web admin portal (HTTPS)
- Domain: Required for TLS on the admin portal
- Kernel: Linux 5.6+ (for kernel WireGuard) — Ubuntu 22.04 qualifies out of the box
Recommended server (2026 pricing):
- Hetzner CX11: 1 vCPU, 2GB RAM, €3.29/month — up to 50 VPN users
- Hetzner CX22: 2 vCPU, 4GB RAM, €4.35/month — 50-200 VPN users
Architecture Overview
┌─────────────────────────────────────────────────────────┐
│ Firezone Stack │
│ │
│ Internet Your Network │
│ │
│ Client Device │
│ (WireGuard)──── 51820/udp ────▶ ┌─────────────────┐ │
│ │ Firezone │ │
│ Admin Browser │ (Elixir App) │ │
│ ─── 443/tcp ─────────────────▶ │ │ │
│ │ ┌───────────┐ │ │
│ │ │ WireGuard │ │ │
│ │ │ Kernel │ │ │
│ │ │ Module │ │ │
│ │ └───────────┘ │ │
│ └────────┬────────┘ │
│ │ │
│ ┌────────▼────────┐ │
│ │ PostgreSQL │ │
│ │ (sessions, │ │
│ │ users, keys) │ │
│ └─────────────────┘ │
│ │
│ VPN Clients tunnel to protected internal resources │
│ via the Firezone gateway │
└─────────────────────────────────────────────────────────┘
Firezone acts as a WireGuard gateway. VPN clients connect to your Firezone server, which routes traffic to your internal network or specific allowed resources. The Elixir web app manages user accounts, device keys, and access policies.
Self-Hosting with Docker Compose
Step 1: Create Project Directory
mkdir -p ~/firezone && cd ~/firezone
Step 2: Generate Secrets
# Generate cryptographic secrets for Firezone
echo "SECRET_KEY_BASE=$(docker run --rm firezone/firezone bin/gen_secret)"
echo "GUARDIAN_SECRET_KEY=$(docker run --rm firezone/firezone bin/gen_secret)"
echo "DATABASE_ENCRYPTION_KEY=$(docker run --rm firezone/firezone bin/gen_secret)"
Copy the output into your .env file.
Step 3: Create the Environment File
# .env
EXTERNAL_URL=https://vpn.yourdomain.com
ADMIN_EMAIL=admin@yourdomain.com
ADMIN_PASSWORD=changeme-strong-password
# Generated secrets from Step 2
SECRET_KEY_BASE=your-generated-secret-here
GUARDIAN_SECRET_KEY=your-generated-secret-here
DATABASE_ENCRYPTION_KEY=your-generated-secret-here
# WireGuard
WIREGUARD_PORT=51820
WIREGUARD_IPV4_NETWORK=10.3.2.0/24
WIREGUARD_IPV4_ADDRESS=10.3.2.1
WIREGUARD_IPV6_NETWORK=fd00::3:2:0/120
WIREGUARD_IPV6_ADDRESS=fd00::3:2:1
# Database
DATABASE_PASSWORD=changeme-db-password
Step 4: Create docker-compose.yml
# docker-compose.yml
version: "3.8"
services:
firezone:
image: firezone/firezone:latest
container_name: firezone
ports:
- "13000:13000" # Internal HTTP (proxied by caddy)
- "51820:51820/udp" # WireGuard — must be open to internet
environment:
EXTERNAL_URL: ${EXTERNAL_URL}
DEFAULT_ADMIN_EMAIL: ${ADMIN_EMAIL}
DEFAULT_ADMIN_PASSWORD: ${ADMIN_PASSWORD}
DATABASE_ENCRYPTION_KEY: ${DATABASE_ENCRYPTION_KEY}
SECRET_KEY_BASE: ${SECRET_KEY_BASE}
GUARDIAN_SECRET_KEY: ${GUARDIAN_SECRET_KEY}
DATABASE_URL: ecto://firezone:${DATABASE_PASSWORD}@postgres/firezone
WIREGUARD_PORT: ${WIREGUARD_PORT}
WIREGUARD_IPV4_NETWORK: ${WIREGUARD_IPV4_NETWORK}
WIREGUARD_IPV4_ADDRESS: ${WIREGUARD_IPV4_ADDRESS}
WIREGUARD_IPV6_NETWORK: ${WIREGUARD_IPV6_NETWORK}
WIREGUARD_IPV6_ADDRESS: ${WIREGUARD_IPV6_ADDRESS}
DISABLE_VPN_ON_OIDC_ERROR: "true"
cap_add:
- NET_ADMIN
- SYS_MODULE
sysctls:
- net.ipv6.conf.all.disable_ipv6=0
- net.ipv4.ip_forward=1
- net.ipv6.conf.all.forwarding=1
volumes:
- firezone-data:/var/firezone
depends_on:
postgres:
condition: service_healthy
restart: unless-stopped
postgres:
image: postgres:15-alpine
container_name: firezone-db
environment:
POSTGRES_DB: firezone
POSTGRES_USER: firezone
POSTGRES_PASSWORD: ${DATABASE_PASSWORD}
volumes:
- postgres-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U firezone"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
caddy:
image: caddy:2-alpine
container_name: firezone-proxy
ports:
- "80:80"
- "443:443"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- caddy-data:/data
depends_on:
- firezone
restart: unless-stopped
volumes:
firezone-data:
postgres-data:
caddy-data:
Step 5: Create Caddyfile
# Caddyfile
vpn.yourdomain.com {
reverse_proxy firezone:13000
}
Step 6: Launch
# Start everything
docker compose up -d
# Watch startup logs (takes 60-90 seconds)
docker compose logs -f firezone
Look for:
[info] Running FzWeb.Endpoint with cowboy 2.9.0 at :::13000
[info] WireGuard interface wg-firezone initialized
Open https://vpn.yourdomain.com to access the admin portal.
First-Run Configuration
Verify WireGuard Interface
# Confirm WireGuard is running inside the container
docker exec firezone wg show
# Expected output:
interface: wg-firezone
public key: [your-server-public-key]
private key: (hidden)
listening port: 51820
If wg show returns nothing, the NET_ADMIN and SYS_MODULE capabilities may be blocked. Verify your Docker host allows these capabilities (most VPS providers do by default).
Confirm UDP Port 51820
Test from an external machine:
# From outside your network:
nmap -sU -p 51820 vpn.yourdomain.com
# Should return: 51820/udp open|filtered
Most firewalls drop UDP by default — ensure port 51820/udp is open in your server's firewall rules:
# UFW (Ubuntu)
ufw allow 51820/udp
# iptables
iptables -A INPUT -p udp --dport 51820 -j ACCEPT
Configuring OIDC/SSO (Google, Okta, Azure AD)
Firezone supports any OIDC-compliant identity provider. This means users log in with their existing company credentials — no separate Firezone accounts to manage.
Google Workspace
In Google Cloud Console:
APIs & Services → Credentials → Create Credentials → OAuth 2.0 Client ID
Application type: Web application
Authorized redirect URI: https://vpn.yourdomain.com/auth/oidc/google/callback/
In Firezone admin (Settings → Security):
OIDC Configuration:
Discovery Document URI: https://accounts.google.com/.well-known/openid-configuration
Client ID: your-google-client-id
Client Secret: your-google-client-secret
Redirect URI: https://vpn.yourdomain.com/auth/oidc/google/callback/
Response Type: code
Scope: openid email profile
Label: "Sign in with Google"
Okta
Discovery Document URI: https://your-org.okta.com/.well-known/openid-configuration
Client ID: your-okta-client-id
Client Secret: your-okta-client-secret
Redirect URI: https://vpn.yourdomain.com/auth/oidc/okta/callback/
After OIDC is configured, users click "Sign in with Google/Okta" on the Firezone portal rather than entering a separate password.
User Onboarding: Self-Service WireGuard Configs
One of Firezone's best features: users manage their own devices without admin involvement.
Admin step (one-time):
- Create user account in Firezone admin portal
- Set allowed IPs (what subnets this user can reach through the VPN)
- Send the user their Firezone portal URL
User flow (self-service):
1. User visits https://vpn.yourdomain.com
2. Clicks "Sign in with Google" (or your SSO provider)
3. Clicks "Add Device" → "Generate WireGuard Configuration"
4. Downloads or scans QR code with WireGuard client
WireGuard config (auto-generated):
[Interface]
PrivateKey = [user-private-key]
Address = 10.3.2.2/32
DNS = 1.1.1.1
[Peer]
PublicKey = [your-server-public-key]
AllowedIPs = 10.0.0.0/8 # Your internal network range
Endpoint = vpn.yourdomain.com:51820
PersistentKeepalive = 25
The user imports this config into the WireGuard app on their device — available for Windows, macOS, Linux, iOS, and Android. Tap the toggle to connect.
Access Policies: Split Tunneling
Configure which network ranges VPN users can reach:
Admin Portal → Rules → New Rule
Allow Rule — "Engineering: Full Internal Access"
Users/Groups: engineering-team
Destination: 10.0.0.0/8 (all internal networks)
Allow Rule — "Contractors: Specific Services Only"
Users/Groups: contractor-group
Destination: 10.0.1.50/32 (specific app server)
10.0.1.51/32 (specific database read replica)
Block Rule — "Default Deny"
Users/Groups: ALL
Destination: 0.0.0.0/0
Priority: lowest (evaluated last)
This zero-trust model means even authenticated VPN users only reach what they're explicitly allowed to access — not your entire internal network.
Production Hardening
Backups
#!/bin/bash
# backup-firezone.sh
DATE=$(date +%Y%m%d_%H%M)
BACKUP_DIR="/backups/firezone"
mkdir -p $BACKUP_DIR
# Database backup
docker exec firezone-db pg_dump -U firezone firezone \
| gzip > "$BACKUP_DIR/firezone_db_$DATE.sql.gz"
# Keys backup (critical — losing these orphans all client configs)
docker cp firezone:/var/firezone/ "$BACKUP_DIR/firezone_keys_$DATE/"
# Upload to S3
rclone copy $BACKUP_DIR/ s3remote:backups/firezone/
# Retain 30 days
find $BACKUP_DIR -mtime +30 -delete
Critical: The /var/firezone volume contains your WireGuard server private key. If you lose this without a backup, all existing client configurations become invalid and every user needs to re-generate their config.
Updates
cd ~/firezone
# Check release notes first: github.com/firezone/firezone/releases
docker compose pull
docker compose up -d
# Firezone runs migrations on startup
docker compose logs firezone | grep -i "migration\|error"
Monitoring VPN Connections
# Live WireGuard status
docker exec firezone watch -n5 wg show
# Connection log
docker compose logs firezone | grep -i "connected\|disconnected\|auth"
The Firezone admin portal also shows real-time connection status per user and device.
Firezone vs Tailscale vs Headscale
For completeness, there's a third option: Headscale, the open source implementation of the Tailscale coordination server. Here's how all three compare:
| Firezone | Tailscale | Headscale + Tailscale | |
|---|---|---|---|
| Network model | Hub-and-spoke (gateway) | Mesh (peer-to-peer) | Mesh (peer-to-peer) |
| Self-hostable control plane | ✅ | ❌ | ✅ |
| Client apps | WireGuard (native) | Tailscale client | Tailscale client |
| SSO/OIDC | ✅ | ✅ | ✅ (limited) |
| Admin web UI | ✅ Full-featured | ✅ (Tailscale.com) | Basic |
| Exit nodes | ✅ | ✅ | ✅ |
| ACL/policies | ✅ | ✅ | ✅ |
| Mobile clients | ✅ WireGuard apps | ✅ Tailscale app | ✅ Tailscale app |
| License | Apache 2.0 / EL 2.0 | Closed source | BSD 3-Clause |
| Cost (25 users) | ~$5/mo (VPS) | $150/mo | ~$5/mo (VPS) |
Choose Firezone if you need a traditional hub-and-spoke VPN gateway — all traffic routes through your server, which is better for compliance and egress control.
Choose Headscale + Tailscale client if you want mesh networking with fully self-hosted coordination — peer-to-peer connections with no central traffic routing.
Choose Tailscale if you want zero setup friction and don't mind your device coordination metadata going through Tailscale's servers.
Firezone vs Cloudflare Tunnel
Cloudflare Tunnel is often compared to Firezone, but they solve different problems:
| Firezone | Cloudflare Tunnel | |
|---|---|---|
| Protocol support | Any TCP/UDP | HTTP/S, SSH, RDP (via browser) |
| VPN access | ✅ Full network tunnel | ❌ Application-level only |
| Public exposure | ❌ (private gateway) | ✅ (Cloudflare's edge) |
| Cost | VPS infrastructure | Free–$20+/month |
| Use case | Internal network access | Exposing internal services publicly |
Cloudflare Tunnel is the right tool for exposing internal web services to the public internet without opening firewall ports. Firezone is the right tool for giving employees private network access to internal resources. They're complementary, not competing.
Methodology
- GitHub stats from github.com/firezone/firezone, March 2026
- WireGuard performance data from wireguard.com, March 2026
- Firezone documentation from docs.firezone.dev, March 2026
- Tailscale pricing from tailscale.com/pricing, March 2026
- Headscale from github.com/juanfont/headscale, March 2026
- License details from github.com/firezone/firezone/blob/main/LICENSE, March 2026
- Self-hosted community reference: DoctorFTB/firezone-1.x-self-hosted
Find more open source VPN and zero-trust access tools on OSSAlt — self-hosting guides, community ratings, and feature comparisons.
Related: Best Open Source Alternatives to HashiCorp Vault 2026 · Self-Hosting vs Cloud: The Real Cost Comparison 2026 · Keycloak vs Authentik 2026