Application Infrastructure Azure

Nginx Proxy Manager with Authelia SSO on Ubuntu 24.04 on Azure User Guide

| Product: Nginx Proxy Manager with Authelia SSO on Ubuntu 24.04 LTS on Azure

Overview

Nginx Proxy Manager is an open source web UI that lets you run nginx as a reverse proxy in front of your applications without editing an nginx configuration file by hand. Authelia is an open source authentication and two-factor single sign-on server designed to sit in front of proxied applications. Running one behind the other is the standard self-hosted SSO pattern, and Authelia's own documentation ships a dedicated Nginx Proxy Manager integration guide for it.

The work in that pattern is the forward authentication wiring: the auth_request snippets, the internal authorisation endpoint, the redirect back to the login portal, the TLS the session cookie depends on. This image ships that wiring already done and already proven. On first boot the VM creates two proxy hosts for you - one serving the Authelia login portal, one a protected demo application - and refuses to finish unless it can observe, on that specific VM, that an unauthenticated request is redirected to the portal and that an authenticated session passes through to the backend carrying the identity Authelia resolved.

What is included:

  • Nginx Proxy Manager 2.15.1 running as a container managed by Docker, pinned by image digest
  • Authelia 4.39.20 installed from the official Debian package, bound to the loopback interface only
  • Authelia pre-wired as Nginx Proxy Manager's forward authentication provider
  • Two proxy hosts created on first boot: the login portal, and a protected demo application
  • An interim TLS certificate whose private key is generated on your VM at first boot
  • Unique admin credentials for both components, generated on the first boot of every VM
  • Free Let's Encrypt certificate issuance and renewal from the Nginx Proxy Manager UI
  • A dedicated Azure data disk at /srv/npma holding both datastores and issued certificates
  • docker.service, authelia.service and the first-boot unit as enabled systemd units
  • 24/7 cloudimg support

Prerequisites

An active Azure subscription, an SSH key pair, and a VNet plus subnet in the target region. Standard_B2s (2 vCPU / 4 GiB RAM) is a reasonable starting point. Network security group inbound rules: allow 22/tcp from your management network, 80/tcp and 443/tcp for proxied traffic and Let's Encrypt challenges, and 81/tcp for the admin UI. For production, restrict 81/tcp to trusted source IPs so the admin console is not exposed to the whole internet.

You will also want a domain you control. Authelia is domain based - its session cookie is scoped to a domain, so browser sign-in only completes at a hostname under that domain. The image ships the placeholder domain example.com so everything works out of the box for inspection; Step 9 replaces it with yours.

Step 1 - Deploy from the Azure Marketplace

Sign in to the Azure Portal, choose Create a resource, search the Marketplace for Nginx Proxy Manager with Authelia by cloudimg, and select Create. On Basics pick your subscription, resource group, region and size; under Administrator account choose SSH public key and paste your key; under Inbound port rules allow SSH (22), HTTP (80), HTTPS (443) and 81. Review the dedicated data disk on the Disks tab, then Review + create and Create.

Step 2 - Deploy from the Azure CLI

az vm create \
  --resource-group <your-rg> \
  --name npm-authelia \
  --image <marketplace-image-urn> \
  --size Standard_B2s \
  --admin-username azureuser \
  --ssh-key-values ~/.ssh/id_ed25519.pub \
  --vnet-name <your-vnet> --subnet <your-subnet> \
  --public-ip-sku Standard

az vm open-port --resource-group <your-rg> --name npm-authelia --port 80  --priority 1010
az vm open-port --resource-group <your-rg> --name npm-authelia --port 443 --priority 1020
az vm open-port --resource-group <your-rg> --name npm-authelia --port 81  --priority 1030

Step 3 - Connect to your VM

ssh azureuser@<vm-public-ip>

First boot generates every secret and builds the whole integration, which takes about a minute. If the commands below report anything as inactive, wait a moment and retry.

Step 4 - Confirm both components are running

systemctl is-active docker.service authelia.service nginx-proxy-manager-authelia-firstboot.service

All three report active. Docker runs the Nginx Proxy Manager container, Authelia runs as a native service, and the first-boot unit is a completed one-shot.

sudo docker ps --format '{{.Names}}  {{.Image}}  {{.Status}}'
findmnt -no SOURCE,TARGET,FSTYPE,SIZE /srv/npma

The container runs the pinned jc21/nginx-proxy-manager:2.15.1 image, and your dedicated data disk is mounted at /srv/npma. Both datastores, the generated nginx configuration and all issued certificates live there, so they survive a resize or an OS disk swap.

docker.service, authelia.service and the first-boot service reporting active, the pinned Nginx Proxy Manager container running, and the dedicated data disk mounted at /srv/npma

Step 5 - Retrieve your credentials

Nginx Proxy Manager ships a well known default login (admin@example.com / changeme). This image never has one. A unique admin account is created for both components on the first boot of every VM and written to a root-only file:

sudo ls -l /root/nginx-proxy-manager-authelia-credentials.txt

The file is 0600 root:root. Read it to get your credentials:

sudo cat /root/nginx-proxy-manager-authelia-credentials.txt

It contains the Nginx Proxy Manager admin email and password, the Authelia admin username and password, and the three URLs the appliance serves. Store them in your password manager, then confirm the versions and the health endpoint:

authelia --version
sudo docker exec nginx-proxy-manager nginx -t
curl -s http://127.0.0.1:81/api/ | python3 -m json.tool

setup reports true, which is Nginx Proxy Manager's way of saying an administrator already exists - the unauthenticated setup wizard window never opens on your VM.

The Authelia and Docker versions, the Nginx Proxy Manager health endpoint reporting setup true, and the credentials file present with 0600 root-only permissions

Step 6 - Watch the forward authentication working

This is what the appliance exists to do. The commands below run against the two proxy hosts the image created for you. Because the shipped hostnames are placeholders, --resolve points them at the VM itself; after Step 9 you will use your own DNS instead.

An unauthenticated request to the protected host is redirected to the Authelia login portal:

curl -s -k -o /dev/null -D - -m 15 --resolve app.example.com:443:127.0.0.1 \
  https://app.example.com/ | grep -iE '^HTTP/|^location:'

You get a 302 and a location: header pointing at auth.example.com with an rd= parameter carrying the page you asked for. The protected content itself is never served:

if curl -s -k -m 15 --resolve app.example.com:443:127.0.0.1 https://app.example.com/ \
     | grep -q 'Protected demo application'; then
  echo 'LEAKED: protected content was served without authentication'; exit 1
else
  echo 'protected content withheld from the unauthenticated request'
fi

Nothing leaked. Now check that the portal rejects a wrong password:

curl -s -k -o /dev/null -w 'wrong password: HTTP %{http_code}\n' -m 15 \
  --resolve auth.example.com:443:127.0.0.1 \
  -X POST -H 'Content-Type: application/json' \
  -d '{"username":"admin","password":"definitely-not-the-password"}' \
  https://auth.example.com/api/firstfactor

HTTP 401. Finally, sign in with your real per-VM password and use the resulting session against the same protected URL:

AUTHELIA_PASSWORD=$(sudo grep -m1 '^AUTHELIA_ADMIN_PASSWORD=' /root/nginx-proxy-manager-authelia-credentials.txt | cut -d= -f2-)
JAR=$(mktemp)
curl -s -k -c "$JAR" -m 20 --resolve auth.example.com:443:127.0.0.1 \
  -X POST -H 'Content-Type: application/json' \
  -d "{\"username\":\"admin\",\"password\":\"$AUTHELIA_PASSWORD\"}" \
  https://auth.example.com/api/firstfactor
