Traefik with Authelia SSO on Ubuntu 24.04 on Azure User Guide
Overview
Traefik with Authelia SSO is an edge appliance that terminates inbound HTTPS traffic, routes it to your backend services, and requires every visitor to sign in first. Traefik handles routing, load balancing and TLS. Authelia is the authentication and authorization server behind it, providing a login portal, brute-force protection, optional two-factor authentication and a single session shared across every service you put behind the proxy.
The important part is that these two halves are already wired together and proven. Traefik holds each request at the edge and asks Authelia whether the caller has a valid session before the backend is ever reached. Callers without one are redirected to the login portal; callers with one are proxied through, and their identity is passed upstream in request headers. That is single sign-on: one login, one session, every protected service.
What is included:
- Traefik 3.7.10 (a single static Go binary) at
/usr/local/bin/traefik - Authelia 4.39.20 from the official package, on loopback
127.0.0.1:9091, never exposed directly - The Authelia
forwardAuthmiddleware, already applied to the demo resource and to Traefik's own dashboard web(:80, permanently redirected to HTTPS),websecure(:443) and a loopbackping(127.0.0.1:8082) entrypoint- Local SQLite storage and in-memory sessions, so the appliance runs standalone with no database or Redis to deploy
- Brute-force protection on the portal (3 attempts, 2 minute window, 5 minute ban)
- Two dedicated Azure data disks, at
/var/lib/traefikand/var/lib/authelia, re-provisioned with every VM - File-provider dynamic configuration in
/etc/traefik/dynamic/for your own routers and services - A pre-configured Let's Encrypt certificate resolver (HTTP challenge)
- Unique per-VM secrets, admin password and TLS certificate generated on first boot, and a start-up guard that refuses to run on a placeholder or published credential
- 24/7 cloudimg support
How the single sign-on works
Every request carrying the authelia middleware flows through this loop:
browser --> Traefik :443 (Host: app.yourdomain.com)
|
| forwardAuth, BEFORE the backend is reached
v
Authelia 127.0.0.1:9091 /api/authz/forward-auth
|
+-- no valid session --> 302 to https://auth.yourdomain.com/?rd=<original URL>
| (the login portal; the visitor signs in)
|
+-- valid session --> 200 + Remote-User / Remote-Groups
/ Remote-Name / Remote-Email
--> Traefik proxies to your backend
Remove the middleware from a router and that route is public. Add it to a new router and that service is protected, with no changes to the service itself. That is the whole integration, and it is why a service that has no authentication of its own can be safely published through this appliance.
Single sign-on requires a domain name
This is the one thing to plan before you deploy. Authelia is domain-based: the session cookie it issues has to be scoped to a domain shared by the login portal and the services behind it. Authelia validates this and rejects a bare IP address, and a browser cannot store a cookie scoped to an IP literal either. There is no configuration that avoids this — it is how cookies work.
The image therefore ships with the reserved documentation domain example.com as an obvious placeholder, using two host names:
| Host | Serves |
|---|---|
auth.yourdomain.com |
the Authelia login portal |
app.yourdomain.com |
the protected demo resource and the Traefik dashboard |
Until you point DNS at the VM, browsing its public IP address shows an unauthenticated setup page that repeats these instructions. That is expected, and it is the only page reachable without signing in.
Prerequisites
An active Azure subscription, an SSH key pair, and a VNet and subnet in the target region. Standard_B2ms (2 vCPU / 8 GiB RAM) is a good starting point; Authelia's argon2id password hashing is deliberately memory-hard, so do not go below 4 GiB. NSG inbound: allow 22/tcp from your management network, and 80/tcp and 443/tcp for proxied traffic. Authelia on 9091 and the demo backend on 8090 are bound to loopback and must never be opened. You will also need a domain you control, so you can create the two DNS records above.
Step 1 - Deploy from the Azure Marketplace
In the Azure portal choose Create a resource, search for Traefik with Authelia SSO on Ubuntu 24.04 LTS by cloudimg, and select Create. Pick your subscription, resource group and region, choose the Standard_B2ms size, select SSH public key authentication with the username azureuser, and allow inbound 22, 80 and 443. Review and create.
Step 2 - Deploy from the Azure CLI
az vm create \
--resource-group my-resource-group \
--name traefik-authelia-01 \
--image cloudimg:traefik-authelia:default:latest \
--size Standard_B2ms \
--admin-username azureuser \
--generate-ssh-keys \
--public-ip-sku Standard
Then open the ports the proxy needs:
az vm open-port --resource-group my-resource-group --name traefik-authelia-01 --port 80 --priority 1010
az vm open-port --resource-group my-resource-group --name traefik-authelia-01 --port 443 --priority 1020
Step 3 - Point your DNS at the VM
Create two A records for the domain you control, both pointing at the VM's public IP address:
auth.yourdomain.com. A YOUR_VM_PUBLIC_IP
app.yourdomain.com. A YOUR_VM_PUBLIC_IP
Step 4 - Connect to your VM
ssh azureuser@YOUR_VM_PUBLIC_IP
Step 5 - Confirm the services are running
First boot generates this VM's secrets and starts both daemons. Confirm they are up:
systemctl is-active traefik authelia cloudimg-landing
All three report active. Check the versions:
/usr/local/bin/traefik version | head -3
authelia --version

