MAATRIX / Блог / Keycloak на сервере: частые ошибки и решения

Keycloak на сервере: частые ошибки и решения

MAATRIX

Keycloak — не тот сервис, который прощает небрежность в конфигурации. Стоит перепутать KC_HOSTNAME с реальным адресом или забыть про reverse-proxy заголовки — и вместо единого входа для всех приложений вы получаете бесконечный редирект на логин или загадочный invalid_grant. Ниже — ошибки, с которыми сталкивается почти каждый, кто разворачивает Keycloak на своём VPS, и рабочие решения без танцев с бубном.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →

Keycloak не стартует: разбираем логи

Первый источник проблем — сам старт контейнера или процесса. Типичный docker-compose для Keycloak 26.x выглядит так:

services:
  keycloak:
    image: quay.io/keycloak/keycloak:26.0
    command: start
    environment:
      KC_DB: postgres
      KC_DB_URL: jdbc:postgresql://postgres:5432/keycloak
      KC_DB_USERNAME: keycloak
      KC_DB_PASSWORD: ${KC_DB_PASSWORD}
      KC_HOSTNAME: auth.example.com
      KC_PROXY_HEADERS: xforwarded
      KC_HTTP_ENABLED: "true"
      KEYCLOAK_ADMIN: admin
      KEYCLOAK_ADMIN_PASSWORD: ${KC_ADMIN_PASSWORD}
    ports:
      - "127.0.0.1:8080:8080"
    depends_on:
      - postgres
    restart: unless-stopped

Если контейнер падает сразу после старта, смотрите логи:

docker compose logs -f keycloak

Частые находки:

  • Failed to obtain JDBC connection — Postgres ещё не готов принимать подключения, а Keycloak уже пытается достучаться. depends_on в docker-compose не ждёт готовности БД, только запуска контейнера. Добавьте healthcheck для postgres и условие condition: service_healthy.
  • ERROR: relation "..." does not exist — Keycloak запускался с одной версией схемы БД, потом образ обновили без миграции. Перед апгрейдом всегда делайте бэкап БД и проверяйте release notes на breaking changes в схеме.
  • Unknown option: '--...' — команда start-dev вместо start (или наоборот) плюс параметр, который не поддерживается в вашей версии образа. Опции CLI между мажорными версиями Keycloak меняются заметно чаще, чем хотелось бы — не копируйте конфиг из статьи трёхлетней давности не глядя на версию.

Если используете systemd вместо контейнера — не забудьте отдельного пользователя без прав root и ограничение памяти в unit-файле:

[Service]
User=keycloak
Group=keycloak
Environment="JAVA_OPTS_APPEND=-Xms512m -Xmx1024m"
ExecStart=/opt/keycloak/bin/kc.sh start --optimized
Restart=on-failure
RestartSec=5

start-dev в продакшене: ловушка, в которую все попадают

Самая частая причина странного поведения Keycloak в проде — команда start-dev вместо start. Она включена в тысячах гайдов, потому что позволяет запустить сервер за 30 секунд без сертификатов и без настройки hostname. Но start-dev:

  • отключает проверку hostname (redirect URI можно подделать);
  • использует H2 в памяти, если явно не указана внешняя БД (данные теряются при перезапуске);
  • не проверяет корректность SSL-цепочки.

Если вы видите в логах строку Running the server in development mode — это не шутка, это прямое указание, что конфигурация не готова к продакшену. Переход на start требует явно задать:

kc.sh build --db=postgres
kc.sh start --hostname=auth.example.com --https-certificate-file=/etc/keycloak/fullchain.pem --https-certificate-key-file=/etc/keycloak/privkey.pem

Либо, если TLS терминируется на reverse-proxy (что чаще всего и происходит), — --http-enabled=true --hostname-strict=false вместе с корректными заголовками X-Forwarded-* от прокси.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Бесконечный редирект на страницу логина

