Authelia es un servidor de autenticación open source que se coloca delante de tus aplicaciones web y exige inicio de sesión, con segundo factor opcional (TOTP o WebAuthn), antes de dejar pasar cada petición. Trae su propio portal de login, una base de usuarios en archivo o LDAP y reglas de acceso por dominio, ruta, grupo o red. En este tutorial lo desplegarás con Docker Compose en Ubuntu 24.04, lo integrarás con Nginx mediante auth_request y protegerás una aplicación con usuario, contraseña y código TOTP.

Requisitos previos

  • Un servidor con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath, y un usuario no root con privilegios sudo.
  • Docker Engine y el plugin de Docker Compose instalados desde el repositorio oficial de Docker.
  • Nginx y Certbot instalados: sudo apt install nginx certbot python3-certbot-nginx, y los puertos 80 y 443 abiertos con sudo ufw allow 'Nginx Full'.
  • Un dominio propio con dos registros DNS A apuntando al servidor: auth.your_domain para el portal de Authelia y app.your_domain para la aplicación que vas a proteger.
  • Una aplicación escuchando en 127.0.0.1:3000. Para probar sirve python3 -m http.server 3000 --bind 127.0.0.1.
  • Una app de autenticación en el móvil (Aegis, Google Authenticator, 1Password o similar).

Paso 1: Preparar los directorios y los secretos

Authelia guarda su configuración en config/ y la base de datos SQLite (dispositivos 2FA, registros de autenticación) en data/. Crea la estructura en /opt/authelia:

sudo mkdir -p /opt/authelia/config /opt/authelia/data
cd /opt/authelia

Authelia necesita tres secretos aleatorios: uno para firmar los enlaces de restablecimiento de contraseña, otro para cifrar la sesión y otro para cifrar la base de datos. Genera tres valores distintos y guárdalos para el paso 3:

for name in JWT SESSION STORAGE; do echo "$name: $(openssl rand -hex 64)"; done
JWT: 3f9c1e...
SESSION: a81b0d...
STORAGE: 5e27f4...

Paso 2: Crear el archivo de Docker Compose

El despliegue tiene dos contenedores: Authelia y Redis, que guarda las sesiones para que no se pierdan al reiniciar Authelia. Crea el archivo:

sudo nano /opt/authelia/compose.yaml
services:
  authelia:
    image: authelia/authelia:4.39
    container_name: authelia
    restart: unless-stopped
    depends_on:
      - redis
    volumes:
      - ./config:/config
      - ./data:/data
    ports:
      - "127.0.0.1:9091:9091"
    environment:
      TZ: Europe/Madrid

  redis:
    image: redis:7-alpine
    container_name: authelia-redis
    restart: unless-stopped
    volumes:
      - redis-data:/data

volumes:
  redis-data:

El puerto se publica solo en 127.0.0.1. Docker añade sus propias reglas de iptables y se salta UFW, así que publicarlo en 0.0.0.0 dejaría Authelia accesible desde Internet sin pasar por Nginx. Redis no publica ningún puerto: Authelia lo alcanza por la red interna de Compose con el nombre redis.

Paso 3: Configurar Authelia

Crea el archivo de configuración principal:

sudo nano /opt/authelia/config/configuration.yml

Pega el siguiente contenido. Sustituye your_domain por tu dominio y los tres secretos por los valores que generaste en el paso 1:

server:
  address: 'tcp://:9091/'

log:
  level: 'info'

theme: 'auto'

identity_validation:
  reset_password:
    jwt_secret: 'your_jwt_secret'

totp:
  issuer: 'your_domain'

authentication_backend:
  file:
    path: '/config/users_database.yml'
    password:
      algorithm: 'argon2'

access_control:
  default_policy: 'deny'
  rules:
    - domain: 'app.your_domain'
      policy: 'two_factor'

session:
  secret: 'your_session_secret'
  expiration: '1h'
  inactivity: '15m'
  cookies:
    - domain: 'your_domain'
      authelia_url: 'https://auth.your_domain'
      default_redirection_url: 'https://app.your_domain'
  redis:
    host: 'redis'
    port: 6379

regulation:
  max_retries: 5
  find_time: '2m'
  ban_time: '10m'

storage:
  encryption_key: 'your_storage_secret'
  local:
    path: '/data/db.sqlite3'

notifier:
  filesystem:
    filename: '/data/notifications.txt'

