Skip to main content

How to Self-Host Firezone: WireGuard VPN 2026

·OSSAlt Team
firezonewireguardvpnzero-trustself-hostingdockertailscale-alternativenetwork-security
Share:

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:

ConsiderationFirezone (Self-Hosted)Tailscale
Coordination serverYour serversTailscale'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 complexityMedium-HighLow
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 HTTPS
    • 443/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):

  1. Create user account in Firezone admin portal
  2. Set allowed IPs (what subnets this user can reach through the VPN)
  3. 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:

FirezoneTailscaleHeadscale + Tailscale
Network modelHub-and-spoke (gateway)Mesh (peer-to-peer)Mesh (peer-to-peer)
Self-hostable control plane
Client appsWireGuard (native)Tailscale clientTailscale client
SSO/OIDC✅ (limited)
Admin web UI✅ Full-featured✅ (Tailscale.com)Basic
Exit nodes
ACL/policies
Mobile clients✅ WireGuard apps✅ Tailscale app✅ Tailscale app
LicenseApache 2.0 / EL 2.0Closed sourceBSD 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:

FirezoneCloudflare Tunnel
Protocol supportAny TCP/UDPHTTP/S, SSH, RDP (via browser)
VPN access✅ Full network tunnel❌ Application-level only
Public exposure❌ (private gateway)✅ (Cloudflare's edge)
CostVPS infrastructureFree–$20+/month
Use caseInternal network accessExposing 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


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

The SaaS-to-Self-Hosted Migration Guide (Free PDF)

Step-by-step: infrastructure setup, data migration, backups, and security for 15+ common SaaS replacements. Used by 300+ developers.

Join 300+ self-hosters. Unsubscribe in one click.