Классика: пользователь логинится, Keycloak редиректит обратно в приложение, приложение снова требует логин — и по кругу. Причины почти всегда одна из трёх:

  1. Cookie SameSite и домен не совпадают. Если Keycloak на auth.example.com, а приложение на app.example.com, куки сессии Keycloak (KEYCLOAK_SESSION, AUTH_SESSION_ID) должны корректно устанавливаться для своего домена, а не блокироваться браузером из-за SameSite=Strict за HTTP вместо HTTPS.
  2. Расхождение времени между сервером Keycloak и клиентом/приложением. JWT-токены имеют exp и iat с допуском в несколько секунд. Если на VPS сбит NTP, токен может считаться просроченным ещё до выдачи. Проверьте:
timedatectl status
# при рассинхроне
sudo systemctl restart systemd-timesyncd
  1. Неверный KC_HOSTNAME за прокси. Если Keycloak не знает, что он открыт снаружи по https://auth.example.com, а сам генерирует ссылки на http://keycloak:8080 (внутреннее имя контейнера), браузер получает редиректы на недоступный адрес. Проверка, какие заголовки реально доходят до Keycloak:
curl -sI https://auth.example.com/realms/master \
  -H "X-Forwarded-Proto: https" \
  -H "X-Forwarded-Host: auth.example.com"

Nginx как reverse-proxy перед Keycloak

Рабочий конфиг для nginx перед Keycloak — с обязательной передачей заголовков и увеличенными таймаутами, потому что операции с realm и импорт большого числа пользователей могут выполняться дольше стандартных 60 секунд:

server {
    listen 443 ssl http2;
    server_name auth.example.com;

    ssl_certificate     /etc/letsencrypt/live/auth.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/auth.example.com/privkey.pem;

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        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 443;

        proxy_read_timeout 120s;
        proxy_send_timeout 120s;
        proxy_buffer_size 16k;
        proxy_buffers 4 16k;
    }
}

Без X-Forwarded-Proto Keycloak может генерировать ссылки на http://, что браузер с включённым HSTS просто заблокирует. Если вы только настраиваете proxy с нуля, у нас есть отдельный разбор — nginx как reverse-proxy: пошаговая установка — и статья о частых проблемах именно nginx-прокси: nginx как reverse-proxy: частые ошибки и решения.

Проблемы с SSL и unable to find valid certification path

Когда приложение (например, backend на Java/Spring) обращается к Keycloak по HTTPS с самоподписанным или внутренним CA сертификатом, вылезает классическая ошибка Java:

sun.security.validator.ValidatorException: PKIX path building failed:
unable to find valid certification path to requested target

Решение — добавить сертификат CA в truststore приложения:

keytool -import -alias keycloak-ca -file ca.crt \
  -keystore $JAVA_HOME/lib/security/cacerts -storepass changeit -noprompt

Но правильнее в проде вообще не использовать самоподписанные сертификаты для публичного домена — Let's Encrypt через certbot закрывает вопрос бесплатно и без ручного распространения CA по клиентам. Если у вас уже был случай, когда сертификат вовремя не продлился и всё легло — почитайте почему не обновился SSL-сертификат и как это предотвратить, там разобраны типичные причины пропущенного renew.

База данных: подключение, деградация под нагрузкой и бэкапы

Keycloak активно пишет в БД на каждый логин — сессии, события, кэш офлайн-токенов. На небольшом VPS с 1-2 vCPU и Postgres на том же сервере под нагрузкой в несколько сотен логинов в минуту типична деградация: растёт max_connections, появляются таймауты.

Проверка активных подключений:

SELECT count(*), state FROM pg_stat_activity
WHERE datname = 'keycloak' GROUP BY state;

Если idle in transaction растёт бесконтрольно — Keycloak не успевает закрывать транзакции, часто из-за медленного диска или нехватки памяти для Postgres. Практический минимум для связки Keycloak + Postgres на отдельном VPS — 2 vCPU / 4 ГБ RAM на каждый сервис, то есть от 4 vCPU / 8 ГБ суммарно, если ожидается активная нагрузка от нескольких приложений одновременно; для тестового стенда хватит и меньше. Точные цифры сильно зависят от количества realm, пользователей и частоты логинов — ориентируйтесь на свой профиль нагрузки, а не на общие цифры из статьи.

