by: Helge, published: Jun 19, 2023, updated: Apr 14, 2024, in

Firezone: WireGuard VPN With User Self-Service Portal & SSO

This article explains how to set up Firezone with automatic HTTPS certificates (via Caddy) and OpenID Connect single sign-on (via Authelia). This post is part of my series on home automation that shows how to install, configure, and run a home server with (dockerized or virtualized) services such as Home Assistant and ownCloud.

Why Firezone on Top of WireGuard?

Firezone addresses some of WireGuard’s shortcomings by adding features such as the following:

  • Web portal for admins and users.
  • SSO to the portal via OpenID Connect (OIDC).
  • User self-generation of device config files in the portal (docs).
  • Simplified client IP address management.

Network Setup

Dynamic DNS

You need a public DNS record that points to your machine’s IP address. Since home networks typically are behind a router whose IP address changes occasionally, you need to configure a dynamic DNS service. This yields a special DNS record that always points to your router’s current IP address.

As I’m using a Fritz!Box as router, the easiest dynamic DNS service to use for me is MyFritz. The setup is straightforward; I’ve described it here. Once configured, you’ll get a DNS address like

Friendly Public DNS Address

This step is not strictly necessary, but it hides the ugly, implementation-specific dynamic DNS address behind a friendly DNS address in any DNS domain of your choosing. Simply create a CNAME record like that points to

Port Forwarding

In your router, forward the following ports to your Docker host:

  • 443/tcp: HTTPS access to the portal.
  • 51820/udp: WireGuard VPN traffic.

Firezone Installation


I’m assuming that you’ve set up Docker, the Caddy container, and Authelia as described in the previous articles in this series. I’m also assuming that you’ve configured Authelia as OpenID Connect IdP as described in my ownCloud article and Unbound as described in my DNS server article.

Dockerized Firezone Directory Structure

This is what the directory structure will look like when we’re done:

 └── encrypted/
     └── docker/
         └── firezone/
             ├── data/
             ├── db/
             ├── container-vars.env
             ├── container-vars-postgres.env
             └── docker-compose.yml

We’re placing the configuration on the encrypted ZFS dataset (rpool/encrypted).

Create the directories:

$ mkdir -p /rpool/encrypted/docker/firezone/data
$ mkdir -p /rpool/encrypted/docker/firezone/db

Firezone Docker Compose File

Create docker-compose.yml with the following content (based on Firezone’s template):


    container_name: firezone
    hostname: firezone
    image: firezone/firezone:latest
    restart: unless-stopped
      backend:              # backend communications to DB
      caddy_caddynet:       # frontend communications (web UI)
      - 13000               # Web UI
      - "51820:51820/udp"   # WireGuard VPN
      - container-vars.env
      - /etc/localtime:/etc/localtime:ro
      - ./data:/var/firezone
      - NET_ADMIN           # perform various network-related operations
      - SYS_MODULE          # load and unload kernel modules
      # Needed for masquerading and NAT.
      - net.ipv6.conf.all.disable_ipv6=0
      - net.ipv4.ip_forward=1
      - net.ipv6.conf.all.forwarding=1
      - firezone-postgres

    container_name: firezone-postgres
    hostname: firezone-postgres
    image: postgres:15
    restart: unless-stopped
      test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}"]
      start_period: 20s
      interval: 30s
      retries: 5
      timeout: 5s
      - backend
      - container-vars-postgres.env
      - ./db:/var/lib/postgresql/data

    external: true
    driver: bridge

Generate a Default Environment File

Generate a default environment file default.env with various secrets (see manual install docs):

$ docker run --rm firezone/firezone bin/gen-env > default.env

We’ll use some of the values from default.env in our own configuration below.

Postgres Environment File

Edit container-vars-postgres.env so that it looks like the following:


Firezone Environment File

Edit container-vars.env so that it looks like the following (docs):

# Postgres
DATABASE_PASSWORD=YOUR_PASSWORD_HERE      # same password as in container-vars-postgres.env

# Firezone web server

# Firezone admin setup

# Secrets

# Firezone misc.

# Email
OUTBOUND_EMAIL_FROM="Your Name <[email protected]>"  # replace with your name/email

