Tutoriales de VPS
Instalar Keycloak SSO en un VPS (2026): Docker, PostgreSQL, Caddy HTTPS, OIDC y copias de seguridad
Despliega Keycloak 26.7.2, PostgreSQL 17 y Caddy HTTPS en un VPS Ubuntu con Docker Compose, configura realms, OIDC, MFA, roles, copias de seguridad y actualizaciones seguras, y crea un punto de acceso de inicio de sesión único autoalojado para tu equipo.

Muchos equipos ya tienen GitLab, Grafana, Jenkins, paneles de administración internos y aplicaciones de IA autoalojadas, pero siguen creando cuentas por separado en cada sistema. Cuando un empleado se va, hay que deshabilitar cada cuenta una por una, las políticas de contraseñas difieren en todas partes y la auditoría unificada resulta difícil. El valor de Keycloak no es añadir otra página de inicio de sesión, sino centralizar cuentas, inicio de sesión único, MFA, roles y políticas de sesión para que las aplicaciones compatibles con OpenID Connect (OIDC) o SAML compartan un único punto de entrada de identidad.
Este artículo usa Keycloak 26.7.2, PostgreSQL 17, Docker Compose, Caddy y Ubuntu 24.04 como base para construir en un solo VPS un servicio SSO apto para entornos de producción pequeños, y demuestra por completo HTTPS con dominio, gestión del administrador temporal, realms, clientes OIDC, URI de redirección, roles, copias de seguridad, actualizaciones y resolución de problemas. La versión de Keycloak y la matriz de soporte oficial se verificaron a septiembre de 2026; antes de desplegar realmente, conviene revisar los avisos de seguridad y las notas de versión más recientes.
Keycloak es un sistema de gestión de identidades y accesos, no un gestor de contraseñas común. Puede ofrecer inicio de sesión unificado a varias aplicaciones y gestionar de forma centralizada:
- autenticación OpenID Connect, OAuth 2.0 y SAML 2.0;
- usuarios, grupos, roles de realm, roles de cliente y gestión de atributos;
- TOTP, WebAuthn, políticas de contraseñas, detección de fuerza bruta y control de sesiones;
- integración con fuentes de identidad externas como LDAP, Active Directory, GitHub y Google;
- autoservicio de perfil, restablecimiento de contraseña y gestión del segundo factor por parte del usuario;
- eventos de administración, eventos de inicio de sesión y permisos de administrador granulares.
Si solo quieres añadir una barrera de inicio de sesión a una página web que no admite OIDC, Authelia o Basic Auth en el proxy inverso suele ser más ligero. Si la propia aplicación admite OIDC y necesitas un ciclo de vida de usuario unificado, permisos organizativos o integrar más sistemas en el futuro, Keycloak ofrece más margen de crecimiento.
| Opción | Ventajas | Costes principales | Más adecuada para |
|---|---|---|---|
| Keycloak | OIDC/SAML completos, realms, roles, fuentes de identidad y políticas ricos | Muchas opciones de configuración, el servicio Java necesita límites de memoria explícitos | Varias aplicaciones, cuentas de equipo, integración de identidad empresarial |
| Authentik | Interfaz de administración amigable, capacidades completas de proxy y directorio | También tiene muchos componentes y conceptos | Equipos que quieren orquestar el inicio de sesión con flujos visuales |
| Authelia | Ligero, adecuado para proteger páginas web detrás de un proxy inverso | No es una plataforma de identidad empresarial completa | Unas pocas páginas internas que necesitan autenticación previa unificada |
| Cada aplicación gestiona sus cuentas | Arranque más rápido | Bajas, MFA, auditoría y políticas de contraseñas dispersas | Una sola aplicación con muy pocos usuarios |
Este artículo usa una arquitectura de nodo único:
浏览器 / 应用
|
| HTTPS :443
v
Caddy
|
| HTTP 127.0.0.1:8080
v
Keycloak 26.7.2
|
| Docker 内部网络 :5432
v
PostgreSQL 17
健康检查:仅本机访问 127.0.0.1:9000
Caddy termina TLS en el VPS y Keycloak solo sirve HTTP por la dirección de loopback. PostgreSQL no mapea ningún puerto del host. El 9000 de la interfaz de administración de Keycloak se enlaza solo a 127.0.0.1, no se coloca detrás de Caddy y no se expone a Internet.
El contenedor oficial de Keycloak calcula el heap de la JVM según el límite de memoria del contenedor: el heap máximo es por defecto aproximadamente el 70 % del límite, más la sobrecarga de memoria no heap. La recomendación oficial para despliegues de producción pequeños es un límite de memoria de contenedor de 2 GB. Teniendo en cuenta PostgreSQL, Caddy, la caché del sistema y las tareas de copia de seguridad, empieza con las siguientes configuraciones:
| Escala de uso | Punto de partida sugerido del VPS | Notas |
|---|---|---|
| Pruebas, menos de 20 usuarios | 2 vCPU, 4 GB de RAM, 40 GB NVMe | No es una garantía de disponibilidad, no se recomienda para inicios de sesión críticos |
| Equipo pequeño, cientos de usuarios, baja concurrencia | 4 vCPU, 8 GB de RAM, 80 GB NVMe | Keycloak limitado a 2 GB, dejando margen amplio para base de datos y sistema |
| Picos de inicio de sesión evidentes o muchas fuentes de identidad | Prueba de carga primero, luego escala o pasa a varios nodos | El hash de contraseñas, los directorios externos y los plugins personalizados afectan la capacidad |
Un solo VPS sigue siendo un punto único de fallo. Mientras todas las aplicaciones dependan de él para iniciar sesión, la indisponibilidad de Keycloak afectará a las sesiones nuevas. La actividad crítica debería usar un PostgreSQL externo, al menos dos nodos de Keycloak, balanceo de carga y copias de seguridad en otra máquina; no confundas la configuración de nodo único de este artículo con una arquitectura de alta disponibilidad.
Usa un dominio dedicado para SSO, por ejemplo sso.example.com. Añade un registro A en tu proveedor de DNS apuntando a la dirección pública del VPS; añade un registro AAAA solo si el VPS realmente tiene IPv6 funcional configurado.
Se necesitan muy pocos puertos:
| Puerto | Origen | Uso | Público |
|---|---|---|---|
22/TCP | IP de administración | SSH | Mejor restringido por origen |
80/TCP | Público | Validación de certificados de Caddy y redirección HTTP | Sí |
443/TCP | Público o red especificada | Inicio de sesión de Keycloak y endpoints OIDC/SAML | Sí |
8080/TCP | Host de Caddy | HTTP de Keycloak | No, solo loopback |
9000/TCP | Monitorización local | Comprobaciones de estado y métricas | No, solo loopback |
5432/TCP | Red interna de Docker | PostgreSQL | No, no mapeado |
Si el grupo de seguridad de la nube ya permite el tráfico pero el acceso sigue fallando, revisa capa por capa siguiendo la guía de resolución de problemas de puertos, firewall, grupos de seguridad y direcciones de escucha; no desactives simplemente el firewall para verificar.
Los siguientes comandos usan Ubuntu 24.04 como ejemplo. Docker debe instalarse desde el repositorio oficial; si el VPS ya ejecuta Docker, no vuelvas a ejecutar scripts de terceros que sobrescribirían la configuración del repositorio.
sudo apt update
sudo apt full-upgrade -y
sudo apt install -y ca-certificates curl openssl caddy jq
docker --version
docker compose version
sudo systemctl enable --now caddy
Primero confirma que docker compose es Compose v2. Los entornos de producción también deberían configurar rotación de logs, límites de recursos, comprobaciones de estado y un proceso de reversión; puedes seguir junto con esto la lista de verificación de configuración de producción de Docker Compose.
Crea un directorio accesible solo para administradores:
sudo install -d -m 750 -o "$USER" -g "$USER" /opt/keycloak
cd /opt/keycloak
umask 077
Genera la contraseña de la base de datos y la contraseña del administrador temporal. Aquí se usan caracteres hexadecimales para evitar que caracteres como $ sean expandidos de nuevo por Compose como variables:
POSTGRES_PASSWORD_VALUE="$(openssl rand -hex 32)"
BOOTSTRAP_PASSWORD_VALUE="$(openssl rand -hex 32)"
cat > .env <<EOF
KC_HOSTNAME=sso.example.com
POSTGRES_DB=keycloak
POSTGRES_USER=keycloak
POSTGRES_PASSWORD=${POSTGRES_PASSWORD_VALUE}
KC_BOOTSTRAP_ADMIN_USERNAME=temp-admin
KC_BOOTSTRAP_ADMIN_PASSWORD=${BOOTSTRAP_PASSWORD_VALUE}
EOF
chmod 600 .env
unset POSTGRES_PASSWORD_VALUE BOOTSTRAP_PASSWORD_VALUE
Sustituye sso.example.com por tu dominio real. No hagas commit de .env en Git, ni lo pegues en chats, capturas de pantalla o tickets. Keycloak también admite oficialmente almacenar configuración sensible en un Java KeyStore; este artículo usa un archivo de entorno con permisos restringidos para que los pasos de despliegue en una sola máquina sean más fáciles de reproducir.
Al confirmar los permisos de archivo y el resultado de la expansión de Compose, no muestres contraseñas reales directamente:
stat -c '%a %U:%G %n' .env
grep -E '^(KC_HOSTNAME|POSTGRES_DB|POSTGRES_USER|KC_BOOTSTRAP_ADMIN_USERNAME)=' .env
Keycloak recomienda oficialmente ejecutar primero build y luego iniciar con start --optimized. Así, opciones de tiempo de compilación como los controladores de base de datos, las comprobaciones de estado y las métricas se escriben de antemano en la imagen, y el arranque no necesita repetir la detección y la compilación.
Crea /opt/keycloak/Containerfile:
FROM quay.io/keycloak/keycloak:26.7.2 AS builder
ENV KC_DB=postgres
ENV KC_HEALTH_ENABLED=true
ENV KC_METRICS_ENABLED=true
RUN /opt/keycloak/bin/kc.sh build
FROM quay.io/keycloak/keycloak:26.7.2
COPY --from=builder /opt/keycloak/ /opt/keycloak/
ENTRYPOINT ["/opt/keycloak/bin/kc.sh"]
No uses latest en producción. Este artículo fija Keycloak 26.7.2; al actualizar, cambia ambos FROM, reconstruye y completa la copia de seguridad de la base de datos y las pruebas de reversión.
Crea /opt/keycloak/compose.yaml:
services:
postgres:
image: postgres:17-alpine
restart: unless-stopped
environment:
POSTGRES_DB: ${POSTGRES_DB}
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}"]
interval: 10s
timeout: 5s
retries: 10
start_period: 20s
networks:
- keycloak_internal
logging:
driver: json-file
options:
max-size: "10m"
max-file: "5"
keycloak:
build:
context: .
dockerfile: Containerfile
image: local/keycloak:26.7.2
restart: unless-stopped
command: ["start", "--optimized"]
environment:
KC_DB: postgres
KC_DB_URL: jdbc:postgresql://postgres:5432/${POSTGRES_DB}
KC_DB_USERNAME: ${POSTGRES_USER}
KC_DB_PASSWORD: ${POSTGRES_PASSWORD}
KC_HOSTNAME: https://${KC_HOSTNAME}
KC_HTTP_ENABLED: "true"
KC_PROXY_HEADERS: xforwarded
KC_HEALTH_ENABLED: "true"
KC_METRICS_ENABLED: "true"
KC_BOOTSTRAP_ADMIN_USERNAME: ${KC_BOOTSTRAP_ADMIN_USERNAME}
KC_BOOTSTRAP_ADMIN_PASSWORD: ${KC_BOOTSTRAP_ADMIN_PASSWORD}
depends_on:
postgres:
condition: service_healthy
ports:
- "127.0.0.1:8080:8080"
- "127.0.0.1:9000:9000"
mem_limit: 2g
cpus: 2.0
healthcheck:
test:
- CMD-SHELL
- "{ printf 'HEAD /health/ready HTTP/1.0\r\n\r\n' >&0; grep 'HTTP/1.0 200'; } 0<>/dev/tcp/localhost/9000"
interval: 30s
timeout: 5s
retries: 10
start_period: 60s
networks:
- keycloak_internal
logging:
driver: json-file
options:
max-size: "10m"
max-file: "5"
networks:
keycloak_internal:
driver: bridge
volumes:
postgres_data:
Aquí hay varios puntos clave:
- PostgreSQL 17 está dentro del rango PostgreSQL 14–18 oficialmente compatible con Keycloak 26.7.2;
- La base de datos no tiene
portsy solo Keycloak en la red interna puede acceder a ella; KC_HTTP_ENABLED=truesolo permite que Caddy y Keycloak se comuniquen en texto plano en la máquina local; Internet sigue usando HTTPS;KC_HOSTNAMEse escribe como URL externa completa para evitar errores en el emisor, la redirección y las direcciones de recursos estáticos;KC_PROXY_HEADERS=xforwardedpermite que Keycloak analice elX-Forwarded-*establecido por Caddy;9000se enlaza solo a la dirección de loopback para las comprobaciones del host, sin exponerse a través de Caddy;- Keycloak se configura con un límite de memoria de 2 GB para evitar que la JVM amplíe el heap según la memoria de todo el host.
Si el límite de 2 GB provoca presión de memoria en otros servicios de la misma máquina, no lo reduzcas simplemente a unos cientos de MB y finjas que está listo para producción. Migra otros servicios, añade memoria al VPS o ejecuta primero pruebas de carga reales.
Primero renderiza la configuración de Compose. El resultado renderizado contiene información sensible, así que úsalo solo para comprobaciones locales y bórralo inmediatamente después de verlo:
cd /opt/keycloak
docker compose config --quiet
docker compose build --pull keycloak
docker compose up -d
docker compose ps
El primer arranque inicializa la base de datos y el realm master, y suele ser más lento que los arranques posteriores. Sigue los logs de forma continua:
docker compose logs -f --tail=200 keycloak
Cuando aparezcan los mensajes de arranque normal, pulsa Ctrl+C para salir de los logs sin detener el contenedor. Comprueba la interfaz principal y la interfaz de administración:
curl -fsSI http://127.0.0.1:8080/
curl -fsS http://127.0.0.1:9000/health/ready | jq
curl -fsS http://127.0.0.1:9000/health/live | jq
ss -lnt | grep -E ':8080|:9000'
/health/ready debería devolver HTTP 200 con estado UP. 8080 y 9000 deberían escuchar solo en 127.0.0.1. La imagen oficial de Keycloak no instala curl para reducir la superficie de ataque, por lo que la comprobación de estado dentro del contenedor en Compose usa el método Bash TCP indicado en la documentación oficial; las comprobaciones del host aún pueden usar curl.
Si Keycloak muestra unhealthy, mira primero:
docker inspect --format '{{json .State.Health}}' "$(docker compose ps -q keycloak)" | jq
docker compose logs --tail=200 postgres keycloak
No uses restart: always para ocultar errores de contraseña de base de datos, nombre de host o migración. Un contenedor que se reinicia continuamente solo hace que los logs sean más difíciles de leer.
Crea o edita /etc/caddy/Caddyfile:
sso.example.com {
encode zstd gzip
reverse_proxy 127.0.0.1:8080
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains"
X-Content-Type-Options "nosniff"
Referrer-Policy "strict-origin-when-cross-origin"
}
}
Sustituye el dominio por el valor de .env. Caddy establece o amplía X-Forwarded-For por defecto, y establece X-Forwarded-Proto y X-Forwarded-Host; Keycloak ya analiza estas cabeceras mediante KC_PROXY_HEADERS=xforwarded. No permitas que clientes no confiables eludan Caddy y se conecten directamente a 8080.
Valida y recarga:
sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddy
sudo systemctl status caddy --no-pager
curl -fsSI https://sso.example.com/
Si falla la emisión del certificado, comprueba si el DNS ya ha surtido efecto, si los puertos 80/443 están ocupados por otros servicios, si el grupo de seguridad de la nube los permite y si un registro AAAA erróneo apunta las solicitudes a una IPv6 inalcanzable. Para configuración multisitio y resolución de problemas de certificados, consulta la guía completa de HTTPS automático de Caddy.
Keycloak advierte oficialmente de forma explícita contra exponer el puerto de administración 9000 a través de un proxy inverso. No configures reverse_proxy 127.0.0.1:9000 ni lo permitas en el grupo de seguridad.
Abre:
https://sso.example.com/admin/
Inicia sesión con KC_BOOTSTRAP_ADMIN_USERNAME y KC_BOOTSTRAP_ADMIN_PASSWORD de .env. El Keycloak actual trata las cuentas creadas mediante parámetros de arranque como administradores temporales; solo deben usarse para crear el realm master la primera vez, y no se recrean en cada arranque solo porque las variables de entorno sigan existiendo.
Inmediatamente después de iniciar sesión, completa lo siguiente:
- Crea un nuevo administrador permanente en el realm
master, usando una cuenta personal en lugar de unadmincompartido; - Activa TOTP o WebAuthn para el administrador permanente y confirma que puedes volver a iniciar sesión en una ventana de incógnito;
- Asigna al administrador permanente solo los roles de administración que necesita, evitando que las cuentas diarias tengan permisos más allá de sus responsabilidades;
- Tras iniciar sesión correctamente con el administrador permanente, elimina
temp-admin; - Elimina las dos líneas
KC_BOOTSTRAP_ADMIN_*de.envy luego reconstruye el contenedor de Keycloak; - Escribe el proceso de recuperación del administrador permanente en la documentación de operaciones offline del equipo.
Después de editar .env para eliminar las variables del administrador temporal, elimina también las dos líneas de variables de entorno correspondientes de compose.yaml y ejecuta:
cd /opt/keycloak
docker compose config --quiet
docker compose up -d --force-recreate keycloak
docker compose logs --tail=100 keycloak
Eliminar solo las variables de entorno no borra automáticamente la cuenta temporal; debes eliminarla manualmente en la consola de administración. A la inversa, no elimines la cuenta temporal antes de verificar el administrador permanente, o podrías quedarte fuera del sistema.
No pongas usuarios normales y aplicaciones de negocio directamente en el realm master. master se usa principalmente para gestionar otros realms; el negocio debe crear un realm independiente, por ejemplo company.
En el desplegable de realm de la esquina superior izquierda, selecciona Create realm:
- Realm name:
company - Enabled: activado
El nombre del realm pasa a formar parte de las URL de endpoints públicos, así que intenta no cambiarlo tras crearlo. Usa un nombre ASCII corto en minúsculas y no incluyas secretos de la empresa ni descripciones de entorno.
Tras crearlo, revisa al menos los siguientes elementos:
- Realm settings → Login: si se permiten el autorregistro, la contraseña olvidada y el inicio de sesión por correo;
- Realm settings → Email: configura SMTP y prueba los correos de restablecimiento de contraseña;
- Authentication → Policies: políticas de longitud, caracteres e historial de contraseñas;
- Realm settings → Security defenses: activa la detección de fuerza bruta y establece umbrales razonables;
- Realm settings → Tokens: ajusta Access Token, SSO Session y Offline Session según el riesgo;
- Events: registra fallos de inicio de sesión y cambios de administración, y planifica un periodo de retención.
No actives a la vez el autorregistro, contraseñas laxas, sesiones largas y reintentos ilimitados por "comodidad de inicio de sesión". Una vez que SSO centraliza el punto de entrada, la seguridad de ese punto de entrada también afecta a todas las aplicaciones conectadas.
Supón que la aplicación a integrar está en:
https://app.example.com
Su documentación exige que la URI de redirección sea:
https://app.example.com/oauth/callback
Cambia al realm company y ve a Clients → Create client:
- Client type:
OpenID Connect - Client ID:
internal-app - Client authentication: activado para aplicaciones del lado del servidor; desactivado para clientes públicos de navegador puro según la documentación de la aplicación
- Standard flow: normalmente activado para el inicio de sesión con código de autorización en el navegador
- Direct access grants: desactivado salvo que haya una necesidad clara
- Valid redirect URIs:
https://app.example.com/oauth/callback - Web origins: solo
https://app.example.com
Tras guardar, las aplicaciones del lado del servidor pueden obtener el Client Secret en Credentials. El secreto solo puede colocarse en variables de entorno del lado del servidor o en un sistema de gestión de secretos, nunca en JavaScript del navegador, repositorios públicos o imágenes de frontend.
Las URI de redirección deben seguir la documentación oficial de la aplicación. Lo más seguro es introducir direcciones exactas; no escribas * solo para ahorrar esfuerzo. La guía oficial de administración de Keycloak advierte explícitamente que usar redirecciones totalmente comodín en producción facilita a los atacantes construir redirecciones maliciosas.
La mayoría de las aplicaciones solo necesitan los siguientes cuatro elementos:
| Parámetro | Valor de ejemplo |
|---|---|
| Issuer | https://sso.example.com/realms/company |
| Discovery URL | https://sso.example.com/realms/company/.well-known/openid-configuration |
| Client ID | internal-app |
| Client Secret | El valor generado en Credentials |
Primero verifica el documento de descubrimiento desde el VPS o el servidor de la aplicación:
curl -fsS \
https://sso.example.com/realms/company/.well-known/openid-configuration \
| jq '{issuer,authorization_endpoint,token_endpoint,jwks_uri,end_session_endpoint}'
El issuer de la salida debe ser:
https://sso.example.com/realms/company
Si muestra http://127.0.0.1:8080, un nombre de contenedor interno o el dominio equivocado, corrige primero KC_HOSTNAME y las cabeceras del proxy, y luego conecta la aplicación. No desactives la verificación de issuer o TLS en la aplicación para sortear el error.
Keycloak suele tener tres capas de objetos de permiso:
- Group: representa una estructura organizativa o un conjunto de personas, por ejemplo
engineering,finance; - Realm Role: un rol compartido entre varias aplicaciones, por ejemplo
employee,security-auditor; - Client Role: pertenece solo a un cliente concreto, por ejemplo
grafana-admin,wiki-editor.
Se recomienda añadir usuarios a grupos y luego mapear roles a grupos, en lugar de asignar manualmente docenas de roles a cada usuario. Por ejemplo:
Group: engineering
├─ Realm Role: employee
├─ Client Role: grafana-viewer
└─ Client Role: internal-app-editor
Así, cuando un empleado cambia de puesto, solo hay que ajustar el grupo. La aplicación debe seguir validando el token, el issuer, la audiencia, la firma y los roles en su propio backend; "el usuario puede iniciar sesión desde Keycloak" no significa "el usuario tiene automáticamente permisos de administrador de la aplicación".
Si la aplicación no ve los roles esperados, comprueba si el Client Scope y el Protocol Mapper incluyen los claims correspondientes en el ID Token o el Access Token. No metas indiscriminadamente todos los roles y atributos de usuario en el token; cookies o cabeceras de solicitud demasiado grandes pueden hacer que el proxy inverso devuelva 431 Request Header Fields Too Large.
Un punto de entrada de identidad que controla varios sistemas debería, como mínimo, exigir MFA a los administradores. Puedes activar Configure OTP en Authentication → Required actions y luego exigir a grupos de usuarios concretos que configuren TOTP mediante acciones de usuario predeterminadas o un flujo de autenticación condicional.
Las políticas de contraseñas pueden partir de la siguiente base y ajustarse al riesgo de la organización:
- mínimo 14 caracteres;
- rechazar contraseñas usadas recientemente;
- activar la detección de fuerza bruta;
- exigir MFA a los administradores;
- no compartir cuentas de administrador;
- usar una validez corta para los enlaces de restablecimiento por correo;
- mantener los Access Token cortos y confiar en Refresh Token seguros para la renovación;
- acortar las sesiones inactivas y las sesiones máximas para aplicaciones de alto riesgo.
No configures a ciegas los Access Token en varias horas. Una vez que un token se filtra, puede usarse hasta que caduque o se revoque de forma efectiva. Tampoco te limites a acortar los Access Token mientras dejas que las aplicaciones registren Client Secrets y Refresh Tokens.
Ve a Realm settings → Email y rellena SMTP Host, Port, From, cuenta de autenticación y método de cifrado. Haz clic en probar conexión y crea realmente un usuario de prueba normal para verificar:
- Los correos de verificación se entregan;
- Los enlaces de restablecimiento de contraseña usan el dominio HTTPS correcto;
- Los enlaces no se pueden usar tras caducar;
- Los correos no filtran nombres de host internos;
- El dominio remitente tiene SPF, DKIM y DMARC configurados.
La contraseña SMTP, como la contraseña de la base de datos, es configuración sensible. Si se inyecta mediante variables de entorno o KeyStore, no la filtres en la salida de docker compose config, el historial del terminal o los logs de monitorización.
Este artículo activa health y metrics en tiempo de compilación. El host puede comprobar:
curl -fsS http://127.0.0.1:9000/health/ready | jq
curl -fsS http://127.0.0.1:9000/health/live | jq
curl -fsS http://127.0.0.1:9000/metrics | head
Los cuatro endpoints de salud comunes tienen significados distintos:
| Endpoint | Uso |
|---|---|
/health/started | Sonda de arranque, determina si el proceso de arranque ha terminado |
/health/live | Sonda de vida, determina si el proceso aún puede ejecutarse |
/health/ready | Sonda de preparación, determina si puede aceptar nuevo tráfico |
/health | Información de salud agregada |
Un fallo temporal de ready no significa necesariamente que el contenedor deba reiniciarse de inmediato; cuando la base de datos tiene un breve hipo, reiniciar amplifica el fallo. La documentación oficial también indica que las comprobaciones de estado del contenedor basadas en ready sirven para determinar si aceptar tráfico y no deben equipararse directamente a "debe reiniciarse".
Externamente, también deberías comprobar desde otra máquina:
curl -fsS \
https://sso.example.com/realms/company/.well-known/openid-configuration \
>/dev/null
Las comprobaciones externas pueden detectar fallos de DNS, certificados, Caddy y enlace de red pública, mientras que el /health/ready local solo muestra que Keycloak está listo. Puedes conectar comprobaciones de vida externas a Uptime Kuma y enviar las métricas de JVM, pool de conexiones de base de datos y tasa de errores de inicio de sesión a Prometheus o a tu plataforma de monitorización existente.
La base de datos de Keycloak contiene datos sensibles como usuarios, hashes de contraseñas, Client Secrets, claves de firma de realm y sesiones. Las copias de seguridad deben cifrarse, restringirse en acceso y copiarse a otra máquina o almacenamiento de objetos.
Crea el directorio de copias de seguridad:
sudo install -d -m 700 -o "$USER" -g "$USER" /opt/keycloak/backups
cd /opt/keycloak
Ejecuta una copia de seguridad lógica:
set -a
. ./.env
set +a
BACKUP_FILE="/opt/keycloak/backups/keycloak-$(date +%F-%H%M%S).dump"
docker compose exec -T postgres \
pg_dump -U "$POSTGRES_USER" -d "$POSTGRES_DB" -Fc \
> "$BACKUP_FILE"
chmod 600 "$BACKUP_FILE"
pg_restore --list "$BACKUP_FILE" | head
unset POSTGRES_PASSWORD KC_BOOTSTRAP_ADMIN_PASSWORD
También haz copia de lo siguiente, pero no mezcles el .env en texto plano con documentación pública:
compose.yaml,Containerfiley el Caddyfile;- el
.envcifrado o la configuración del sistema externo de gestión de secretos; - temas personalizados, JAR de Provider y sus versiones exactas;
- el volcado de PostgreSQL;
- números de versión, pasos de recuperación y el registro del último simulacro.
La exportación JSON de realm de Keycloak puede ayudar en la migración y la revisión de configuración, pero la documentación oficial indica explícitamente que tiene limitaciones de consistencia y alcance de datos: las exportaciones en línea no garantizan consistencia y no incluyen todas las sesiones, eventos, estados de flujo de trabajo y tokens revocados. Por lo tanto, las exportaciones de realm no pueden sustituir las copias de seguridad de la base de datos.
Si realmente se necesita una exportación CLI completa, programa primero una ventana de mantenimiento para detener Keycloak y luego ejecuta el comando oficial export. La exportación parcial de la consola de administración enmascara contraseñas y Client Secrets y tampoco es adecuada como copia de seguridad completa de recuperación ante desastres.
Un simulacro de recuperación debería verificar al menos:
- Crear un PostgreSQL vacío en un entorno aislado;
- Importar la copia de seguridad usando
pg_restore; - Arrancar con la misma versión de Keycloak;
- Verificar realms, usuarios, clientes, roles y claves de firma;
- Completar inicio de sesión, refresco y cierre de sesión con una aplicación de prueba;
- Registrar el tiempo de recuperación y los puntos de fallo.
Si una copia de seguridad nunca se ha restaurado con éxito, solo es un "archivo posiblemente utilizable". Puedes consultar la guía de copias de seguridad y simulacros de recuperación en VPS para encadenar base de datos, configuración y almacenamiento externo en una tarea programada.
Un servicio de identidad no debería quedarse mucho tiempo en versiones antiguas, pero tampoco deberías descargar una imagen nueva directamente sin copias de seguridad y un plan de reversión. Antes de actualizar:
- Lee las notas de versión, la guía de actualización y los avisos de seguridad de la versión objetivo;
- Haz copia de PostgreSQL, configuración, temas personalizados y Providers;
- Verifica la actualización en un entorno de prueba usando una copia de la copia de seguridad de producción;
- Comprueba si las extensiones personalizadas admiten la versión objetivo;
- Registra el ID de imagen actual, la versión de Keycloak y la versión de PostgreSQL;
- Prepara la recuperación de la base de datos, no solo la reversión de la imagen.
Comprueba la versión y la imagen actuales:
cd /opt/keycloak
docker compose exec keycloak /opt/keycloak/bin/kc.sh --version
docker compose images
docker inspect local/keycloak:26.7.2 --format '{{.Id}}'
Al actualizar, cambia las dos apariciones de la versión de Keycloak en Containerfile, construye primero pero no borres la imagen antigua de inmediato:
docker compose build --pull keycloak
docker compose up -d keycloak
docker compose logs -f --tail=200 keycloak
Tras completar las comprobaciones de estado, el inicio de sesión en la consola de administración y un inicio de sesión OIDC real, limpia la imagen antigua. El arranque de Keycloak puede modificar el esquema de la base de datos, así que simplemente volver a la imagen antigua puede no revertir; el enfoque fiable es restaurar la base de datos previa a la actualización y usar la imagen antigua junto con ella.
Primero confirma si Keycloak se está ejecutando y si el puerto escucha solo en loopback:
cd /opt/keycloak
docker compose ps
docker compose logs --tail=200 keycloak
curl -fsSI http://127.0.0.1:8080/
ss -lnt | grep ':8080'
Si acceder a 127.0.0.1:8080 desde el host también falla, el problema está en Keycloak, la base de datos o Compose, no en Caddy. Si la comprobación local es correcta, revisa el Caddyfile, SELinux/AppArmor y los logs de Caddy.
Confirma:
KC_HOSTNAME: https://${KC_HOSTNAME}
KC_HTTP_ENABLED: "true"
KC_PROXY_HEADERS: xforwarded
También confirma que los usuarios solo pueden acceder al servicio a través de Caddy y que ningún otro proxy sobrescribe X-Forwarded-Proto. Si hay un CDN delante de Caddy, confía solo en los rangos de salida publicados oficialmente por el CDN, no en cabeceras de reenvío de clientes arbitrarios.
Compara el redirect_uri realmente enviado por la aplicación con las Valid redirect URIs del cliente de Keycloak. El protocolo, el dominio, el puerto, la ruta y la barra final pueden afectar la coincidencia:
https://app.example.com/oauth/callback
https://app.example.com/oauth/callback/
Son dos direcciones diferentes. Prefiere añadir direcciones exactas; no uses * como solución temporal y olvides restringirlo después.
Revisa el documento de descubrimiento:
curl -fsS \
https://sso.example.com/realms/company/.well-known/openid-configuration \
| jq -r .issuer
El Issuer configurado en la aplicación debe coincidir carácter por carácter. No trates la Discovery URL como Issuer ni mezcles la ruta antigua /auth/realms/....
Comprueba en orden:
- Si el usuario pertenece al Group correcto;
- Si el Group mapea Realm Roles o Client Roles;
- Si la aplicación lee el ID Token o el Access Token;
- Si el Client Scope / Mapper incluye los claims necesarios;
- Si la aplicación espera
realm_access.roles,resource_accesso claims personalizados; - Si el usuario volvió a iniciar sesión para obtener un token nuevo tras cambiar los roles.
No juzgues la seguridad de la autorización solo decodificando el JWT en el navegador. El backend también debe validar la firma, el issuer, la audiencia, la caducidad y las reglas de autorización.
Revisa los logs pero no imprimas contraseñas directamente:
cd /opt/keycloak
docker compose logs --tail=200 postgres keycloak
docker compose exec postgres pg_isready -U keycloak -d keycloak
Si el volumen de la base de datos ya se ha inicializado, cambiar después solo la contraseña en .env no cambiará automáticamente la contraseña del usuario de la base de datos. Deberías realizar explícitamente un cambio de contraseña, o reconstruir cuando confirmes que no hay datos y tengas autorización; no borres el volumen como paso ordinario de resolución de problemas.
Confirma que el mem_limit: 2g de Compose está en vigor:
docker inspect "$(docker compose ps -q keycloak)" \
--format '{{.HostConfig.Memory}}'
docker stats --no-stream
El contenedor oficial calcula el heap según el límite de memoria; sin límite, la JVM puede expandirse según la memoria total del host. Si realmente hay OOM, analiza primero la concurrencia de inicio de sesión, la caché, los Providers personalizados y la latencia de la base de datos, y luego ajusta el límite; no añadas solo Swap para ocultar el problema.
Las comprobaciones de preparación no sustituyen las comprobaciones reales de negocio. Sigue verificando:
- DNS público y certificados;
- Discovery URL;
- El flujo de código de autorización del cliente de prueba;
- LDAP/fuentes de identidad externas;
- Servicios SMTP o MFA;
- La redirección de la aplicación y la validación del token en el backend.
Se recomienda usar un realm de prueba dedicado o una cuenta de prueba para la monitorización sintética de inicio de sesión, para que los scripts de monitorización no tengan permisos de administrador de producción.
Confirma cada elemento antes de lanzar:
- Keycloak usa una versión explícita
26.7.2, nolatest; - PostgreSQL no mapea puertos públicos;
-
8080y9000se enlazan solo a127.0.0.1; - Caddy HTTPS funciona y el dominio coincide exactamente con
KC_HOSTNAME; -
KC_PROXY_HEADERS=xforwardedestá configurado; - El puerto de administración
9000no está expuesto por Caddy; - Se ha creado un administrador personal permanente y se ha activado MFA;
- El administrador temporal se ha eliminado y las variables de arranque se han quitado;
- Los usuarios de negocio y las aplicaciones no están en el realm
master; - Las URI de redirección OIDC y Web Origins usan valores exactos;
- Los Client Secrets no han entrado en el frontend, Git o los logs;
- Están activadas las políticas de contraseñas, la detección de fuerza bruta y duraciones de sesión razonables;
- SMTP, restablecimiento de contraseña y MFA se han probado realmente;
- La base de datos y la configuración tienen copias de seguridad cifradas en otra máquina;
- Se ha completado un simulacro de recuperación en un entorno aislado;
- Tanto las comprobaciones de estado internas como la ruta de inicio de sesión externa están monitorizadas;
- Existe un plan de reversión a nivel de base de datos antes de las actualizaciones.
El contenedor oficial recomienda un límite de memoria de 2 GB para despliegues de producción pequeños, con el heap máximo de la JVM por defecto en torno al 70 % del límite del contenedor. Los entornos de prueba pueden ser menores, pero cuando Keycloak, PostgreSQL y el sistema comparten máquina, todo el VPS debería partir de 4 GB; para soportar los inicios de sesión críticos de un equipo, 8 GB deja un margen más razonable para la base de datos, la caché y las actualizaciones. La capacidad final debe basarse en pruebas de carga con inicios de sesión concurrentes reales, políticas de hash de contraseñas, fuentes de identidad y plugins.
No se recomienda. start-dev reduce los umbrales de seguridad para el desarrollo local. Producción debería usar start, configurar un nombre de host fijo, HTTPS o terminación TLS controlada en el proxy inverso, una base de datos de producción, y seguir la recomendación oficial de ejecutar primero build y luego start --optimized.
Porque este artículo usa terminación TLS en el borde: la conexión del navegador a Caddy es HTTPS, y la de Caddy a Keycloak en la misma máquina, enlazada solo a loopback, es HTTP. El modo de producción de Keycloak desactiva HTTP por defecto, así que debe activarse explícitamente, junto con el nombre de host externo completo y KC_PROXY_HEADERS=xforwarded. Internet no puede acceder directamente a 8080.
No. La documentación oficial indica que las exportaciones tienen limitaciones de consistencia y alcance de datos y no incluyen todos los eventos, sesiones persistentes, estados de flujo de trabajo y tokens revocados. La recuperación ante desastres debe centrarse en copias de seguridad cifradas de PostgreSQL, con las exportaciones de realm como complemento para migración o revisión de configuración.
Sí. Cada aplicación suele tener su propio cliente, configurado con URI de redirección exactas, Web Origins, secretos y roles. Los entornos con varios departamentos o límites de seguridad muy diferentes pueden dividirse en varios realms, pero los usuarios, las sesiones y la configuración están aislados entre realms, así que considera la sincronización de cuentas y los costes operativos antes de dividir.
Puede servir como punto de partida para equipos pequeños que puedan aceptar breves interrupciones de inicio de sesión, pero no es alta disponibilidad. Que las sesiones existentes sigan siendo utilizables depende del diseño de tokens y sesiones de la aplicación; los nuevos inicios de sesión, los refrescos de token y los cierres de sesión suelen verse afectados por fallos de Keycloak. La actividad crítica debería desplegar varios nodos de Keycloak, una base de datos externa de alta disponibilidad, balanceo de carga y un proceso de recuperación ensayado.
- Descargas y versión actual de Keycloak
- Ejecutar Keycloak en un contenedor
- Configuración de producción de Keycloak
- Configuración de proxy inverso de Keycloak
- Keycloak Hostname v2
- Bases de datos y plataformas compatibles con Keycloak
- Comprobaciones de estado de Keycloak
- Administrador temporal y recuperación de Keycloak
- Importación y exportación de realms de Keycloak
- Documentación de reverse_proxy de Caddy
El criterio de un despliegue exitoso de Keycloak no es "que se abra la consola de administración", sino que el nombre de host externo sea correcto, las URI de redirección estén restringidas, el administrador temporal se haya limpiado, los procesos de MFA y baja de cuentas sean ejecutables, la base de datos pueda recuperarse, las actualizaciones puedan revertirse y la ruta real de inicio de sesión OIDC esté monitorizada. Primero haz funcionar este bucle mínimo y luego conecta GitLab, Grafana, Jenkins o aplicaciones autoalojadas; la expansión posterior será mucho más segura.