很多团队已经有 GitLab、Grafana、Jenkins、内部管理后台和自建 AI 应用,却仍在每个系统里单独创建账号。员工离职时要逐个禁用,密码策略各不相同,审计也很难统一。Keycloak 的价值不是再增加一个登录页,而是把账号、单点登录、MFA、角色和会话策略集中起来,让支持 OpenID Connect(OIDC)或 SAML 的应用共用一个身份入口。
本文以 Keycloak 26.7.2、PostgreSQL 17、Docker Compose、Caddy 和 Ubuntu 24.04 为基准,在一台 VPS 上搭建可用于小型生产环境的 SSO 服务,并完整演示域名 HTTPS、临时管理员处理、Realm、OIDC Client、回调地址、角色、备份、升级和故障排查。Keycloak 版本与官方支持矩阵按 2026 年 9 月核对;实际部署时仍应先查看最新安全公告和版本说明。
Keycloak 是身份与访问管理系统,不是普通密码管理器。它可以为多个应用提供统一登录,并集中完成:
- OpenID Connect、OAuth 2.0 和 SAML 2.0 身份认证;
- 用户、组、Realm Role、Client Role 和属性管理;
- TOTP、WebAuthn、密码策略、暴力破解检测和会话控制;
- 对接 LDAP、Active Directory、GitHub、Google 等外部身份源;
- 用户自助修改资料、重置密码和管理二次验证;
- 管理事件、登录事件和细粒度管理员权限。
如果你只想给一个不支持 OIDC 的网页加一道登录验证,Authelia 或反向代理 Basic Auth 通常更轻。若应用本身支持 OIDC,且你需要统一用户生命周期、组织权限或后续接入更多系统,Keycloak 更有扩展空间。
| 方案 | 优势 | 主要代价 | 更适合 |
|---|---|---|---|
| Keycloak | OIDC/SAML 完整,Realm、角色、身份源和策略丰富 | 配置项多,Java 服务需要明确内存限制 | 多应用、团队账号、企业身份集成 |
| Authentik | 管理界面友好,代理与目录能力齐全 | 组件和概念同样不少 | 希望用可视化流程编排登录 |
| Authelia | 轻量,适合配合反向代理保护网页 | 不是完整的企业身份平台 | 少量内部网页统一前置认证 |
| 应用各自管理账号 | 起步最快 | 离职回收、MFA、审计和密码策略分散 | 只有一个应用且用户极少 |
本文使用单节点架构:
浏览器 / 应用
|
| 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 在 VPS 上终止 TLS,Keycloak 只通过回环地址提供 HTTP。PostgreSQL 完全不映射宿主机端口。Keycloak 管理接口的 9000 只绑定 127.0.0.1,不放进 Caddy,也不对公网开放。
Keycloak 官方容器会按容器内存限制计算 JVM 堆:最大堆默认约为限制的 70%,另有非堆内存开销。官方对小型生产部署建议使用 2GB 容器内存限制。考虑 PostgreSQL、Caddy、系统缓存和备份任务,建议从下面的配置起步:
| 使用规模 | VPS 建议起步 | 说明 |
|---|---|---|
| 测试、少于 20 个用户 | 2 vCPU、4GB 内存、40GB NVMe | 不代表可用性保证,不建议承载关键登录 |
| 小团队、数百用户、低并发 | 4 vCPU、8GB 内存、80GB NVMe | Keycloak 限制 2GB,给数据库和系统留足余量 |
| 登录高峰明显或身份源较多 | 先压测,再扩容或做多节点 | 密码哈希、外部目录和自定义插件都会影响容量 |
单台 VPS 仍是单点故障。只要所有应用都依赖它登录,Keycloak 不可用就会影响新会话。关键业务应使用外部 PostgreSQL、至少两个 Keycloak 节点、负载均衡和异机备份;不要把本文的单节点方案误认为高可用架构。
为 SSO 使用独立域名,例如 sso.example.com。在 DNS 服务商添加指向 VPS 公网地址的 A 记录;只有 VPS 确实配置了可用 IPv6 时才添加 AAAA 记录。
需要的端口很少:
| 端口 | 来源 | 用途 | 是否公开 |
|---|---|---|---|
22/TCP | 管理 IP | SSH | 最好限制来源 |
80/TCP | 公网 | Caddy 证书验证与 HTTP 跳转 | 是 |
443/TCP | 公网或指定网络 | Keycloak 登录与 OIDC/SAML 端点 | 是 |
8080/TCP | Caddy 本机 | Keycloak HTTP | 否,只绑定回环 |
9000/TCP | 本机监控 | 健康检查与指标 | 否,只绑定回环 |
5432/TCP | Docker 内部网络 | PostgreSQL | 否,不映射 |
如果云安全组已经放行但仍无法访问,按端口、防火墙、安全组和监听地址排查指南逐层检查,不要直接关闭防火墙验证。
以下命令以 Ubuntu 24.04 为例。Docker 建议按官方仓库安装;如果 VPS 已经在运行 Docker,不要重复执行会覆盖仓库配置的第三方脚本。
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
先确认 docker compose 是 Compose v2。生产环境还应配置日志轮转、资源限制、健康检查和回滚流程,可结合Docker Compose 生产配置清单一起执行。
创建只允许管理员访问的目录:
sudo install -d -m 750 -o "$USER" -g "$USER" /opt/keycloak
cd /opt/keycloak
umask 077
生成数据库密码和临时管理员密码。这里使用十六进制字符,避免 $ 等字符被 Compose 当成变量再次展开:
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
把 sso.example.com 替换为你的真实域名。不要把 .env 提交到 Git,也不要把它贴进聊天、截图或工单。Keycloak 官方还支持把敏感配置放入 Java KeyStore;本文用权限收紧的环境文件,是为了让单机部署步骤更容易复现。
确认文件权限和 Compose 展开结果时,不要直接输出真实密码:
stat -c '%a %U:%G %n' .env
grep -E '^(KC_HOSTNAME|POSTGRES_DB|POSTGRES_USER|KC_BOOTSTRAP_ADMIN_USERNAME)=' .env
Keycloak 官方推荐先执行 build,再用 start --optimized 启动。这样数据库驱动、健康检查和指标等构建期选项提前写入镜像,启动时不必重复检测和构建。
创建 /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"]
不要在生产环境使用 latest。本文固定 Keycloak 26.7.2;升级时修改两个 FROM,重新构建并完成数据库备份与回滚测试。
创建 /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:
这里有几个关键点:
- PostgreSQL 17 在 Keycloak 26.7.2 官方支持的 PostgreSQL 14–18 范围内;
- 数据库没有
ports,只能由内部网络上的 Keycloak 访问; KC_HTTP_ENABLED=true只允许 Caddy 与 Keycloak 在本机明文通信,公网仍使用 HTTPS;KC_HOSTNAME写成完整外部 URL,避免签发者、回调和静态资源地址错误;KC_PROXY_HEADERS=xforwarded让 Keycloak解析 Caddy 设置的X-Forwarded-*;9000只绑定回环地址,供宿主机检查,不经 Caddy 暴露;- Keycloak 设置 2GB 内存上限,避免 JVM 根据整台宿主机内存扩大堆。
若 2GB 限制导致同机其他服务内存紧张,不要简单降到几百 MB 后假装生产可用。应迁移其他服务、增加 VPS 内存或先做真实负载测试。
先渲染 Compose 配置。渲染结果包含敏感信息,因此只用于本机检查,查看后立即删除:
cd /opt/keycloak
docker compose config --quiet
docker compose build --pull keycloak
docker compose up -d
docker compose ps
首次启动会初始化数据库和 master Realm,通常比后续启动慢。持续查看日志:
docker compose logs -f --tail=200 keycloak
出现正常启动信息后按 Ctrl+C 退出日志,不会停止容器。检查主接口与管理接口:
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 应返回 HTTP 200 且状态为 UP。8080 和 9000 应只监听 127.0.0.1。官方 Keycloak 镜像为了缩小攻击面没有安装 curl,所以 Compose 的容器内健康检查使用官方文档给出的 Bash TCP 方法;宿主机检查仍可使用 curl。
如果 Keycloak 显示 unhealthy,先看:
docker inspect --format '{{json .State.Health}}' "$(docker compose ps -q keycloak)" | jq
docker compose logs --tail=200 postgres keycloak
不要用 restart: always 掩盖数据库密码、主机名或迁移错误。容器不断重启只会让日志更难读。
创建或编辑 /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"
}
}
把域名替换为 .env 中的值。Caddy 默认会设置或扩充 X-Forwarded-For,并设置 X-Forwarded-Proto 与 X-Forwarded-Host;Keycloak 已通过 KC_PROXY_HEADERS=xforwarded 解析这些头。不要让不受信任的客户端绕过 Caddy 直接连接 8080。
验证并重载:
sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddy
sudo systemctl status caddy --no-pager
curl -fsSI https://sso.example.com/
如果证书申请失败,检查 DNS 是否已生效、80/443 是否被其他服务占用、云安全组是否放行,以及错误 AAAA 记录是否把请求指向不可达 IPv6。多站点配置和证书排障可参考Caddy 自动 HTTPS 完全指南。
Keycloak 官方明确不建议通过反向代理公开管理端口 9000。不要配置 reverse_proxy 127.0.0.1:9000,也不要在安全组里放行它。
打开:
https://sso.example.com/admin/
使用 .env 中的 KC_BOOTSTRAP_ADMIN_USERNAME 和 KC_BOOTSTRAP_ADMIN_PASSWORD 登录。当前 Keycloak 把通过启动参数创建的账号视为临时管理员,它只应用于首次创建 master Realm,并不会因为环境变量仍存在而在每次启动时重建。
登录后立即完成:
- 在
masterRealm 创建一个新的正式管理员,使用个人账号而不是共享admin; - 为正式管理员启用 TOTP 或 WebAuthn,并确认能用无痕窗口重新登录;
- 给正式管理员分配所需管理角色,避免日常账号拥有超出职责的权限;
- 用正式管理员登录成功后,删除
temp-admin; - 从
.env删除两行KC_BOOTSTRAP_ADMIN_*,再重建 Keycloak 容器; - 把正式管理员恢复流程写入团队的离线运维文档。
编辑 .env 删除临时管理员变量后,还要从 compose.yaml 删除对应的两行环境变量,然后执行:
cd /opt/keycloak
docker compose config --quiet
docker compose up -d --force-recreate keycloak
docker compose logs --tail=100 keycloak
仅删除环境变量不会自动删除临时账号;必须在管理后台手动删除。反过来,也不要在尚未验证正式管理员时先删临时账号,否则可能把自己锁在系统外。
不要把普通用户和业务应用直接放进 master Realm。master 主要用于管理其他 Realm;业务应创建独立 Realm,例如 company。
在左上角 Realm 下拉菜单中选择 Create realm:
- Realm name:
company - Enabled:开启
Realm 名会进入公开端点 URL,创建后尽量不要改。建议使用简短的小写 ASCII 名称,不要放公司机密或环境说明。
创建后至少检查以下项目:
- Realm settings → Login:是否允许自助注册、忘记密码、邮箱登录;
- Realm settings → Email:配置 SMTP,测试重置密码邮件;
- Authentication → Policies:密码长度、字符和历史密码策略;
- Realm settings → Security defenses:开启暴力破解检测并设置合理阈值;
- Realm settings → Tokens:根据风险调整 Access Token、SSO Session 和 Offline Session;
- Events:记录登录失败和管理变更,并规划保留周期。
不要为了“登录方便”同时开启自助注册、宽松密码、长会话和无限重试。SSO 把入口集中后,入口安全性也会影响所有接入应用。
假设要接入的应用地址是:
https://app.example.com
它的文档要求回调地址为:
https://app.example.com/oauth/callback
切换到 company Realm,进入 Clients → Create client:
- Client type:
OpenID Connect - Client ID:
internal-app - Client authentication:服务端应用开启;纯浏览器公共客户端按应用文档关闭
- Standard flow:浏览器授权码登录通常开启
- Direct access grants:没有明确需求时关闭
- Valid redirect URIs:
https://app.example.com/oauth/callback - Web origins:只填
https://app.example.com
保存后,服务端应用可在 Credentials 获取 Client Secret。Secret 只能放在服务端环境变量或秘密管理系统中,不能写进浏览器 JavaScript、公开仓库或前端镜像。
回调地址必须以应用官方文档为准。最安全的是填写精确地址,不要为了省事写 *。Keycloak 官方管理指南明确警告,生产环境使用全通配回调会让攻击者更容易构造恶意重定向。
大多数应用只需要下面四项:
| 参数 | 示例值 |
|---|---|
| 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 | 在 Credentials 中生成的值 |
先从 VPS 或应用服务器验证发现文档:
curl -fsS \
https://sso.example.com/realms/company/.well-known/openid-configuration \
| jq '{issuer,authorization_endpoint,token_endpoint,jwks_uri,end_session_endpoint}'
输出中的 issuer 必须是:
https://sso.example.com/realms/company
如果它显示 http://127.0.0.1:8080、内部容器名或错误域名,先修正 KC_HOSTNAME 和代理头,再接入应用。不要在应用里关闭 issuer 或 TLS 校验来绕过错误。
Keycloak 常见的权限对象有三层:
- Group:表示组织结构或人员集合,例如
engineering、finance; - Realm Role:跨多个应用共享的角色,例如
employee、security-auditor; - Client Role:只属于某个 Client,例如
grafana-admin、wiki-editor。
推荐把用户加入组,再把角色映射给组,而不是给每个用户手工分配几十个角色。例如:
Group: engineering
├─ Realm Role: employee
├─ Client Role: grafana-viewer
└─ Client Role: internal-app-editor
这样员工转岗时只需要调整组。应用仍必须在自己的后端校验 token、issuer、audience、签名和角色;“用户能从 Keycloak 登录”不等于“用户自动拥有应用管理员权限”。
如果应用看不到预期角色,检查 Client Scope 和 Protocol Mapper 是否把对应 claim 放入 ID Token 或 Access Token。不要把所有角色和用户属性无差别塞进 token,过大的 Cookie 或请求头会导致反向代理返回 431 Request Header Fields Too Large。
一个身份入口控制多个系统,至少应为管理员强制 MFA。可以在 Authentication → Required actions 启用 Configure OTP,再通过默认用户动作或条件认证流程要求特定用户组配置 TOTP。
密码策略可从以下基线开始,再结合组织风险调整:
- 最少 14 个字符;
- 拒绝近期使用过的密码;
- 开启暴力破解检测;
- 管理员强制 MFA;
- 不共享管理员账号;
- 邮箱重置链接使用短有效期;
- Access Token 保持较短,依赖安全的 Refresh Token 续期;
- 对高风险应用缩短空闲会话和最长会话。
不要盲目把 Access Token 设置成数小时。Token 泄露后,在过期或被有效撤销前都可能被使用。也不要只缩短 Access Token,却让应用把 Client Secret、Refresh Token 记录进日志。
进入 Realm settings → Email,填写 SMTP Host、Port、From、认证账号和加密方式。点击测试连接并实际创建一个普通测试用户,验证:
- 验证邮件能送达;
- 重置密码链接使用正确的 HTTPS 域名;
- 链接过期后不能继续使用;
- 邮件不会泄露内部主机名;
- 发件域名配置 SPF、DKIM 和 DMARC。
SMTP 密码与数据库密码一样属于敏感配置。若使用环境变量或 KeyStore 注入,不要在 docker compose config 输出、终端历史和监控日志里泄露。
本文在构建期启用了 health 与 metrics。宿主机可检查:
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
四个常见健康端点含义不同:
| 端点 | 用途 |
|---|---|
/health/started | 启动探针,判断启动过程是否完成 |
/health/live | 存活探针,判断进程是否仍可运行 |
/health/ready | 就绪探针,判断是否能接收新流量 |
/health | 聚合健康信息 |
ready 暂时失败不一定应该立刻重启容器;数据库短时抖动时,重启会放大故障。官方也说明基于 ready 的容器健康检查适合判断是否接收流量,不应直接等同于“必须重启”。
对外还应从另一台机器检查:
curl -fsS \
https://sso.example.com/realms/company/.well-known/openid-configuration \
>/dev/null
外部检查能发现 DNS、证书、Caddy 和公网链路故障,而本机 /health/ready 只能说明 Keycloak 自身就绪。可将外部探活接入 Uptime Kuma,把 JVM、数据库连接池和登录错误率接入 Prometheus 或现有监控平台。
Keycloak 的数据库包含用户、密码哈希、Client Secret、Realm 签名密钥和会话等敏感数据。备份必须加密、限制访问并复制到另一台机器或对象存储。
创建备份目录:
sudo install -d -m 700 -o "$USER" -g "$USER" /opt/keycloak/backups
cd /opt/keycloak
执行逻辑备份:
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
同时备份以下内容,但不要把明文 .env 与公开文档混放:
compose.yaml、Containerfile和 Caddyfile;- 加密后的
.env或外部秘密管理系统配置; - 自定义主题、Provider JAR 和其准确版本;
- PostgreSQL dump;
- 版本号、恢复步骤和最近一次演练记录。
Keycloak 的 Realm JSON 导出可辅助迁移和配置审查,但官方明确指出它有一致性和数据范围限制:在线导出不能保证一致,且不会包含所有会话、事件、工作流状态和撤销 token。因此,Realm 导出不能替代数据库备份。
若确实需要完整 CLI 导出,应先安排维护窗口停止 Keycloak,再按官方 export 命令执行。管理后台的 Partial export 会屏蔽密码和 Client Secret,也不适合作为完整灾难恢复备份。
恢复演练至少应验证:
- 在隔离环境创建空 PostgreSQL;
- 使用
pg_restore导入备份; - 使用相同 Keycloak 版本启动;
- 验证 Realm、用户、Client、角色和签名密钥;
- 用测试应用走完登录、刷新和退出;
- 记录恢复时间与失败点。
如果备份从未恢复成功过,它只能算“可能可用的文件”。可参考VPS 备份与恢复演练指南把数据库、配置和异地存储串成定期任务。
身份服务不应长期停留在旧版本,但也不应在没有备份和回滚方案时直接拉取新镜像。升级前:
- 阅读目标版本发布说明、升级指南和安全公告;
- 备份 PostgreSQL、配置、自定义主题和 Provider;
- 在测试环境用生产备份副本验证升级;
- 检查自定义扩展是否支持目标版本;
- 记录当前镜像 ID、Keycloak 版本和 PostgreSQL 版本;
- 准备数据库恢复,而不只是镜像回滚。
查看当前版本与镜像:
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}}'
升级时修改 Containerfile 中两处 Keycloak 版本,先构建但不立即删除旧镜像:
docker compose build --pull keycloak
docker compose up -d keycloak
docker compose logs -f --tail=200 keycloak
完成健康检查、管理后台登录和真实 OIDC 登录后,再清理旧镜像。Keycloak 启动可能修改数据库结构,因此简单改回旧镜像未必能回滚;可靠方案是恢复升级前数据库并配套使用旧镜像。
先确认 Keycloak 是否运行、端口是否只在回环监听:
cd /opt/keycloak
docker compose ps
docker compose logs --tail=200 keycloak
curl -fsSI http://127.0.0.1:8080/
ss -lnt | grep ':8080'
如果宿主机访问 127.0.0.1:8080 都失败,问题在 Keycloak、数据库或 Compose,不在 Caddy。若本机正常,再检查 Caddyfile、SELinux/AppArmor 和 Caddy 日志。
确认:
KC_HOSTNAME: https://${KC_HOSTNAME}
KC_HTTP_ENABLED: "true"
KC_PROXY_HEADERS: xforwarded
同时确认用户只能从 Caddy 访问服务,且没有其他代理覆盖 X-Forwarded-Proto。如果 CDN 位于 Caddy 前面,只信任 CDN 官方公布的出口网段,不要信任任意客户端传入的转发头。
对照应用实际发出的 redirect_uri 与 Keycloak Client 的 Valid redirect URIs。协议、域名、端口、路径和末尾斜杠都可能影响匹配:
https://app.example.com/oauth/callback
https://app.example.com/oauth/callback/
这是两个不同地址。优先添加精确地址,不要用 * 临时绕过后忘记收紧。
检查发现文档:
curl -fsS \
https://sso.example.com/realms/company/.well-known/openid-configuration \
| jq -r .issuer
应用配置的 Issuer 必须逐字符一致。不要把 Discovery URL 当成 Issuer,也不要混用旧版 /auth/realms/... 路径。
依次检查:
- 用户是否属于正确 Group;
- Group 是否映射 Realm Role 或 Client Role;
- 应用读取的是 ID Token 还是 Access Token;
- Client Scope / Mapper 是否包含所需 claim;
- 应用是否期待
realm_access.roles、resource_access或自定义 claim; - 修改角色后是否重新登录获取了新 token。
不要只在浏览器里解码 JWT 就判断授权安全。后端还必须验证签名、issuer、audience、过期时间和授权规则。
查看日志但不要直接打印密码:
cd /opt/keycloak
docker compose logs --tail=200 postgres keycloak
docker compose exec postgres pg_isready -U keycloak -d keycloak
如果数据库 Volume 已经初始化,后来只修改 .env 的密码不会自动修改数据库用户密码。应明确执行密码变更,或在确认无数据且有授权时重建;不要删除 Volume 作为普通排障步骤。
确认 Compose 的 mem_limit: 2g 已生效:
docker inspect "$(docker compose ps -q keycloak)" \
--format '{{.HostConfig.Memory}}'
docker stats --no-stream
官方容器按内存限制计算堆,未设置限制时 JVM 可能按宿主机总内存扩大。若确实 OOM,应先分析登录并发、缓存、自定义 Provider 和数据库延迟,再调整限制;不要只增加 Swap 掩盖问题。
就绪检查不能替代真实业务检查。继续验证:
- 公网 DNS 与证书;
- Discovery URL;
- 测试 Client 的授权码流程;
- 外部 LDAP/身份源;
- SMTP 或 MFA 服务;
- 应用回调和后端 token 校验。
建议用专用测试 Realm 或测试账号做合成登录监控,避免监控脚本拥有生产管理员权限。
上线前逐项确认:
- Keycloak 使用明确版本
26.7.2,没有使用latest; - PostgreSQL 未映射公网端口;
-
8080与9000只绑定127.0.0.1; - Caddy HTTPS 正常,域名与
KC_HOSTNAME完全一致; -
KC_PROXY_HEADERS=xforwarded已配置; - 管理端口
9000没有被 Caddy 公开; - 已创建正式个人管理员并启用 MFA;
- 临时管理员已删除,启动变量已移除;
- 业务用户与应用不放在
masterRealm; - OIDC 回调地址和 Web Origins 使用精确值;
- Client Secret 没有进入前端、Git 和日志;
- 已启用密码策略、暴力破解检测与合理会话期限;
- SMTP、重置密码和 MFA 已实际测试;
- 数据库与配置有加密异机备份;
- 已在隔离环境完成一次恢复演练;
- 内部健康检查和外部登录链路都有监控;
- 升级前有数据库级回滚方案。
官方容器建议小型生产部署设置 2GB 内存限制,最大 JVM 堆默认约占容器限制的 70%。测试环境可以更低,但 Keycloak、PostgreSQL 与系统同机时,整台 VPS 建议从 4GB 起;要承载团队关键登录,8GB 会留出更合理的数据库、缓存和升级空间。最终容量应以真实并发登录、密码哈希策略、身份源和插件压测为准。
不建议。start-dev 为本地开发降低了安全门槛。生产环境应使用 start,配置固定 hostname、HTTPS 或受控的反向代理 TLS 终止、生产数据库,并按官方建议先 build 后 start --optimized。
因为本文采用边缘 TLS 终止:浏览器到 Caddy 是 HTTPS,Caddy 到同一台机器上只绑定回环地址的 Keycloak 是 HTTP。Keycloak 生产模式默认关闭 HTTP,所以需要显式开启,并同时设置完整外部 hostname 与 KC_PROXY_HEADERS=xforwarded。公网不能直接访问 8080。
不能。官方说明导出存在一致性和数据范围限制,不包含所有事件、持久会话、工作流状态和撤销 token。灾难恢复应以加密的 PostgreSQL 备份为核心,Realm 导出可作为迁移或配置审查的补充。
可以。每个应用通常创建独立 Client,并为它配置精确回调地址、Web Origins、Secret 和角色。多个部门或安全边界差异很大的环境可以拆成多个 Realm,但 Realm 之间的用户、会话和配置相互隔离,拆分前要考虑账号同步与运维成本。
可以作为小团队、可接受短时登录中断场景的起点,但它不是高可用。已有会话是否继续可用取决于应用的 token 与会话设计,新登录、刷新 token 和退出通常会受 Keycloak 故障影响。关键业务应部署多个 Keycloak 节点、外部高可用数据库、负载均衡和经过演练的恢复流程。
- Keycloak 下载与当前版本
- 在容器中运行 Keycloak
- Keycloak 生产配置
- Keycloak 反向代理配置
- Keycloak Hostname v2
- Keycloak 支持的数据库与平台
- Keycloak 健康检查
- Keycloak 临时管理员与恢复
- Keycloak Realm 导入与导出
- Caddy reverse_proxy 文档
Keycloak 部署成功的标准不是“能打开管理后台”,而是外部 hostname 正确、回调地址收紧、临时管理员已清理、MFA 与账号回收流程可执行、数据库能恢复、升级能回滚,并且真实 OIDC 登录链路受到监控。先把这条最小闭环跑通,再接入 GitLab、Grafana、Jenkins 或自建应用,后续扩展会安全得多。