# WireGuard network settings copied from default.env.
# Firezone recommends NOT changing these.

Database & User Initialization

Migrate the database by running the following:

$ cd /rpool/encrypted/docker/firezone
$ docker compose run --rm firezone bin/migrate

Create the first user:

$ docker compose run --rm firezone bin/create-or-reset-admin

Delete the Default Environment File

We don’t need default.env anymore. Delete it:

$ rm default.env

Start the Firezone Container

Navigate into the directory with docker-compose.yml and run:

$ docker compose up -d

Inspect the container logs for errors with the command docker compose logs --tail 30 --timestamps.

Let’s Encrypt Certificate for Firezone via Caddy


Add the following to Caddyfile (details):

vpn.{$MY_DOMAIN} {
	reverse_proxy firezone:13000
	tls {
		dns cloudflare {env.CLOUDFLARE_API_TOKEN}

DNS A Record

Add the following A record to your internal DNS domain (docs):     # replace with your Docker host's IP address

Try to resolve the name on a machine in your network (e.g., nslookup

Reload Caddy’s Configuration

Instruct Caddy to reload its configuration by running:

docker exec -w /etc/caddy caddy caddy reload

You should now be able to access the Firezone web interface at without getting a certificate warning from your browser.

Initial Configuration


Log in to Firezone with the user credentials from container-vars.env (DEFAULT_ADMIN_EMAIL and DEFAULT_ADMIN_EMAIL).

Navigate to Defaults.

  • Allowed IPs: you have two options:
    • Specify to only route traffic to your home network through the VPN. Make sure the DNS server (see below) is covered by the netmask. This is what I’m using.
    • Leave at, ::/0 to route all traffic through the VPN.
  • DNS Servers: Enter your internal DNS server’s IPv4 address (see my DNS article).

Internal DNS Server (Unbound)

If you’re using Unbound as your internal DNS server as in my guide, make sure to configure interface-automatic: yes, or Unbound silently ignores (drops) DNS requests from VPN clients.

Docker IPv6

In my case, IPv6 is not required on the VPN network, but if you’d like it enabled, follow Firezone’s docs.

Firewall Rule for WireGuard

See this earlier article for information on how to configure Proxmox’s firewall. Add a rule to allow WireGuard traffic:

  • Direction: in
  • Action: ACCEPT
  • Protocol: udp
  • Destination port: 51820
  • Enable: checked

SSO to Firezone via OpenID Connect (OIDC) Authentication to Authelia

This section describes how to set up single sign-on to Firezone via OpenID Connect authentication to Authelia. It is based on the Authelia Firezone integration guide.

Authelia: Configure OpenID Connect IdP

Client ID

Generate a random alphanumeric string to be used as client ID:

$ tr -cd '[:alnum:]' < /dev/urandom | fold -w "64" | head -n 1

Copy the generated string for later use in Authelia’s config file.

Client Secret

The shared secret between Firezone and Authelia is entered as plaintext in the Firezone UI but as a hash of the plaintext in Authelia’s configuration. Create a new secret by running the following command (docs):

$ docker run authelia/authelia:latest authelia crypto hash generate pbkdf2 --random --random.length 32 --random.charset alphanumeric

The command’s relevant output looks as follows:

Random Password: v0e1zWJhvKQYud1lVUx4XhLibOwp0zyd
Digest: $pbkdf2-sha512$310000$vFbvgWgmhAIdZCbcLsrrXA$yRENW40rZpWLUP2ABQglEAhIHgpl7QAJ3eq8ZDEMmEHDL9Rro3eGwQ/4u05JsSLsEO5NIw.iAWVbo7EsiL8V1w

From the above output, the following two strings are required:

  • Plaintext secret: v0e1zWJhvKQYud1lVUx4XhLibOwp0zyd
  • Hashed secret: $pbkdf2-sha512$310000$vFbvgWgmhAIdZCbcLsrrXA$yRENW40rZpWLUP2ABQglEAhIHgpl7QAJ3eq8ZDEMmEHDL9Rro3eGwQ/4u05JsSLsEO5NIw.iAWVbo7EsiL8V1w

Note: do not use the above values. Create your own!

YAML Configuration File

Add the following to the oidc: section of Authelia’s configuration file config/configuration.yml (details):

      - client_id: lQ6vsAawaJYH53gSfiUSXKT5XxFYmVjt5o5iFSvOiIgz7wd3lyycNaQpXMo8VhgB  # Replace with your own random ID
        client_name: Firezone
        client_secret: '$pbkdf2-sha512$310000$vFbvgWgmhAIdZCbcLsrrXA$yRENW40rZpWLUP2ABQglEAhIHgpl7QAJ3eq8ZDEMmEHDL9Rro3eGwQ/4u05JsSLsEO5NIw.iAWVbo7EsiL8V1w'  # Replace with your own hashed secret
          -  # Replace with your own URL
          - openid
          - profile
          - email
        token_endpoint_auth_method: client_secret_post
        userinfo_signed_response_alg: none

Restart Authelia

We changed the container’s environment, which makes it necessary to recreate the container (stopping and starting is not enough). Navigate into the authelia directory and run:

$ docker compose down
$ docker compose up -d

Inspect the container logs for errors with the command docker compose logs --tail 30 --timestamps.

Firezone: Enable OIDC Authentication

In Firezone’s UI, navigate to Settings > Security and click Add OpenID Connect Provider. Set the following values:

  • Config ID: authelia
  • Label: Authelia
  • Scope: openid email profile
  • Client ID: the ID you generated above. In the example config: lQ6vsAawaJYH53gSfiUSXKT5XxFYmVjt5o5iFSvOiIgz7wd3lyycNaQpXMo8VhgB
  • Client secret: the plaintext secret you generated above. In the example config: v0e1zWJhvKQYud1lVUx4XhLibOwp0zyd
  • Discovery Document URI:
  • Redirect URI:
  • Auto-create users: disable unless you want every user defined in Authelia to automatically have access to Firezone, too.

Note: Make sure to replace the dummy URLs above with your own.

Save the settings.

Log In via OAuth

In a different browser, access Click Sign in with Authelia. It should work without a hitch. After you log in, inspect the Authelia container logs for errors with the command docker compose logs --tail 30 --timestamps.

Disable Local Authentication

Once you’re sure that OpenID Connect works as expected, disable local authentication by navigating to Settings > Security and disabling Local Auth.

Maintenance & Troubleshooting

Upgrading Postgres

Minor Upgrade

You can perform a minor upgrade by switching to a newer Docker image. However, after starting the new version you’ll see warnings like the following:

WARNING:  database "postgres" has a collation version mismatch
DETAIL:  The database was created using collation version 2.31, but the operating system provides version 2.36.
HINT:  Rebuild all objects in this database that use the default collation and run ALTER DATABASE postgres REFRESH COLLATION VERSION, or build PostgreSQL with the right library version.

To fix that, connect to the database container’s shell and execute some SQL commands:

$ docker exec -it firezone-postgres bash

$ psql -U postgres -d firezone
$ reindex database firezone;
$ alter database firezone refresh collation version;

$ psql -U postgres -d postgres
$ reindex database postgres;
$ alter database postgres refresh collation version;

$ psql -U postgres -d template1
$ reindex database template1;
$ alter database template1 refresh collation version;

Major Upgrade

Sadly, the process for performing major upgrades is unnecessarily convolued. It’ not sufficient to simply switch to a newer Docker image. I found these instructions that look promising (untested!).

Interactive Shell in the Firezone Container

Run the following to get an interactive shell in the Firezone container for troubleshooting:

$ docker exec -it firezone sh



  • Removed the version from docker-compose.yml; a warning mentions that it’s obsolete.


Updated Configuration for Authelia 4.38

The following Authelia settings need to be changed or updated:

  • Rename to oidc.clients.client_id.
  • Rename oidc.clients.description to oidc.clients.client_name.
  • Rename oidc.clients.secret to oidc.clients.client_secret.
  • Rename userinfo_signing_algorithm to userinfo_signed_response_alg.
  • Add token_endpoint_auth_method: client_secret_post.

Previous Article Unbound DNS Server Configuration & Static IPv6 Address on Proxmox
Next Article Grafana Setup Guide With Automatic HTTPS & OAuth SSO via Authelia