9 min read

Authelia: Centralized MFA for Self-Hosted Services

Stop exposing unsecured apps. Learn how to deploy Authelia in Docker with a KeyDB backend and SWAG to enforce centralized Multi-Factor Authentication.
Illustration of the Authelia logo beside a one-time password (OTP) login screen, representing centralized multi-factor authentication protecting self-hosted apps behind a SWAG reverse proxy.
Authelia adds centralized authentication and MFA to every application behind your SWAG reverse proxy, protecting your homelab with one secure login.

With CrowdSec and Fail2Ban in place from the last post, three of the Digital Castle's four layers are up: the moat (your firewall), the traps (Fail2Ban), and the watchman (CrowdSec). Today we add the fourth: the Gatekeeper.

Authelia sits between SWAG and the apps behind it, intercepting every request and demanding a second factor before anything gets through - even traffic that's already made it past your firewall, Fail2Ban, and CrowdSec. It can be a beast to configure the first time. It's worth it.

LEARNING PATH

Mastering Reverse Proxies with SWAG

Learn the concepts, deploy SWAG, secure your services, understand the request flow, and confidently migrate from other reverse proxy solutions.

Loading...

Difficulty Path
Beginner → Advanced

Authelia Install & Setup

Prerequisite Steps

This post assumes SWAG is running from Part 2. Part 3's CrowdSec & Fail2Ban setup isn't a hard requirement for Authelia to work, but it's strongly recommended - Authelia protects who gets through the door, Fail2Ban and CrowdSec handle who's even allowed to knock. ✅

Authelia provides a centralized solution for user authentication and authorization, simplifying user management and enhancing security for your Nginx applications.

📡
This can be a bit of a beast to setup for the first time, but is worth it in the end. Make sure your SWAG instance is running properly, and you have a handle on the basics like enabling a new subdomain served site. Example, you have yourdomain.com but you want to simply serve sonarr.yourdomain.com right? That's actually a subdomain site served via SWAG! This requires proper DNS setup for whomever you run DNS through as well.

How Authelia Performs Multi-Factor Authentication

Authelia sits between your reverse proxy (SWAG) and the apps behind it, intercepting requests and performing auth checks before forwarding traffic. This guide uses a simple flat user file rather than LDAP - add this below your SWAG and CrowdSec compose blocks.

👉 I chose to make a simple user file, but you might prefer to use LDAP or another method. Let me know if any of you want to see the LLDAP docker setup & integration with Authelia in the comments!

1. Authelia Docker Compose

# Authelia begin
# to add a user, add directly to `authelia/users_database.yml`
# then get the encrypted password with:
# docker run --rm ghcr.io/authelia/authelia:4.34.6 authelia hash-password yourpassword
  authelia:
    image: ghcr.io/authelia/authelia:latest
    container_name: authelia
    networks:
      backend:
      docker_vlan:
        ipv4_address: X.X.X.X   # only if you're on macvlan per Part 2's networking section
    environment:
      TZ: 'America/Toronto'
      PUID: '1000'
      PGID: '1000'
    volumes:
      - /yourpath/DOCKERS/authelia:/config
    restart: unless-stopped

  KeyDB-Authelia:
    image: eqalpha/keydb:latest
    container_name: keydb-authelia
    restart: unless-stopped
    environment:
      REDIS_ARGS: "--save 60 10"
    networks:
      - backend
    restart: unless-stopped

I use KeyDB instead of Redis because Redis moved to a non-open-source license - KeyDB is a performant, fully open-source (BSD) drop-in replacement.

If you're not hosting email, make a flat file as your user "database": nano /yourpath/authelia/users_database.yml

💥
I have 2 networks defined above, but you do not have to. 'backend' network is simply a default bridge network with NO external access. I have a bunch of "backend services" here like databases for other apps, for which these need no internet or other network access.

If you'd like to setup your backend network now, config snippet below.

Multi-network Compose Config

networks:
  your_vlan:
    driver: macvlan
    driver_opts:
      parent: enp37s0f1.7 # Your REAL physical interface
    ipam:
      config:
        - subnet: 10.0.0.0/24
          gateway: 10.0.0.1
  backend:
    name: backend
    driver: bridge

2. users_database.yml

# yamllint disable rule:line-length
---
###############################################################
#                         Users Database                      #
###############################################################

# This file can be used if you do not have an LDAP set up.

users:
  first_user:
    displayname: "myusername"
    password: "crazyargon2_hashed_pw" <--Change this!!!
    email: [email protected]
    groups:
      - admins
      - dev
  second_user:
    displayname: "My Buddy"
    password: "Hiscrazy_encrypted_pw"
    email: [email protected]
    groups: []

...
# yamllint enable rule:line-length

users: at the top has to stay; everything below it declares a user. The placeholder key (first_user) can be anything - a username, a first name, whatever's memorable to you.

How to Generate Hashed Argon2 Passwords 🤫

