Let's Encrypt: «не удалось получить сертификат издателя» (ошибка 2), принудительный повторный выпуск и лимиты запросов

При установке или пересборке Discourse валидный сертификат Let’s Encrypt может быть отклонён со следующей ошибкой:

C=US, O=Let's Encrypt, CN=YR2
error 2 at 1 depth lookup: unable to get issuer certificate
error fullchain.cer: verification failed

Я воспроизвёл эту проблему сразу после успешного выпуска RSA-сертификата. Сбой проверки приводит к ненужному запросу --force, что может вызвать HTTP 429 / rateLimited / «too many certificates». В моей установке выпуск ECDSA-сертификатов был заблокирован, и HTTPS не запустился.

При наличии затронутых цепочек каждый пересбор или перезапуск контейнера может вызывать ненужное принудительное повторное выпуск RSA- и ECDSA-сертификатов, даже если они всё ещё действительны. Успешные повторные выпуски расходуют квоту Let’s Encrypt, поэтому частые пересборки или перезапуски могут быстро привести к достижению лимита частоты.

Эта проблема может оставаться незамеченной, пока HTTPS продолжает работать с уже установленными сертификатами. Удаление и переустановка экземпляра с тем же именем хоста не сбрасывает эту квоту. Если необходимые сертификаты отсутствуют в новой установке, ограничение частоты выпуска может помешать запуску HTTPS.

Исправление

В файле templates/web.letsencrypt.ssl.template.yml, внутри функции cert_exists(), замените:

- openssl verify -CAfile <(openssl x509 -in ca.cer) fullchain.cer
+ openssl verify -untrusted ca.cer fullchain.cer

Остальную часть функции оставьте без изменений. Исправленный шаблон должен быть включён в следующую пересборку контейнера для обновления /usr/local/bin/letsencrypt.

Если вы уже достигли лимита частоты, патч не сбрасывает квоту. Примените исправление и соблюдайте время retry after перед попытками дальнейшего выпуска.

PR: FIX: preserve the Let's Encrypt issuer chain in cert_exists - Pull Request #1136 - discourse/discourse_docker - GitHub

Подтверждение проблемы с проверкой цепочки

Из каталога затронутого сертификата внутри контейнера (/shared/letsencrypt/<hostname> или <hostname>_ecc) сравните результаты:

openssl verify -CAfile <(openssl x509 -in ca.cer) fullchain.cer
openssl verify -untrusted ca.cer fullchain.cer

В воспроизведённом случае первая команда завершается ошибкой 2, а вторая возвращает:

fullchain.cer: OK

Причина и валидация

Команда openssl x509 -in ca.cer читает только первый сертификат из пакета эмитента. В наблюдаемой цепочке YR2 это приводит к потере перекрёстно подписанного Root YR, необходимого для достижения ISRG Root X1.

Параметр -untrusted ca.cer предоставляет полный пакет эмитента, сохраняя доверенные якоря в системном хранилище, что сохраняет намерение исправления 2021 года (#576).

Я проверил фактические цепочки RSA и ECDSA внутри контейнера. Полная пересборка с патчем переиспользовала оба существующих сертификата: два сообщения Skip, обе цепочки установлены, и nginx -t прошёл успешно.

Связанные отчёты

411097
413078

PR: FIX: preserve the Let's Encrypt issuer chain in cert_exists - Pull Request #1136 - discourse/discourse_docker - GitHub

6 лайков

Пожалуйста, разместите здесь ваш корректный сертификат и цепочку, чтобы мы могли их оценить.

3 лайка

Спасибо @supermathie за то, что разобрались в этом вопросе. Я приложил fullchain.cer и ca.cer с затронутой инстанции.
fullchain.cer.txt (5.9 КБ)
ca.cer.txt (3.8 КБ)

Вывод OpenSSL ниже показывает, что исходная команда проверки завершается ошибкой 2, а использование -untrusted с тем же сертификатом и цепочкой проходит успешно.

openssl version
openssl verify -CAfile <(openssl x509 -in ca.cer) fullchain.cer
openssl verify -show_chain -untrusted ca.cer fullchain.cer

OpenSSL 3.5.7 9 Jun 2026 (Library: OpenSSL 3.5.7 9 Jun 2026)
C=US, O=Let's Encrypt, CN=YR2
error 2 at 1 depth lookup: unable to get issuer certificate
error fullchain.cer: verification failed
fullchain.cer: OK
Chain:
depth=0: CN=hubprod.surikai.fr (untrusted)
depth=1: C=US, O=Let's Encrypt, CN=YR2 (untrusted)
depth=2: C=US, O=ISRG, CN=Root YR (untrusted)
depth=3: C=US, O=Internet Security Research Group, CN=ISRG Root X1

Я столкнулся с этой проблемой в двух разных средах. Я применяю исправление на своей тестовой инстанции с момента создания PR, и оно работает как ожидается.

2 лайка

Для справки, клиент ACME создаёт следующие相关文件 в ${LETSENCRYPT_DIR}:

  • ${DISCOURSE_HOSTNAME}.cer: содержит выданный сертификат
  • ca.cer: содержит цепочку сертификатов и, что несколько запутанно, так называется
  • fullchain.cer: объединяет два предыдущих файла

Наиболее корректной командой будет:

openssl verify -untrusted ca.cer ${DISCOURSE_HOSTNAME}.cer

Текущая команда — это хаос (и она неверна, поскольку на самом деле не проверяет, что цепочка валидна до корневого сертификата системы), а любой другой вариант опирается на побочные эффекты или допущения. Я обновлю PR.

2 лайка

Логично. Прямая проверка листового сертификата — более чистое решение, чем полагаться на то, что OpenSSL читает только первый сертификат из fullchain.cer.

Я закрою PR #1136 в пользу вашего.

Буду рад протестировать новый PR, если это необходимо.

1 лайк

Слияние выполнено :partying_face:

Спасибо @jmx за чёткое выявление проблемы и предложение исправления — оно было хорошим, я просто хотел сделать чуть больше.

3 лайка

Эта тема была автоматически закрыта через 14 часов. Новые ответы больше не разрешены.