Files
frigate_w_audio/README.md
T
Claude ecbe1fc03d Initial Authelia + fail2ban stack
Self-hosted SSO portal with file-based users, SQLite storage, filesystem
notifier, and an iptables-banning fail2ban sidecar. Designed to drop into
a DotheEvo-style ~/docker layout next to a dockerized Caddy on the main
server, joining the same external caddy_net so Caddy reaches Authelia by
container name. fail2ban runs in host network mode with NET_ADMIN/NET_RAW
caps so its bans hit DOCKER-USER and actually drop packets at the edge.

Includes a Caddy snippet (caddy/snippet.example.caddyfile) to merge into
the user's real Caddyfile -- this repo doesn't manage Caddy itself.

https://claude.ai/code/session_013XZ1vmgk78k2PEQ5DmJhF3
2026-04-26 00:26:48 +00:00

13 KiB

Authelia + fail2ban

Self-hosted authentication portal (Authelia) with an IP-banning sidecar (fail2ban). Designed to sit next to a dockerized Caddy on the main server and gate every public subdomain (cam.example.com, doorbell.example.com, etc.) behind a single sign-on portal at auth.example.com.

   Internet
      |
      v
  +-------+      caddy_net (docker)        +--------------------+
  | Caddy |--- forward_auth -------------->|     Authelia       |
  +---+---+                                |  /api/authz/...    |
      |  reverse_proxy                     +--------+-----------+
      |                                             |
      v                                             v
  Frigate (IoT VLAN), ntfy, etc.             ./authelia/db.sqlite3
                                             ./authelia/authelia.log
                                                     ^
                                                     | tail
                                              +------+--------+
                                              |   fail2ban    | host net
                                              | DOCKER-USER   | +iptables
                                              +---------------+
  • One docker-compose file, two services, one external network (caddy_net).
  • File-backed users database, SQLite storage, in-memory sessions (no Redis).
  • Filesystem notifier -- password reset / new-device emails get written to a file you tail -f. Swap to SMTP later by editing one block.
  • fail2ban runs in host network mode and bans via the DOCKER-USER chain so drops happen at the host edge, before traffic reaches the Caddy container's published ports.
  • Caddy is not in this repo. You merge a snippet into your real Caddyfile (see caddy/snippet.example.caddyfile).

Repo layout