This is a hashed password you cannot simply just type in. To get the proper hashed password you must run a command, which temporarily runs an instance of authelia, generates the password after you enter it, you COPY THIS HASH and paste it into your already created users_database.yml. As here:

docker run --rm -it authelia/authelia:latest authelia crypto hash generate argon2

Type and confirm your password when prompted, copy the resulting $argon2id$v=... string, and paste it into users_database.yml's password: field.

3. Authelia Configuration - Read Carefully‼️

Authelia populates a default configuration.yml once it's first run: nano /yourpath/DOCKERS/authelia/configuration.yml

Note: If you have to make adjustments to the authelia configuration file (and you will!) it won't pickup the config changes until you re-create the entire container. Simply do it with this command docker compose up -d --force-recreate authelia.

---
###############################################################################
#                           Authelia Configuration                            #
###############################################################################
theme: dark

identity_validation:
  reset_password:
    jwt_secret: 'big_string' # change this to at least a 32-character string

server:
#  address: "tcp://:9091/authelia"
  address: 'tcp://0.0.0.0:9091/authelia' # do not change
  buffers:
    read: 4096
    write: 4096

log:
  level: info
  file_path: /config/logs/authelia.log
  keep_stdout: true

totp:
  issuer: authelia.yourdomain.ca # such as auth.yourdomain.com
  period: 30
  skew: 1

authentication_backend:
  password_reset:
    disable: true # change to true if you want to stop users from resetting their passwords
  refresh_interval: 30m # I found that the default of 5 is too short
  file:
    path: /config/users_database.yml # don't change this
    password:
      algorithm: argon2id
      iterations: 1
      key_length: 32
      salt_length: 16
      memory: 1024
      parallelism: 8

access_control:
  default_policy: deny
  rules:
    # 🌟 RULE 1: Local LAN Bypass (Must stay at the top!)
    # This bypasses MFA entirely if you are on your home network.
    - domain:
        - "yourdomain.ca"
        - "*.yourdomain.ca"
      networks:
        - 192.168.1.0/24  # Change to match your home subnet
        - 10.0.0.0/24     # Add any other internal networks/VLANs
      policy: bypass

    # 🔒 RULE 2: External Traffic (The Catch-All)
    # If the traffic didn't originate from the networks above, enforce full MFA.
    - domain:
        - "yourdomain.ca"
        - "*.yourdomain.ca"
      policy: two_factor

session:
  name: authelia_session # do not change
  secret: 'Make your own' # make this a long strong, 26 characters or so
  same_site: 'lax'
  expiration: '1h'
  inactivity: '5m'
  remember_me: '1M'
  cookies:
    - domain: 'yourdomain.ca' # change
      authelia_url: 'https://authelia.yourdomain.ca' # change
      default_redirection_url: 'https://yourdomain.ca'
      name: 'authelia_session'
      same_site: 'lax'
      inactivity: '5m'
      expiration: '1h'
      remember_me: '1d'
  redis:
    host: keydb-authelia # change to your host machine's IP if this doesn't work
    port: 6379 # if you use IP above, make sure this matches the mapped port
    # password: "" # do not change. if this doesn't work, remove the "" and try again
    # database_index: 0
    # maximum_active_connections: 10
    # minimum_idle_connections: 0

regulation:
  max_retries: 3
  find_time: 10m
  ban_time: 2h

storage:
  encryption_key: 'make your own!' # use a 20-character random string
  local:
    path: /config/db.sqlite3

notifier: # you can only use one of smtp or filesystem. uncomment/comment below as necessary to choose which
  filesystem:
    filename: /config/notification.txt
  disable_startup_check: false
  # smtp:
    # username: [email protected]
    # password: "YOUR_EMAIL_PASSWORD"
    # address: smtp.gmail.com:587 # change to match your smtp uri and port
    # sender: [email protected]
    # subject: "[Authelia] {title}"
    # startup_check_address: [email protected]
    # disable_require_tls: false
    # disable_html_emails: false
    # tls:
      # skip_verify: false
      # minimum_version: TLS1.2

This is a basic, bulletproof config - Authelia supports far more (LDAP, granular access rules, LAN bypass) if you want to go deeper.

The notifier section is where your one-time TOTP linking key shows up.

4. Google Authenticator with Authelia

Yes, you can (and I do) use Google Authenticator with my Authelia. So no, you don't necessarily HAVE to use yet another MFA app on your phone! Please read in detail all you need here: https://www.authelia.com/overview/authentication/one-time-password/

If you have authelia running, as above, and your users_database.yml, all GTG. Proceed!

