Keycloak es un servidor de identidad de código abierto que centraliza el inicio de sesión de tus aplicaciones mediante OpenID Connect, OAuth 2.0 y SAML 2.0. Con un único punto de autenticación, los usuarios inician sesión una vez y acceden a todas las aplicaciones conectadas (Single Sign-On). En este tutorial desplegarás Keycloak 26 con Docker Compose y PostgreSQL en Ubuntu 24.04, lo publicarás con Nginx y un certificado de Let's Encrypt, y crearás un realm, un usuario y un cliente OpenID Connect listos para integrar una aplicación.

Requisitos previos

Para seguir esta guía necesitas:

  • Un servidor con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath, con al menos 2 GB de RAM (4 GB recomendados en producción).
  • Un usuario no root con privilegios sudo y UFW activo con SSH permitido.
  • Docker Engine y el plugin de Docker Compose instalados desde el repositorio oficial de Docker.
  • Un dominio con un registro A que apunte a la IP del servidor. En esta guía se usa sso.your_domain; sustitúyelo por el tuyo.
  • Nginx instalado (sudo apt install nginx).

Paso 1: Preparar las credenciales

Keycloak necesita dos secretos: la contraseña de la base de datos y la de un administrador inicial. Crea el directorio del proyecto y un archivo .env con contraseñas aleatorias:

sudo mkdir -p /opt/keycloak
sudo tee /opt/keycloak/.env > /dev/null <<EOF
POSTGRES_PASSWORD=$(openssl rand -hex 24)
KC_BOOTSTRAP_ADMIN_PASSWORD=$(openssl rand -hex 16)
EOF
sudo chmod 600 /opt/keycloak/.env

Consulta la contraseña del administrador inicial, porque la necesitarás en el paso 4:

sudo grep KC_BOOTSTRAP_ADMIN_PASSWORD /opt/keycloak/.env

Paso 2: Crear el archivo de Docker Compose

Crea el archivo:

sudo nano /opt/keycloak/docker-compose.yml

Pega esta configuración:

services:
  postgres:
    image: postgres:17
    restart: unless-stopped
    environment:
      POSTGRES_DB: keycloak
      POSTGRES_USER: keycloak
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    volumes:
      - postgres-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U keycloak -d keycloak"]
      interval: 10s
      timeout: 5s
      retries: 5

  keycloak:
    image: quay.io/keycloak/keycloak:26.7
    restart: unless-stopped
    command: start
    depends_on:
      postgres:
        condition: service_healthy
    environment:
      KC_DB: postgres
      KC_DB_URL: jdbc:postgresql://postgres:5432/keycloak
      KC_DB_USERNAME: keycloak
      KC_DB_PASSWORD: ${POSTGRES_PASSWORD}
      KC_HOSTNAME: https://sso.your_domain
      KC_PROXY_HEADERS: xforwarded
      KC_HTTP_ENABLED: "true"
      KC_HEALTH_ENABLED: "true"
      KC_BOOTSTRAP_ADMIN_USERNAME: admin
      KC_BOOTSTRAP_ADMIN_PASSWORD: ${KC_BOOTSTRAP_ADMIN_PASSWORD}
    ports:
      - "127.0.0.1:8080:8080"
      - "127.0.0.1:9000:9000"

volumes:
  postgres-data:

Qué hace cada ajuste importante:

  • command: start arranca Keycloak en modo producción. El modo start-dev usa una base de datos embebida y no debe usarse fuera de pruebas.
  • KC_HOSTNAME fija la URL pública. Keycloak la usa para generar los enlaces y las URL de redirección de OpenID Connect, así que debe coincidir exactamente con la que usan los navegadores.
  • KC_PROXY_HEADERS: xforwarded hace que Keycloak confíe en las cabeceras X-Forwarded-* que envía Nginx, y KC_HTTP_ENABLED permite HTTP entre Nginx y el contenedor, porque TLS termina en Nginx.
  • KC_HEALTH_ENABLED expone los endpoints de salud en el puerto de gestión 9000.
  • Los puertos se publican solo en 127.0.0.1. Docker se salta UFW, así que publicarlos en todas las interfaces dejaría Keycloak accesible sin HTTPS.

Paso 3: Arrancar Keycloak

Arranca los contenedores:

cd /opt/keycloak
sudo docker compose up -d