echo
SESSION=$(awk '/authelia_session/ {print $7}' "$JAR" | tail -1)
PAGE=$(curl -s -k -m 20 --resolve app.example.com:443:127.0.0.1 \
  -H "Cookie: authelia_session=$SESSION" https://app.example.com/)
rm -f "$JAR"
printf '%s' "$PAGE" | grep -oE 'Protected demo application|Signed in as: <code>[a-z]+' \
  || { echo 'the authenticated session did not reach the protected backend'; exit 1; }

The login returns {"status":"OK"}, and the protected page now renders - with Signed in as: admin. That last line is the whole point: Authelia resolved your identity and Nginx Proxy Manager passed it to the backend in a Remote-User header.

An unauthenticated request returning HTTP 302 to the Authelia portal, a wrong password rejected with HTTP 401, and an authenticated session reaching the protected demo application as admin

Step 7 - Sign in through the browser

Point a browser at the protected demo host. You will be redirected to the Authelia login portal instead of the application:

The Authelia single sign-on login portal, reached automatically after requesting the protected application

Sign in as admin with the AUTHELIA_ADMIN_PASSWORD from Step 5. Authelia sends you straight back to the page you originally asked for, and the protected application renders with your identity:

The protected demo application rendered after authentication, showing the signed-in user, groups and email that Authelia passed to the backend

Because the shipped hostnames are placeholders, add them to your workstation's hosts file pointing at your VM's public IP for this first look, or simply complete Step 9 first and use your own domain.

Step 8 - Open the Nginx Proxy Manager admin UI

Browse to http://<public-ip>:81/ and sign in with the NPM_ADMIN_EMAIL and NPM_ADMIN_PASSWORD from Step 5.

The Nginx Proxy Manager dashboard showing two proxy hosts and no redirection hosts, streams or 404 hosts

Open Hosts and then Proxy Hosts. Both hosts the image created are listed, online, and already bound to the per-VM certificate:

The Nginx Proxy Manager proxy host list showing the Authelia portal host and the protected application host, both online with a custom certificate

Select the protected host and open its Advanced tab to see the forward authentication wiring - an include of the Authelia authorisation endpoint and an include of the auth_request snippet. That block is what you copy onto your own hosts in Step 11.

Step 9 - Point the appliance at your own domain

Authelia scopes its session cookie to a domain, so this step is required before real use. Create DNS A records for auth.<your-domain> and your application hostnames pointing at your VM's public IP, then edit the Authelia configuration:

sudo nano /etc/authelia/configuration.yml

Replace every occurrence of the placeholder domain:

  • session.cookies[0].domain becomes <your-domain>
  • session.cookies[0].authelia_url becomes https://auth.<your-domain>
  • the access_control.rules domains become auth.<your-domain> and *.<your-domain>
  • totp.issuer becomes auth.<your-domain>

Check the file parses, then restart Authelia:

sudo authelia validate-config --config /etc/authelia/configuration.yml
sudo systemctl restart authelia

Then in the Nginx Proxy Manager admin UI, edit both proxy hosts and change their Domain Names to the matching hostnames under your domain.

Step 10 - Request a free Let's Encrypt certificate

The image ships an interim self-signed certificate whose private key was generated on your VM at first boot, so browsers will warn until you replace it. Once your DNS points at the VM and port 80 is reachable from the internet, open SSL Certificates, choose Add SSL Certificate and then Let's Encrypt, enter your hostnames and email, accept the terms, and save. Then edit each proxy host, open its SSL tab, select the new certificate, and enable Force SSL and HTTP/2. Nginx Proxy Manager renews it automatically.

Step 11 - Protect your own application

Add a proxy host for your application in the usual way - Hosts, Proxy Hosts, Add Proxy Host - pointing at your backend's scheme, hostname and port, and select your certificate on the SSL tab with Force SSL enabled. Then, on the Advanced tab, paste exactly this:

include /data/nginx/custom/authelia-location.conf;

location / {
    include /data/nginx/custom/proxy.conf;
    include /data/nginx/custom/authelia-authrequest.conf;
    proxy_pass $forward_scheme://$server:$port;
}

Save. That host is now behind Authelia: unauthenticated visitors are redirected to your login portal, and authenticated ones reach your backend with Remote-User, Remote-Groups, Remote-Email and Remote-Name headers set. Many applications can consume those headers directly for automatic sign-in.

Those three snippet files are already on your VM and are readable if you want to see exactly what they do:

ls -l /srv/npma/data/nginx/custom/

Access rules live in Authelia, not in Nginx Proxy Manager. Edit access_control.rules in /etc/authelia/configuration.yml to require two_factor for sensitive hosts, restrict a host to specific subject groups, or bypass a public path.

Step 12 - Add more users

Users live in /etc/authelia/users_database.yml. Generate an argon2id hash for the new user's password:

sudo authelia crypto hash generate argon2 --password '<new-password>' --no-confirm

Add an entry alongside admin using the Digest: value it prints, then restart Authelia. For larger deployments, point Authelia at an LDAP directory instead by replacing the authentication_backend.file section - cloudimg also publishes an Authelia image bundled with an LLDAP directory for exactly that.

Network ports

Port Protocol Purpose
22 tcp SSH management
80 tcp Proxied HTTP, redirect to HTTPS, Let's Encrypt http-01 challenge
443 tcp Proxied HTTPS: the Authelia login portal and every protected host
81 tcp Nginx Proxy Manager admin web UI - restrict to trusted source IPs

Authelia itself listens only on 127.0.0.1:9091 and is never reachable from outside the VM; Nginx Proxy Manager reaches it over the loopback interface. You can confirm that:

sudo ss -ltn | grep -E ':(80|443|81|9091|8099)\b' | awk '{print $1, $4}' | sort -t: -k2 -n

The listening sockets showing ports 80, 443 and 81 on the public interface while Authelia and the demo backend are bound to the loopback address only

Where things live

Path Contents
/srv/npma/data Nginx Proxy Manager SQLite database, generated nginx configs, the Authelia snippets
/srv/npma/letsencrypt Issued certificates
/srv/npma/authelia Authelia SQLite storage, notifications and log
/etc/authelia/configuration.yml Authelia configuration - domain, access rules, secrets
/etc/authelia/users_database.yml Authelia users and argon2id password hashes
/opt/cloudimg/npma/snippets Read-only master copies of the forward-auth snippets
/root/nginx-proxy-manager-authelia-credentials.txt Per-VM credentials, 0600 root:root

Troubleshooting

If the login portal is unreachable, check Authelia first:

sudo systemctl status authelia --no-pager | head -20
curl -s http://127.0.0.1:9091/api/health

Authelia refuses to start if authelia_url is not an https URL, so if you edited the configuration and it will not come back, that is the first thing to check. For the proxy layer:

sudo docker exec nginx-proxy-manager nginx -t
sudo docker logs --tail 40 nginx-proxy-manager

If a browser reaches the login portal but signing in loops back to it, the session cookie is being discarded - that means the hostname you are browsing is not under the session.cookies[0].domain you configured, or you are browsing over plain HTTP. Both are fixed in Steps 9 and 10.

To review what first boot did:

sudo journalctl -u nginx-proxy-manager-authelia-firstboot.service --no-pager | tail -30

Support

This image is published and supported by cloudimg with 24/7 support. Contact support@cloudimg.co.uk.