Configuration Steps:

  • LOGIN to Authelia for the first time with your username and password you put in 3.2 the users_database.yml. Yes your normal password, not that crazy hashed password.
    • As soon as you login, you have completed the "First Factor" and you will see some options. ▶️Click One-Time Password method and then;
    • ➡️Register device. To register your google authenticator, go look at that file I pointed out a few minutes ago "notifier". nano /yourpath/authelia/config/notification.txt or cat /yourpath/authelia/config/notification.txt
    • It should spit out a link you can now click or copy and paste into your browser of choice which will then display a QR code. Exciting yes?
😀
You can now use Google Authenticator or whatever Authenticator you use, to scan the code and activate it on your device!

5. Enabling Authelia Protection with SWAG

  1. Create a DNS entry for the subdomain you want protected (e.g. sonarr.yourdomain.com).
  2. Edit /yourpath/swag/nginx/proxy-confs/sonarr.subdomain.conf and uncomment the two lines marked # enable for Authelia.
  3. Restart SWAG: docker compose up -d --force-recreate swag, then check logs: docker logs -f swag.
  4. Test from outside your LAN - you should hit the Authelia login first, then your OTP prompt, before ever reaching Sonarr.

Here's what Authelia looks like when you come knocking at an app's door, even one without ANY builtin authentication or security:


🏁 The Castle Gates Are Closed

With this, every layer of the Digital Castle is up: firewall, Fail2Ban, CrowdSec, and now Authelia. Before you consider this "done," though - one more thing worth doing immediately: back it all up. Deploy Dockhand for automated backups, and/or set up my simple backup script to intelligently back up your entire Docker infrastructure. A hardened castle you can't restore after a disk failure isn't much of a castle.

There's even moar (additional!) Cybersecurity and hardening guides you can follow:

The Digital Fortress: A Homelab Security Roadmap
Stop exposing port 32400 to the world. Here is how to build a defense-in-depth strategy for your self-hosted stack. 🛡️

What's Next

Every layer of defence is in place. Next up: How Cloudflare, DNS & SWAG Work Together - pulling back to see the full request path, from a browser typing your domain through Cloudflare, to SWAG, and finally to your app.

Coming from another reverse proxy? Once you've finished the path, check out Migrating to SWAG from NGINX Proxy Manager, Caddy & Traefik for a dedicated walkthrough.


Frequently Asked Questions❓

If I use Authelia to protect my services, do I still need a home VPN?

Yes. Authelia and a Virtual Private Network (VPN) serve entirely different roles in a defense-in-depth security model. Authelia provides zero-trust application-level security, allowing you to safely publish specific web apps (like Sonarr or Jellyfin) to the public internet using multi-factor authentication. A VPN gives an authorized device a secure network-level tunnel into your entire local LAN. For maximum security, use Authelia for your public-facing web applications, and reserve a self-hosted VPN (like WireGuard) for accessing sensitive backend infrastructure interfaces like OPNsense, Proxmox, or true storage layers.

Why does this deployment configuration use KeyDB instead of Redis?

Redis updated its core licensing structure to a restrictive, non-open-source model. KeyDB is a fully open-source, BSD-licensed, high-performance drop-in alternative. Because Authelia relies on a fast, in-memory key-value database to securely track user sessions, rate limits, and authentication states without wearing out your host storage drive with database writes, KeyDB provides the exact same performance advantages while remaining completely open-source.

Can I migrate this flat-file user configuration to an LDAP server later?

Yes. Authelia’s internal architecture separates the authentication backend from its core policy engine. You can safely deploy this guide using the simple local YAML user database file (users_database.yml) as a quick baseline setup. When your infrastructure scales to the point of needing an identity management framework like LLDAP or OpenLDAP, you can update the authentication_backend section inside your configuration.yml without modifying your application routing definitions or reverse proxy rules.

How does Authelia communicate back to the SWAG reverse proxy?

Authelia leverages NGINX’s native auth_request module. When an external request hits a protected domain on your SWAG reverse proxy, NGINX intercepts the request and passes the session headers to Authelia on port 9091. If Authelia finds a valid authentication session cookie, it returns a 200 OK response to SWAG, which then forwards the traffic to the backend application container. If no valid session is found, Authelia redirects the user to the centralized login portal.

How do I allow automated API traffic (like Radarr or Sonarr hooks) to bypass Authelia?

When you protect an application with Authelia, automated API requests from other self-hosted tools will be blocked by the login wall. To fix this, you must add specific bypass rules to the access_control section of your configuration.yml. By identifying the specific API URL paths (such as /api/*) or checking for explicit API headers, you can configure Authelia to assign a bypass policy to those specific requests while keeping the main user interface locked behind multi-factor authentication.

Can I disable Authelia authentication when I am connected to my home Wi-Fi?

Yes, and this is highly recommended for an optimal user experience. Inside the access_control block of your Authelia configuration.yml, you can define specific network rules. By setting a policy that maps your local LAN subnet (for example, 192.168.1.0/24) to a bypass or single_factor policy, Authelia will seamlessly grant access when you are home, while strictly enforcing full two_factor security the moment a request originates from an external cellular network or public internet connection.