El primer arranque tarda un minuto o dos porque Keycloak crea el esquema en PostgreSQL. Sigue los logs:

sudo docker compose logs -f keycloak

Cuando esté listo verás una línea como esta:

keycloak-1  | ... INFO  [io.quarkus] (main) Keycloak 26.7.4 on JVM (powered by Quarkus 3.x) started in 18.214s. Listening on: http://0.0.0.0:8080. Management interface listening on http://0.0.0.0:9000.

Pulsa Ctrl+C y comprueba el endpoint de disponibilidad:

curl http://127.0.0.1:9000/health/ready
{
    "status": "UP",
    "checks": [
        {
            "name": "Keycloak database connections async health check",
            "status": "UP"
        }
    ]
}

Paso 4: Publicar Keycloak con Nginx y HTTPS

Crea el bloque de servidor:

sudo nano /etc/nginx/sites-available/keycloak

Keycloak devuelve cabeceras grandes en algunas respuestas (cookies y tokens), por lo que conviene ampliar los búferes del proxy:

server {
    listen 80;
    listen [::]:80;
    server_name sso.your_domain;

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-Host $host;
        proxy_set_header X-Forwarded-Port $server_port;
        proxy_buffer_size 128k;
        proxy_buffers 4 256k;
        proxy_busy_buffers_size 256k;
    }
}

No publiques el puerto 9000 a través de Nginx: los endpoints de gestión son solo para uso interno.

Activa el sitio, comprueba la sintaxis, recarga Nginx y abre los puertos web:

sudo ln -s /etc/nginx/sites-available/keycloak /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
sudo ufw allow 'Nginx Full'

Obtén el certificado con Certbot. El plugin de Nginx añade la configuración TLS y la redirección de HTTP a HTTPS:

sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d sso.your_domain

Comprueba que Keycloak responde por HTTPS y que anuncia URL con https:

curl -s https://sso.your_domain/realms/master/.well-known/openid-configuration | grep -o '"issuer":"[^"]*"'
"issuer":"https://sso.your_domain/realms/master"

Si el issuer aparece con http://, Keycloak no está recibiendo la cabecera X-Forwarded-Proto; revisa el bloque de Nginx.

Paso 5: Crear un administrador permanente

La cuenta admin creada con las variables KC_BOOTSTRAP_ADMIN_* es temporal, y la consola muestra un aviso mientras exista. Abre https://sso.your_domain/admin/ e inicia sesión con admin y la contraseña del paso 1.

En el realm master:

  1. Ve a Users > Add user, escribe un nombre de usuario propio (por ejemplo your_user) y pulsa Create.
  2. En la pestaña Credentials, pulsa Set password, introduce una contraseña robusta y desactiva Temporary.
  3. En la pestaña Role mapping, pulsa Assign role, filtra por roles de realm y asigna el rol admin.

Cierra sesión, entra con la cuenta nueva y, en Users, elimina el usuario admin temporal. Después borra la línea KC_BOOTSTRAP_ADMIN_PASSWORD de /opt/keycloak/.env y las dos variables KC_BOOTSTRAP_ADMIN_* de docker-compose.yml, y recrea el contenedor:

cd /opt/keycloak
sudo docker compose up -d

Paso 6: Crear un realm, un usuario y un cliente OpenID Connect

Un realm es un espacio aislado con sus propios usuarios, roles y aplicaciones. El realm master es solo para administrar Keycloak; tus aplicaciones deben usar un realm propio.

Aunque puedes hacerlo todo desde la consola, la herramienta kcadm.sh incluida en la imagen permite automatizarlo. Ejecuta todos los comandos de este paso desde /opt/keycloak. Autentícate con tu administrador permanente (pedirá la contraseña):

sudo docker compose exec keycloak /opt/keycloak/bin/kcadm.sh config credentials \
  --server http://localhost:8080 --realm master --user your_user

Crea el realm empresa:

sudo docker compose exec keycloak /opt/keycloak/bin/kcadm.sh create realms \
  -s realm=empresa -s enabled=true

Crea un usuario de prueba en ese realm y asígnale una contraseña (sustituye user_password):

sudo docker compose exec keycloak /opt/keycloak/bin/kcadm.sh create users -r empresa \
  -s username=jgarcia -s enabled=true -s [email protected] \
  -s firstName=Juan -s lastName=Garcia