Los puntos clave de esta configuración:

  • access_control deniega todo por defecto y exige dos factores en app.your_domain. El dominio del portal (auth.your_domain) no debe aparecer en las reglas.
  • session.cookies define para qué dominio se emite la cookie y dónde está el portal. Authelia usa authelia_url para construir la redirección al login.
  • regulation bloquea temporalmente a un usuario tras 5 intentos fallidos en 2 minutos.
  • notifier.filesystem escribe en un archivo los correos que Authelia enviaría (códigos de verificación, restablecimiento de contraseña). Sirve para empezar; en producción sustitúyelo por un bloque smtp.

El archivo contiene secretos, así que limita su lectura a root:

sudo chmod 600 /opt/authelia/config/configuration.yml

Paso 4: Crear el primer usuario

Authelia no guarda contraseñas en claro, sino un hash Argon2id. Genéralo con el propio binario de Authelia, sustituyendo your_strong_password por una contraseña real:

sudo docker run --rm authelia/authelia:4.39 authelia crypto hash generate argon2 --password 'your_strong_password'
Digest: $argon2id$v=19$m=65536,t=3,p=4$Zm9vYmFyYmF6$2cN8...

Crea la base de usuarios:

sudo nano /opt/authelia/config/users_database.yml

Pega el bloque siguiente y sustituye el valor de password por el digest completo que obtuviste (empieza por $argon2id$):

users:
  your_user:
    disabled: false
    displayname: 'Your Name'
    password: '$argon2id$v=19$m=65536,t=3,p=4$Zm9vYmFyYmF6$2cN8...'
    email: 'you@your_domain'
    groups:
      - 'admins'

Protege el archivo y arranca los contenedores:

sudo chmod 600 /opt/authelia/config/users_database.yml
sudo docker compose up -d

Comprueba que Authelia arrancó sin errores y responde en su endpoint de salud:

sudo docker compose logs authelia --tail 20
curl -s http://127.0.0.1:9091/api/health
{"status":"OK"}

Si el contenedor se reinicia en bucle, los registros indican la clave de configuración que falla. Los errores más habituales son un secreto vacío o una sangría incorrecta en YAML.

Paso 5: Publicar el portal de Authelia en Nginx

Crea un fragmento reutilizable con las cabeceras de proxy que Authelia necesita para saber el esquema, el host y la IP original:

sudo nano /etc/nginx/snippets/authelia-proxy.conf
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $http_host;
proxy_set_header X-Forwarded-URI $request_uri;
proxy_http_version 1.1;
proxy_set_header Connection "";

Crea el sitio del portal:

sudo nano /etc/nginx/sites-available/auth.your_domain
server {
    listen 80;
    server_name auth.your_domain;

    location / {
        include /etc/nginx/snippets/authelia-proxy.conf;
        proxy_pass http://127.0.0.1:9091;
    }
}

Paso 6: Proteger la aplicación con auth_request

Nginx preguntará a Authelia por cada petición a la aplicación a través del endpoint /api/authz/auth-request. Crea un fragmento con la ubicación interna que hace esa consulta:

sudo nano /etc/nginx/snippets/authelia-location.conf
location = /internal/authelia/authz {
    internal;
    proxy_pass http://127.0.0.1:9091/api/authz/auth-request;
    proxy_set_header X-Original-Method $request_method;
    proxy_set_header X-Original-URL $scheme://$http_host$request_uri;
    proxy_set_header X-Forwarded-For $remote_addr;
    proxy_set_header Content-Length "";
    proxy_set_header Connection "";
    proxy_pass_request_body off;
    proxy_http_version 1.1;
}

Y otro fragmento que aplica la comprobación dentro de cada location protegida:

sudo nano /etc/nginx/snippets/authelia-authrequest.conf
auth_request /internal/authelia/authz;

# Identidad del usuario, para pasarla a la aplicación
auth_request_set $user $upstream_http_remote_user;
auth_request_set $groups $upstream_http_remote_groups;
auth_request_set $name $upstream_http_remote_name;
auth_request_set $email $upstream_http_remote_email;
proxy_set_header Remote-User $user;
proxy_set_header Remote-Groups $groups;
proxy_set_header Remote-Name $name;
proxy_set_header Remote-Email $email;

# Sin sesión válida, Authelia devuelve la URL del portal en Location
auth_request_set $redirection_url $upstream_http_location;
error_page 401 =302 $redirection_url;

Crea el sitio de la aplicación:

sudo nano /etc/nginx/sites-available/app.your_domain
server {
    listen 80;
    server_name app.your_domain;

    include /etc/nginx/snippets/authelia-location.conf;

    location / {
        include /etc/nginx/snippets/authelia-authrequest.conf;
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Activa los dos sitios y valida la configuración:

sudo ln -s /etc/nginx/sites-available/auth.your_domain /etc/nginx/sites-enabled/
sudo ln -s /etc/nginx/sites-available/app.your_domain /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

Authelia solo emite cookies de sesión sobre HTTPS, así que obtén los certificados de Let's Encrypt para los dos subdominios. Certbot añade a cada bloque server la configuración TLS y la redirección desde HTTP:

sudo certbot --nginx -d auth.your_domain -d app.your_domain

Comprueba que una petición sin sesión se redirige al portal:

curl -sI https://app.your_domain/ | grep -iE '^(HTTP|location)'
HTTP/1.1 302 Moved Temporarily
Location: https://auth.your_domain/?rd=https%3A%2F%2Fapp.your_domain%2F&rm=GET

Paso 7: Iniciar sesión y registrar el segundo factor

Abre https://app.your_domain en el navegador. Authelia te lleva al portal:

  1. Inicia sesión con your_user y la contraseña del paso 4.
  2. Como la regla exige two_factor, Authelia te pide un método de segundo factor. Elige Contraseña de un solo uso (TOTP) y registra un nuevo dispositivo.
  3. Antes de mostrar el QR, Authelia verifica tu identidad enviando un código de un solo uso por correo. Con el notificador de archivo, el mensaje está en el servidor:
sudo cat /opt/authelia/data/notifications.txt
  1. Introduce ese código en el portal, escanea el QR con tu app de autenticación y confirma con el código de 6 dígitos que genera.
  2. Authelia te redirige a https://app.your_domain y ves la aplicación.

En los siguientes inicios de sesión bastará con la contraseña y el código TOTP. Desde el mismo portal puedes registrar también una llave de seguridad o passkey (WebAuthn) como método alternativo.

Paso 8: Afinar las reglas de acceso

Las reglas se evalúan en orden y se aplica la primera que coincide, así que coloca las más específicas arriba. Este ejemplo deja pública una ruta de salud, exige el grupo admins para /admin y permite el resto de la aplicación con un solo factor desde la red de la oficina:

access_control:
  default_policy: 'deny'
  networks:
    - name: 'oficina'
      networks:
        - '203.0.113.0/24'
  rules:
    - domain: 'app.your_domain'
      resources:
        - '^/health$'
      policy: 'bypass'

    - domain: 'app.your_domain'
      resources:
        - '^/admin([/?].*)?$'
      subject:
        - 'group:admins'
      policy: 'two_factor'

    - domain: 'app.your_domain'
      networks:
        - 'oficina'
      policy: 'one_factor'

    - domain: 'app.your_domain'
      policy: 'two_factor'

Sustituye el bloque access_control de configuration.yml y reinicia Authelia para aplicar los cambios:

cd /opt/authelia
sudo docker compose restart authelia
sudo docker compose logs authelia --tail 20

Solución de problemas

Los códigos TOTP siempre son incorrectos. Casi siempre es la hora del servidor o del móvil. Comprueba que el servidor está sincronizado:

timedatectl status

La línea System clock synchronized: yes debe aparecer. Si no, activa NTP con sudo timedatectl set-ntp true.

Tras iniciar sesión vuelves al portal en bucle. La cookie no se guarda. Revisa que session.cookies.domain es el dominio padre de app.your_domain y auth.your_domain, y que accedes por HTTPS.

Nginx devuelve 500 en lugar de redirigir. Authelia no está disponible o la ubicación interna no llega a 127.0.0.1:9091. Comprueba curl http://127.0.0.1:9091/api/health y sudo tail /var/log/nginx/error.log.

El usuario queda bloqueado. Es la protección de regulation. Espera el tiempo de ban_time o revisa los intentos en sudo docker compose logs authelia.

Conclusión

Has desplegado Authelia con Docker Compose, lo has publicado detrás de Nginx con TLS y has protegido una aplicación que ahora exige contraseña y código TOTP, con reglas de acceso por ruta, grupo y red. Como siguientes pasos, configura el notificador smtp para que los códigos lleguen por correo, añade más aplicaciones reutilizando los dos fragmentos de Nginx y, si ya tienes un directorio corporativo, cambia el backend de archivo por LDAP.