authelia/
|-- docker-compose.yml
|-- .env.example                    # copy to .env
|-- .gitignore
|-- README.md                       # this file
|
|-- authelia/
|   |-- configuration.yml           # main config -- edit your domain in here
|   |-- users_database.yml.example  # copy to users_database.yml (gitignored)
|   |-- secrets/                    # gitignored; secret files mounted as /secrets
|   `-- notifications/              # filesystem notifier writes here
|
|-- fail2ban/
|   `-- data/                       # mounted as /data in the container
|       |-- jail.d/
|       |   |-- authelia.local
|       |   `-- caddy.local
|       `-- filter.d/
|           |-- authelia.local
|           `-- caddy-4xx.local
|
`-- caddy/
    `-- snippet.example.caddyfile   # merge into YOUR Caddyfile

Prerequisites

  • Docker + docker compose v2 on the main server.
  • Caddy already running on the main server, in Docker, joined to an external network named caddy_net. If your network is named differently, change caddy_net everywhere in this repo.
  • A root domain you control (the examples use example.com). DNS records for auth.<root> and every protected subdomain should point at the Caddy host's public IP.
  • Caddy v2.5.1 or newer (for forward_auth directive).

First-run setup

# 0) From wherever you keep ~/docker stacks
cd ~/docker
git clone <this repo's url> authelia
cd authelia

# 1) External docker network -- Caddy must already be on this. If it isn't,
#    create it now and make sure your Caddy compose joins it.
docker network create caddy_net 2>/dev/null || true

# 2) Bootstrap the secrets directory
mkdir -p authelia/secrets
openssl rand -hex 32 > authelia/secrets/JWT_SECRET
openssl rand -hex 32 > authelia/secrets/SESSION_SECRET
openssl rand -hex 32 > authelia/secrets/STORAGE_ENCRYPTION_KEY
chmod 600 authelia/secrets/*

# 3) Copy and edit the .env
cp .env.example .env
$EDITOR .env                          # set TZ; pin AUTHELIA_VERSION if you want

# 4) Edit configuration.yml -- replace EVERY `example.com` with your real
#    root domain (look for the CHANGE comments)
$EDITOR authelia/configuration.yml

# 5) Create your first user
cp authelia/users_database.yml.example authelia/users_database.yml
$EDITOR authelia/users_database.yml   # set username, email, displayname

#    Generate the password hash:
docker compose run --rm authelia \
    authelia crypto hash generate argon2 --password 'your-real-password'

#    Paste the resulting `$argon2id$v=19$m=...` into the user's `password:`.

# 6) Validate the config before starting (catches typos / schema issues)
docker compose run --rm authelia \
    authelia validate-config --config /config/configuration.yml

# 7) Bring it up
docker compose up -d
docker compose logs -f authelia       # expect "Authelia is listening on ..."

Wire Caddy into Authelia

Open caddy/snippet.example.caddyfile. It defines:

  • (authelia) -- a reusable snippet: import authelia in any site block to gate it.
  • (accesslog) -- writes Caddy's JSON access log to /var/log/caddy/access.log so fail2ban can watch it.
  • auth.example.com -- the Authelia portal subdomain.
  • Example protected blocks for cam.example.com and doorbell.example.com.

Copy the relevant blocks into your real Caddyfile, replace example.com with your domain and 192.168.x.x with real upstream IPs, then reload Caddy:

docker compose -f ~/docker/caddy/docker-compose.yml exec caddy \
    caddy reload --config /etc/caddy/Caddyfile

For each protected domain, also add a rule under access_control.rules in authelia/configuration.yml (Authelia's default policy is deny -- a domain with no rule will not authenticate). Restart Authelia after editing:

docker compose restart authelia

Caddy access log path

fail2ban mounts /var/log/caddy from the host as read-only. Your Caddy compose needs to mount the same host path read-write so Caddy can write to it. In your Caddy compose:

services:
  caddy:
    volumes:
      - /var/log/caddy:/var/log/caddy

Make sure the host directory exists and is writable by Caddy's UID:

sudo mkdir -p /var/log/caddy
sudo chown -R 1000:1000 /var/log/caddy   # adjust UID to match your Caddy

First login + TOTP enrollment

  1. Visit any protected subdomain in a private browser window.
  2. Caddy bounces you to https://auth.<your-domain>/ -- log in with the username and plaintext password you set above.
  3. If the access rule is two_factor, Authelia asks you to register a second factor. Pick TOTP and scan the QR with Authy / 1Password / Google Authenticator / Bitwarden / etc.
  4. On first registration Authelia tries to email you a confirmation link. The filesystem notifier writes it to a file -- grab it with:
    docker compose exec authelia cat /config/notifications/notification.txt
    
    Click that link to confirm registration.
  5. Re-enter the TOTP code -- you're in.

A successful login sets the authelia_session cookie scoped to your root domain, so it covers every subdomain protected by the same Authelia.

User management

Add a user

Append to authelia/users_database.yml, generate a hash with docker compose run --rm authelia authelia crypto hash generate argon2 --password '...', paste it as password:, then docker compose restart authelia (or wait five minutes for the file refresh interval).

Change a password

Same as above -- re-generate the hash and replace the password: field.

Disable a user

Set disabled: true on their entry and restart Authelia.

Reset their TOTP

docker compose exec authelia \
    authelia storage user totp delete --username yourname \
    --config /config/configuration.yml

They will be prompted to re-enroll on next login.

fail2ban

Verify it's running and watching the right files

docker compose exec fail2ban fail2ban-client status
docker compose exec fail2ban fail2ban-client status authelia
docker compose exec fail2ban fail2ban-client status caddy-4xx

Each status <jail> shows the active failures, banned IPs, and the log file it's tailing.

Test a filter against your real logs

# Authelia
docker compose exec fail2ban fail2ban-regex \
    /var/log/authelia/authelia.log \
    /data/filter.d/authelia.local

# Caddy
docker compose exec fail2ban fail2ban-regex \
    /var/log/caddy/access.log \
    /data/filter.d/caddy-4xx.local

If the failregex doesn't match anything, your log format probably differs from what the filter expects. For Authelia: confirm log.format: 'text' in configuration.yml. For Caddy: confirm the (accesslog) snippet is imported into the site you're testing and that format json is set.

Manually unban an IP

docker compose exec fail2ban fail2ban-client set authelia unbanip 1.2.3.4
docker compose exec fail2ban fail2ban-client set caddy-4xx unbanip 1.2.3.4

Tune

Per-jail maxretry, findtime, bantime live in fail2ban/data/jail.d/*.local. Edit and restart fail2ban:

docker compose restart fail2ban

The caddy-4xx jail's defaults (30 fails / 2 minutes -> 30 minute ban) are intentionally loose -- a single failed request shouldn't ban you, but a scanner spraying /wp-admin, /.env, /admin.php etc. will hit it fast. The authelia jail is tighter (3 fails / 10 minutes -> 1 hour ban) on top of Authelia's own in-app regulation (3 fails / 2 minutes -> 5 minute account lockout), giving you defense in depth: Authelia locks the user, fail2ban bans the IP.

Day-to-day operation

docker compose ps                          # everything up?
docker compose logs -f authelia            # follow Authelia
docker compose logs -f fail2ban            # follow fail2ban
docker compose pull && docker compose up -d   # upgrade

Bump AUTHELIA_VERSION in .env when you upgrade Authelia. After any Authelia upgrade, re-run validate-config -- the schema does evolve.

Troubleshooting

Redirect loop between site and auth.<domain>

Cookie domain mismatch. The domain under session.cookies[] must be the root domain (e.g. example.com), and every protected site must be a subdomain of that root, served over HTTPS. Mixed http:// and https:// won't work; the cookie is Secure.

"Configuration: session: option 'domain' and option 'cookies' can't be specified at the same time"

Old-style session.domain: ... left over from pre-4.38 config. Remove it; this repo's configuration.yml already uses the new session.cookies[] form.

"access denied" with no login prompt

Default policy is deny. Add a rule under access_control.rules for the domain you're hitting and restart Authelia.

Authelia container restarts forever

docker compose logs authelia | head -50

Most often: missing/empty secret files, bad YAML in configuration.yml, or a users_database.yml with an invalid hash.

Caddy can't resolve authelia

Caddy isn't on caddy_net. Add networks: [caddy_net] to your Caddy service and caddy_net: external: true at the bottom of its compose, then docker compose up -d caddy.

fail2ban bans don't actually block

Almost always: fail2ban isn't writing to the right iptables chain. With dockerized Caddy, you need chain = DOCKER-USER (already set in the shipped jail files). Verify:

sudo iptables -L DOCKER-USER -n

You should see jump rules pointing at f2b-* chains.

Authelia logs show nothing

log.file_path is /config/authelia.log (i.e. ./authelia/authelia.log on the host). If the file isn't appearing, Authelia probably isn't writing logs because it failed to start -- check docker compose logs authelia.

Switching the notifier to SMTP

When you have a transactional sender (Mailgun, Postmark, Amazon SES, your own postfix), replace the notifier: block in authelia/configuration.yml:

notifier:
  disable_startup_check: false
  smtp:
    address: 'smtps://smtp.example.com:465'
    username: 'authelia@example.com'
    sender: 'Authelia <authelia@example.com>'
    subject: '[Authelia] {title}'
    # Password loaded via AUTHELIA_NOTIFIER_SMTP_PASSWORD_FILE

Add the password file:

echo 'your_smtp_password' > authelia/secrets/SMTP_PASSWORD
chmod 600 authelia/secrets/SMTP_PASSWORD

And add to docker-compose.yml under the authelia service environment::

- AUTHELIA_NOTIFIER_SMTP_PASSWORD_FILE=/secrets/SMTP_PASSWORD

Restart and verify with docker compose logs authelia -- expect a "Notifier SMTP startup check successful" line.

Security notes

  • .env, authelia/secrets/*, authelia/users_database.yml, authelia/db.sqlite3*, and the notifications file are all gitignored. Verify with git status before every commit.
  • Authelia is not port-mapped to the host. Only containers on caddy_net can reach it, and only Caddy is configured to forward unauthenticated traffic to it via forward_auth.
  • The TOTP secrets in the SQLite DB are encrypted at rest with STORAGE_ENCRYPTION_KEY. Lose that file and you lose every user's TOTP -- back it up alongside the DB.
  • regulation is per-user; fail2ban is per-IP. Both are on by default.
  • The shipped caddy-4xx filter ignores favicon.ico, robots.txt, and Apple touch icons so accidentally-missing static assets don't ban your own browser. Add to ignoreregex if other false-positives show up in fail2ban-regex testing.

What's next

Once this is steady-state:

  • Add OIDC clients in Authelia for apps that speak OIDC natively (Grafana, Gitea, etc.) -- they'll do real SSO without forward-auth headers.
  • Switch the filesystem notifier to SMTP (see above).
  • Consider a backup job for authelia/db.sqlite3 and authelia/secrets/ -- losing either is a recovery mess.