sudo docker compose exec keycloak /opt/keycloak/bin/kcadm.sh set-password -r empresa \
  --username jgarcia --new-password user_password

Crea un cliente OpenID Connect confidencial para una aplicación web con backend. redirectUris debe contener las URL a las que Keycloak puede devolver al usuario tras el login; evita comodines amplios en producción:

sudo docker compose exec keycloak /opt/keycloak/bin/kcadm.sh create clients -r empresa \
  -s clientId=mi-aplicacion \
  -s protocol=openid-connect \
  -s publicClient=false \
  -s standardFlowEnabled=true \
  -s 'redirectUris=["https://app.your_domain/callback"]' \
  -s 'webOrigins=["https://app.your_domain"]' \
  -i

La opción -i imprime el identificador interno del cliente, algo como 8f1c2d3e-.... Úsalo para obtener el secreto que configurarás en la aplicación:

sudo docker compose exec keycloak /opt/keycloak/bin/kcadm.sh get clients/client_internal_id/client-secret -r empresa
{
  "type" : "secret",
  "value" : "Xy7...generated-secret"
}

Paso 7: Verificar el inicio de sesión

Comprueba que el realm publica su configuración de descubrimiento, que es lo que leen las librerías OpenID Connect de las aplicaciones:

curl -s https://sso.your_domain/realms/empresa/.well-known/openid-configuration | python3 -m json.tool | head -8
{
    "issuer": "https://sso.your_domain/realms/empresa",
    "authorization_endpoint": "https://sso.your_domain/realms/empresa/protocol/openid-connect/auth",
    "token_endpoint": "https://sso.your_domain/realms/empresa/protocol/openid-connect/token",
    ...

Para probar el login de un usuario sin tener todavía una aplicación, abre la consola de cuenta del realm en https://sso.your_domain/realms/empresa/account e inicia sesión con jgarcia. Si ves la página de perfil, la autenticación funciona.

Para integrar tu aplicación, configura su librería OpenID Connect con estos datos:

ParámetroValor
Issuer / discovery URLhttps://sso.your_domain/realms/empresa
Client IDmi-aplicacion
Client secretel obtenido en el paso 6
Redirect URIhttps://app.your_domain/callback
Scopesopenid profile email

Paso 8: Actualizar y hacer copias de seguridad

Todo el estado de Keycloak (realms, usuarios, clientes, sesiones persistentes) vive en PostgreSQL. Haz una copia de la base de datos antes de cada actualización:

cd /opt/keycloak
sudo docker compose exec -T postgres pg_dump -U keycloak keycloak | gzip | sudo tee /root/keycloak-$(date +%F).sql.gz > /dev/null

Para actualizar dentro de la rama 26.7, descarga la imagen y recrea el contenedor. Keycloak migra el esquema automáticamente al arrancar:

sudo docker compose pull
sudo docker compose up -d

Solución de problemas

Keycloak no arranca y los logs muestran errores de conexión a la base de datos. Comprueba que PostgreSQL está sano con sudo docker compose ps y que las variables POSTGRES_PASSWORD y KC_DB_PASSWORD coinciden. Si cambiaste la contraseña en .env después del primer arranque, PostgreSQL sigue usando la original, porque solo la aplica al crear el volumen.

El navegador muestra Invalid parameter: redirect_uri. La URL de redirección que envía la aplicación no coincide con ninguna de redirectUris del cliente. Compara ambas carácter a carácter, incluida la barra final.

La consola de administración se queda cargando o redirige a http://. KC_HOSTNAME no coincide con la URL pública o Nginx no envía X-Forwarded-Proto. Revisa ambos y recrea el contenedor con sudo docker compose up -d.

Error 502 Bad Gateway o upstream sent too big header en Nginx. El primero indica que el contenedor no está escuchando todavía (revisa los logs); el segundo, que faltan las directivas proxy_buffer_size y proxy_buffers del paso 4.

Conclusión

Ya tienes Keycloak 26 en producción con PostgreSQL, publicado con HTTPS detrás de Nginx, con un administrador permanente y un realm con un usuario y un cliente OpenID Connect listos para integrar. Como siguientes pasos, puedes activar la autenticación en dos pasos (OTP o WebAuthn) en el realm, federar usuarios desde un directorio LDAP existente en User federation y programar la copia de PostgreSQL con un temporizador de systemd.