Traefik's unauthenticated health endpoint is on loopback and answers immediately:
curl -sf -o /dev/null -w 'traefik ping: %{http_code}\n' http://127.0.0.1:8082/ping
Step 6 - Retrieve your per-VM credentials
Nothing in this image ships with a known password. The administrator account, the three Authelia secrets and the default TLS certificate are all generated on this VM's first boot:
sudo cat /root/traefik-authelia-credentials.txt

Record the AUTHELIA_ADMIN_PASSWORD value somewhere safe. It is unique to this VM and is not recoverable from the image.
Step 7 - Set your own domain
Two files carry the placeholder domain. In /etc/authelia/configuration.yml, set the session cookie domain and the portal URL:
session:
cookies:
- name: 'authelia_session'
domain: 'yourdomain.com'
authelia_url: 'https://auth.yourdomain.com/'
In /etc/traefik/dynamic/routers.yml, replace both host rules:
routers:
authelia-portal:
rule: "Host(`auth.yourdomain.com`)"
traefik-dashboard:
rule: "Host(`app.yourdomain.com`) && (PathPrefix(`/dashboard`) || PathPrefix(`/api`))"
protected-app:
rule: "Host(`app.yourdomain.com`)"
Then restart both services:
sudo systemctl restart authelia traefik
Authelia refuses to start on an invalid configuration rather than starting insecurely, so if it does not come back, read the reason with sudo journalctl -u authelia -n 30 and sudo tail /var/lib/authelia/authelia.log.
Step 8 - Watch the forward-auth loop
This is the product working. Request the protected resource without a session and Traefik hands you to the portal instead of to the backend:
curl -sS --resolve app.example.com:443:127.0.0.1 -k -o /dev/null \
-w 'unauthenticated: %{http_code} -> %{redirect_url}\n' https://app.example.com/
You get 302 and a redirect to the Authelia portal, carrying your original URL in ?rd=. Now try a deliberately wrong password — the portal refuses it:
curl -sS --resolve auth.example.com:443:127.0.0.1 -k \
-X POST -H 'Content-Type: application/json' \
-d '{"username":"admin","password":"not-the-password","keepMeLoggedIn":false}' \
-w '\nHTTP %{http_code}\n' https://auth.example.com/api/firstfactor
That returns {"status":"KO"} and HTTP 401. Sign in properly and keep the session cookie:
curl -sS --resolve auth.example.com:443:127.0.0.1 -k -c /tmp/authelia-jar \
-X POST -H 'Content-Type: application/json' \
-d '{"username":"admin","password":"<AUTHELIA_ADMIN_PASSWORD>","keepMeLoggedIn":false}' \
-w '\nHTTP %{http_code}\n' https://auth.example.com/api/firstfactor
That returns {"status":"OK"} and HTTP 200. Now request the exact same protected URL again, this time with the session:
curl -sS --resolve app.example.com:443:127.0.0.1 -k -b /tmp/authelia-jar \
-o /dev/null -w 'authenticated: %{http_code}\n' https://app.example.com/
200. Same URL, same appliance — the only difference is a valid Authelia session.

The --resolve flags above make the request use the shipped placeholder host names from the VM itself, which is why this works before your DNS has propagated. Once your own records are live, drop them and use your real host names from a browser.
Step 9 - Sign in from a browser
Browse https://app.yourdomain.com/. You are redirected to the Authelia portal:

Sign in as admin with the password from Step 6. Authelia establishes the session and sends you back to the URL you originally asked for:

The default certificate is self-signed and unique to this VM, so your browser warns on the first visit until you switch to Let's Encrypt below.
Step 10 - Open the Traefik dashboard
Traefik's dashboard is not published unauthenticated — api.insecure is false, and the dashboard router carries the same authelia middleware as everything else. Because you now hold a session, it opens at https://app.yourdomain.com/dashboard/:

Open HTTP Middlewares to confirm the integration is live. authelia@file is listed with type forwardauth and a green status, which means Traefik loaded it and is consulting Authelia on every protected request:

You can confirm the same thing from the shell:
sudo grep -A3 'forwardAuth' /etc/traefik/dynamic/authelia.yml
Step 11 - Protect your own service
Add a router for your service and give it the authelia middleware. Nothing else is required — your service does not need to know about Authelia at all:
# /etc/traefik/dynamic/my-app.yml
http:
routers:
my-app:
rule: "Host(`intranet.yourdomain.com`)"
entryPoints: [websecure]
service: "my-app"
middlewares: [authelia]
tls: {}
services:
my-app:
loadBalancer:
servers:
- url: "http://127.0.0.1:3000"
Traefik watches /etc/traefik/dynamic/ and picks the file up with no restart. Add an A record for the new host name and point the session cookie domain at a parent of it, and the same single login now covers both services.
Your application can read the signed-in user from the headers Authelia adds: Remote-User, Remote-Groups, Remote-Name and Remote-Email. Many applications can consume these directly as trusted-header authentication, which removes their own login screen entirely.
Step 12 - Access control rules
The shipped policy requires a successful login for everything. To vary that per host, path or group, add rules to /etc/authelia/configuration.yml — the first matching rule wins:
access_control:
default_policy: 'deny'
rules:
- domain: 'public.yourdomain.com'
policy: 'bypass'
- domain: 'app.yourdomain.com'
policy: 'one_factor'
- domain: 'admin.yourdomain.com'
policy: 'two_factor'
subject: ['group:admins']
Changing default_policy to deny is the safer posture once your rules are written, because a host you forget to list is then refused rather than merely asked to log in.
Step 13 - Add two-factor authentication
Authelia supports time-based one-time passwords out of the box. Set a rule's policy to two_factor, restart Authelia, then sign in and follow the registration prompt. The image uses the filesystem notifier, so the registration link is written to a file rather than emailed:
sudo test -f /var/lib/authelia/notification.txt && echo "notifier file present" || echo "no notifications yet"
For production, switch notifier to SMTP so password resets and 2FA registrations reach users by email.
Managing users
Users live in /etc/authelia/users_database.yml. Generate a hash and add an entry — never write a plaintext password, and the start-up guard will refuse to start the appliance if you do:
sudo authelia crypto hash generate argon2 --password 'a-strong-password' --no-confirm
Add the resulting digest as the user's password value:
users:
alice:
disabled: false
displayname: 'Alice Smith'
password: 'PASTE_THE_DIGEST_HERE'
email: 'alice@yourdomain.com'
groups:
- admins
Authelia reloads the file automatically, so no restart is needed to add a user.
The start-up security guard
Both services run traefik-authelia-guard.sh before they start, as a deliberate fail-closed gate. It refuses to start the appliance if first boot has not completed, if any of the three Authelia secrets is still a placeholder or one of Authelia's published example values, if the users database is the build-time one or contains Authelia's published demo account, if a password is not an argon2id hash, if the TLS key is missing or world-readable, if api.insecure has been turned on, or if the forward-auth middleware has been removed. You can run it yourself at any time:
sudo /usr/local/sbin/traefik-authelia-guard.sh

If it ever refuses, it prints the exact reason, and the appliance stays down rather than serving traffic in an insecure state.
Enabling Let's Encrypt certificates
The certificate resolver is pre-configured and needs only your email address. Set it in /etc/traefik/traefik.yml:
certificatesResolvers:
letsencrypt:
acme:
email: 'you@yourdomain.com'
storage: '/var/lib/traefik/acme.json'
httpChallenge:
entryPoint: web
Then add the resolver to each router's TLS block in /etc/traefik/dynamic/routers.yml:
tls:
certResolver: letsencrypt
Both host names must resolve publicly to this VM and port 80 must be reachable, because the HTTP challenge is served there. Certificates are stored on the /var/lib/traefik data disk, so they survive a resize or a reboot.
Getting the real client IP behind a load balancer
If you front this VM with an Azure Load Balancer, Application Gateway or Front Door, add its ranges to forwardedHeaders.trustedIPs for both entrypoints in /etc/traefik/traefik.yml. Without that, Authelia's brute-force protection sees your load balancer's address instead of the real visitor and its rate limiting becomes ineffective.
Maintenance
Both data disks are separate from the OS disk, so the SQLite database, certificates and logs survive a VM resize. Back up /var/lib/authelia/db.sqlite3, /etc/authelia/users_database.yml and /var/lib/traefik/acme.json. Logs rotate daily and are kept for 14 days in /var/lib/traefik/ and /var/lib/authelia/. Unattended security upgrades are enabled, as on the stock Ubuntu image.
To review what the appliance is doing:
sudo journalctl -u authelia -n 20 --no-pager
Support
This image is published and supported by cloudimg. For help, contact support@cloudimg.co.uk. Traefik is distributed under the MIT licence and Authelia under the Apache 2.0 licence; cloudimg is not affiliated with either project.