Если Postgres настроен с нуля недавно, стоит свериться с общим чек-листом по типичным сбоям — PostgreSQL на сервере: частые ошибки и решения.

Realm, клиенты и redirect URI: где чаще всего косячат руками

Ошибка Invalid parameter: redirect_uri — почти всегда буквальное несовпадение строки в настройках клиента и тем, что реально шлёт приложение. Частые нюансы:

  • слэш в конце URI важен: https://app.example.com/callback и https://app.example.com/callback/ — это разные строки для Keycloak;
  • wildcard * в redirect URI разрешён, но с версии 22+ Keycloak предупреждает о нём в консоли как о небезопасной практике — для продакшена перечисляйте точные URI;
  • при использовании нескольких окружений (dev/staging/prod) проще завести отдельные клиенты в одном realm, чем пытаться держать один клиент с десятком redirect URI — так проще отзывать доступ и не потерять в логах, какое окружение стучалось.

Проверка конфигурации клиента через Admin REST API, если веб-консоль недоступна:

TOKEN=$(curl -s -X POST https://auth.example.com/realms/master/protocol/openid-connect/token \
  -d "client_id=admin-cli" -d "username=admin" -d "password=$KC_ADMIN_PASSWORD" -d "grant_type=password" \
  | jq -r .access_token)

curl -s https://auth.example.com/admin/realms/myrealm/clients \
  -H "Authorization: Bearer $TOKEN" | jq '.[] | {clientId, redirectUris}'

Отдельная тема — резервное копирование. Экспорт realm — то, о чём вспоминают обычно после того, как что-то сломалось. Делать это стоит регулярно и до инцидента:

kc.sh export --dir /tmp/kc-export --realm myrealm --users realm_file

Для полного восстановления после сбоя надёжнее иметь дамп самой БД, а не только экспорт realm — экспорт не всегда переносит все настройки один в один между версиями Keycloak:

pg_dump -U keycloak -d keycloak -F c -f keycloak_backup.dump
# восстановление
pg_restore -U keycloak -d keycloak --clean keycloak_backup.dump

Держите бэкапы вне того же сервера — если VPS выйдет из строя целиком, локальная копия дампа не поможет.

Нужен сервер под эту задачу?

Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.

Арендовать сервер

Нужны сами нейросети для контента?

Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.

Частые вопросы

Keycloak запускается, но админка отдаёт 403 или бесконечно грузится.

Чаще всего это несовпадение KC_HOSTNAME и реального адреса за прокси, либо блокировка WebSocket/keep-alive соединений на уровне nginx. Проверьте заголовки X-Forwarded-* и добавьте proxy_http_version 1.1; в конфиг nginx.

После обновления образа Keycloak realm пропали или клиенты не работают.

Между мажорными версиями (например, 24 → 26) меняется формат хранения некоторых сущностей. Всегда делайте дамп БД перед обновлением и проверяйте changelog конкретной версии на breaking changes, не полагаясь на автоматическую миграцию вслепую.

Можно ли обойтись без отдельного сервера под Keycloak и Postgres, и держать всё в одном контейнере?

Технически да через start-dev с встроенной H2, но это заведомо непригодно для продакшена — данные не переживут перезапуск контейнера, а H2 не рассчитана на конкурентную нагрузку. Для реального использования нужен отдельный Postgres минимум на том же сервере, а лучше — на отдельном.

Как понять, что дело именно в Keycloak, а не в приложении, которое к нему стучится?

Проверьте эндпоинт /realms/<realm>/.well-known/openid-configuration напрямую через curl — если он отвечает корректным JSON с нужными URL, Keycloak работает штатно, и проблему стоит искать в конфигурации client_id/client_secret на стороне приложения.

Нужен ли отдельный VPS под Keycloak, если приложений немного?

Для 1-2 небольших приложений можно держать Keycloak на том же сервере, что и сами приложения, если ресурсов достаточно. Но при росте нагрузки или количества realm разумнее вынести identity-провайдер отдельно — единая точка отказа для всей авторизации не должна конкурировать за CPU и память с прикладным кодом.

Обсудить статью, задать вопрос или начать новую тему

Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.

Перейти в сообщество →