Неполная цепочка SSL-сертификата: как найти и исправить incomplete chain
Неполная цепочка SSL-сертификата: как найти и исправить incomplete chain
Incomplete certificate chain — это ситуация, когда сервер отдаёт только собственный листовой сертификат, без промежуточных (intermediate). Настольные браузеры часто умеют «дотягивать» цепочку из своего кеша, но мобильные клиенты, старые библиотеки, curl, Java-приложения и API документацию-клиенты этого не делают — и отваливаются с ошибкой «unable to get local issuer certificate». В статье разберём, как диагностировать проблему и правильно собрать fullchain.
Почему цепочка важна
Цепочка доверия — это иерархия сертификатов от вашего листового до корневого CA, которому доверяет клиент. Обычно она выглядит так:
Root CA (встроен в trust store клиента)
└─ Intermediate CA 1
└─ Intermediate CA 2 (опционально)
└─ Leaf (ваш сертификат на example.com)
Клиент должен суметь дойти от вашего листового сертификата до какого-то корневого, которому он доверяет. Если сервер не отдаёт intermediate — клиент не может построить путь. Root CA в цепочке отправлять не обязательно (и не рекомендуется) — клиенты его и так знают.
Симптомы неполной цепочки
- Chrome на десктопе работает, Safari на iPhone — нет.
- curl падает с «unable to get local issuer certificate».
- Java-приложение:
PKIX path building failed. - Python requests:
[SSL: CERTIFICATE_VERIFY_FAILED]. - Telegram-бот не принимает webhook, хотя сертификат валиден.
- SSL Labs выдаёт оценку B с пометкой «Chain issues: Incomplete».
Диагностика
Первая проверка:
openssl s_client -connect example.com:443 -servername example.com -showcerts < /dev/null 2>/dev/null \
| grep "^-\|^ *s:\|^ *i:"
Правильный вывод должен содержать минимум две записи s: (subject) и i: (issuer):
s:CN=example.com i:C=US, O=Let's Encrypt, CN=R3 s:C=US, O=Let's Encrypt, CN=R3 i:C=US, O=Internet Security Research Group, CN=ISRG Root X1
Если есть только один блок s:/i: — цепочка неполная. Также протестируйте через SSL Checker enterno.io или SSL Labs — они чётко скажут «chain issues».
Исправление: собрать fullchain.pem
Для Let's Encrypt всё уже готово — используйте fullchain.pem, он уже содержит leaf + intermediate:
# Правильно
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
# Неправильно (только leaf)
ssl_certificate /etc/letsencrypt/live/example.com/cert.pem;
Для коммерческих CA вы получаете архив с несколькими файлами: cert.crt (ваш), intermediate.crt, иногда root.crt. Объедините leaf и intermediate в правильном порядке:
cat cert.crt intermediate.crt > fullchain.crt
# Ваш leaf — первым, intermediate — вторым
# root НЕ добавляйте
Если intermediate несколько, порядок: leaf → intermediate ближе к leaf → intermediate выше → и т.д. Перепутали порядок — клиент всё равно получит ошибку.
Конфигурация в nginx и Apache
nginx:
ssl_certificate /etc/nginx/certs/fullchain.pem;
ssl_certificate_key /etc/nginx/certs/privkey.pem;
Apache:
SSLCertificateFile /etc/apache2/certs/cert.crt
SSLCertificateKeyFile /etc/apache2/certs/privkey.key
SSLCertificateChainFile /etc/apache2/certs/intermediate.crt
# Или (Apache 2.4.8+) leaf+intermediate в одном файле:
SSLCertificateFile /etc/apache2/certs/fullchain.pem
После изменений: nginx -t && systemctl reload nginx / apachectl configtest && systemctl reload apache2.
Где взять intermediate, если его потеряли
Все современные CA публикуют intermediate-сертификаты. Способы получить:
- Сайт CA: у Let's Encrypt — letsencrypt.org/certificates, у Sectigo, DigiCert — аналогичные страницы.
- AIA (Authority Information Access): внутри сертификата есть URL на intermediate. Достанем его:
openssl x509 -in cert.crt -noout -text | grep "CA Issuers" # CA Issuers - URI:http://r3.i.lencr.org/ curl http://r3.i.lencr.org/ | openssl x509 -inform DER -out intermediate.pem - crt.sh: найдите свой сертификат на crt.sh, там будет ссылка на issuer.
Автоматизация и предотвращение
- Используйте certbot/acme.sh — они всегда генерируют
fullchain.pem. - В CI-пайплайне добавьте проверку:
openssl verify -untrusted intermediate.pem leaf.pem. - После деплоя делайте smoke-тест через curl без
-kс чистого Docker-образа (например,alpine:latest) — он не содержит локальных доверенных цепочек. - Настройте Enterno.io Monitors — он проверяет целостность цепочки при каждой проверке и предупреждает, если после renewal чейн развалился.
Связанные проблемы
Если исправили цепочку, но ошибки остались, посмотрите:
- Handshake failed — возможно, проблема в cipher suites или SNI.
- Expired certificate — проверьте сроки всех узлов цепочки (intermediate тоже имеют expiry).
- Устаревшие root CA у клиента — например, ISRG Root X1 был добавлен в Android только в версии 7.1.1; старые устройства нуждаются в cross-sign.
Часто задаваемые вопросы
Включать ли root CA в fullchain.pem?
Нет. Клиент и так имеет его в trust store. Отправка root увеличивает размер handshake и ничего не добавляет.
Что делать с cross-signed intermediate для старых Android?
Используйте «long chain» от Let's Encrypt, где intermediate подписан старым DST Root CA X3. По умолчанию certbot использует короткую цепочку с ISRG Root X1 — для Android < 7.1.1 нужно форсировать --preferred-chain "ISRG Root X1".
Почему Chrome работает, а Python requests — нет?
Chrome может дотянуть intermediate из кеша AIA Fetching. Python requests использует certifi bundle, который содержит только root. Нужен полный fullchain на сервере.
Как быстро проверить с мобильного?
Используйте онлайн-сервис или сторонний SSL/TLS проверку-проверщик, который не использует ваш десктопный кеш. SSL Checker enterno.io проверяет из своего окружения — это эквивалентно «чистому клиенту».
Заключение
Incomplete chain — тихая проблема: на десктопе не видно, а мобильные пользователи и API-интеграции ломаются. Правильное решение — один раз собрать корректный fullchain.pem и поставить автомониторинг целостности. SSL Checker от enterno.io проверит цепочку за 15 секунд, а Monitors будет делать это каждые 5 минут круглосуточно. Также см. ошибки даты сертификата и handshake failed.
Path validation — RFC 5280, §6. Let's Encrypt chains — letsencrypt.org/certificates. Тест — SSL Labs.
Проверьте ваш сайт прямо сейчас
Проверить →