Зачем вообще проверять цепочку: что такое SSL certificate chain
Когда пользователь открывает сайт по HTTPS, сервер отдаёт не только «свой» сертификат (leaf), но и промежуточные сертификаты — intermediate certificate. В идеале клиент (браузер, бот, API) должен суметь построить цепочку доверия до корневого сертификата (root), который уже лежит в системном хранилище доверия — trust store.
Если цепочка отдана неверно или неполно, часть клиентов увидит ошибки уровня «не доверяем», «не удалось проверить подпись», «unknown CA». При этом на одном компьютере «всё работает», а на другом — нет. Типичная причина: у «работающего» клиента промежуточник уже закэширован или установлен, а у «ломающегося» — нет.
Практически это сводится к двум вопросам:
- Что именно отдаёт сервер в TLS-рукопожатии (leaf + intermediates)?
- Может ли конкретный клиент (с конкретным trust store) проверить эту цепочку без «магии»?
Какие сертификаты где должны быть: leaf, intermediate, root и fullchain
Упростим картину:
- Leaf-сертификат — сертификат домена, который вы выпустили (например, для
example.com). - Intermediate certificate — один или несколько сертификатов, которыми подписан leaf.
- Root — корневой сертификат УЦ, которому доверяет ОС/браузер; его сервер обычно не отправляет.
На веб-сервере чаще всего требуется «пакет», который называют fullchain или bundle. Обычно это файл, где сначала идёт leaf, затем промежуточные сертификаты (в правильном порядке). Для большинства случаев: fullchain = leaf + intermediates, без root.
Важно: слово «fullchain» в панелях и инструкциях иногда трактуется по-разному. Поэтому диагностируем не название файла, а фактическое содержимое TLS-ответа сервера.
Если у вас коммерческий сертификат и нужна «предсказуемая» совместимость с корпоративными клиентами, проще использовать проверенные цепочки от УЦ и следить за актуальностью. Для таких задач уместны SSL-сертификаты с понятной цепочкой и поддержкой.

Быстрая проверка с openssl s_client: базовый сценарий
Основной инструмент для диагностики на практике — openssl s_client. Он показывает, какой сертификат и какие промежуточники реально отдал сервер, и чем закончилась проверка.
Минимальная команда для HTTPS (порт 443):
openssl s_client -connect example.com:443 -servername example.com -showcerts
Ключевые моменты:
-connect— куда подключаемся.-servername— включаем SNI (Server Name Indication). Без SNI на многодоменных серверах вы можете увидеть «не тот» сертификат и сделать неверные выводы.-showcerts— печатает всю цепочку сертификатов, которую сервер прислал в рукопожатии.
Как читать вывод: полезные строки
В выводе вас обычно интересуют:
subject=— кому выдан сертификат (leaf или intermediate, в зависимости от блока).issuer=— кем подписан.Certificate chain— список элементов цепочки, которые сервер отдал.Verify return code— итог встроенной проверки OpenSSL (важно: она зависит от доверенных корней на машине, где вы запускаете команду).
Если хочется расширить диагностику (протоколы, шифры, OCSP stapling, алерты рукопожатия), держите под рукой отдельный разбор: диагностика TLS через OpenSSL и testssl.
Verify return code: что означает и почему «на сервере всё ок» не аргумент
verify return code — это краткий диагноз OpenSSL. Он говорит не только о проблемах сертификата, но и о том, смог ли клиент построить доверенную цепочку с использованием своего trust store.
Самые частые варианты:
Verify return code: 0 (ok)— цепочка построена и доверена.unable to get local issuer certificate— сервер не отдал нужный intermediate certificate, а в локальном trust store клиента его нет.unable to verify the first certificate— часто то же самое, но формулировка чуть иная: leaf не удалось связать с доверенной цепочкой.self signed certificateилиself signed certificate in certificate chain— вы используете самоподписанный сертификат или в цепочке внезапно оказался «самоподписанный» промежуточник или корень.certificate has expired— просрочка (иногда просрочен именно intermediate).
Один и тот же сервер может давать
Verify return code: 0 (ok)на вашей админской машине и ошибку на устройстве клиента. Чаще всего это различия trust store или кеширование промежуточников у «счастливых» клиентов.
Проверка именно цепочки сервера (без влияния локального trust store)
openssl s_client по умолчанию пытается проверить цепочку на основе системных корней вашей машины. Чтобы отделить «что отдал сервер» от «что доверено локально», используйте комбинированный подход:
- Сначала достаньте цепочку, которую отдаёт сервер (через
-showcerts). - Потом проверьте её командой
openssl verifyс явно указанными промежуточниками.
Шаг 1: сохранить сертификаты из вывода s_client
Вывод -showcerts включает несколько блоков -----BEGIN CERTIFICATE-----…-----END CERTIFICATE-----. Их можно скопировать в файлы вручную:
leaf.pem— сертификат доменаintermediate.pem— промежуточник(и)
Если промежуточников несколько, их обычно складывают в один файл в порядке «ближе к leaf» → «выше по цепочке».
Шаг 2: проверка цепочки через openssl verify
Пример проверки leaf с промежуточниками:
openssl verify -untrusted intermediate.pem leaf.pem
Если intermediate-ов несколько:
cat inter1.pem inter2.pem > intermediates.pem
openssl verify -untrusted intermediates.pem leaf.pem
Обратите внимание: openssl verify всё равно будет опираться на корни вашей системы (trust store). Но теперь вы точно знаете, что «недостающее звено» — не на стороне сервера (если вы его явно положили в -untrusted), а в корнях клиента.
SNI: почему без него диагностика часто ложная
На одном IP-адресе может жить десятки сайтов, и TLS-сертификат выбирается по SNI. Если вы запускаете:
openssl s_client -connect 203.0.113.10:443 -showcerts
то сервер может отдать дефолтный сертификат, который вообще не относится к вашему домену. В результате вы увидите «несовпадение CN/SAN», странную цепочку и ошибочный verify return code.
Правильный способ — всегда добавлять -servername с доменным именем:
openssl s_client -connect 203.0.113.10:443 -servername example.com -showcerts
Если вы диагностируете нестандартный порт (например, 8443), принцип тот же.
Как понять, какой intermediate certificate действительно нужен
Типовой сценарий ошибки: администратор устанавливает только leaf, думая, что «остальное есть у браузеров». Иногда так действительно «кажется», пока не придёт клиент с чистым trust store или старой системой.
Алгоритм, чтобы понять «чего не хватает»:
- В выводе s_client найдите блок leaf и посмотрите его
issuer— это тот, кто подписал leaf (обычно intermediate). - Проверьте, есть ли в
Certificate chainсертификат сsubject, соответствующим этомуissuer. - Если такого нет — сервер не отдал нужный intermediate certificate.
В корректной цепочке каждый следующий сертификат «объясняет» предыдущий: leaf подписан intermediate-ом, intermediate подписан более высоким CA, и так до root.

Частые проблемы с fullchain: порядок, лишний root и «не тот» бандл
1) Неполная цепочка (сервер отдал только leaf)
Симптомы: unable to get local issuer certificate или unable to verify the first certificate. Исправление: на сервере должен быть настроен fullchain (leaf + intermediates).
2) Неправильный порядок в fullchain
Иногда в файл кладут промежуточники в обратном порядке или вставляют «не относящийся» intermediate. Результат может быть плавающим: часть клиентов построит цепочку альтернативным путём, часть — нет.
Практический совет: держите порядок «leaf → intermediate(ы) → выше по цепочке», без корня.
3) Добавили root в fullchain
Обычно это не нужно. У некоторых клиентов лишний root в цепочке может вызывать предупреждения или странные ошибки валидации. Правильнее: root должен быть в trust store клиента, а не в том, что отправляет сервер.
4) Несовпадение сертификата и ключа
Это уже не про цепочку, но часто всплывает рядом при замене сертификатов: сервер может не поднять конфиг или продолжить отдавать старый сертификат. Проверяйте, что на диске и в конфиге подключены правильные файлы, и что перезагрузка сервиса действительно прошла.
Trust store: почему старые системы ломаются чаще
Trust store — это набор корневых сертификатов, которым доверяет система или приложение. Он отличается:
- между разными ОС (Linux-дистрибутивы, Windows, macOS);
- между версиями одной ОС (особенно на старых серверах и в контейнерах);
- между приложениями (Java часто живёт со своим хранилищем; некоторые сборки curl/OpenSSL — тоже).
Поэтому корректная server-side практика простая: сервер всегда должен отдавать полноценную цепочку intermediates. Тогда клиенту нужно только иметь актуальные корни.
Если вы переносите проект или поднимаете новый стек, удобнее сразу закладывать контроль сертификатов и цепочек на уровне окружения. На VDS это проще автоматизировать (проверки в CI, единые образы, одинаковый trust store на нодах) и воспроизводимо повторять при масштабировании.
Практика: короткий чек-лист, когда «сертификат вроде установлен, но ругается»
- Проверить, что используете SNI:
-servernameобязателен. - Посмотреть
Certificate chainи убедиться, что там есть intermediate certificate. - Сверить
issuerleaf иsubjectближайшего intermediate. - Оценить
Verify return codeи помнить, что он зависит от trust store машины. - Если проблема только у части клиентов — почти наверняка неполный или неверный fullchain или устаревший trust store у клиентов.
Небольшой набор команд для диагностики «на проде»
Посмотреть цепочку и итог проверки
openssl s_client -connect example.com:443 -servername example.com -showcerts
Проверить только факт выдачи сертификата и код в конце (удобно в логах)
openssl s_client -connect example.com:443 -servername example.com </dev/null
Проверить локальные файлы leaf и intermediates
openssl verify -untrusted intermediates.pem leaf.pem
Что считать «правильным результатом»
Для типового веб-сайта правильная картина такая:
- в
Certificate chainприсутствует leaf и как минимум один intermediate certificate; - в конце:
Verify return code: 0 (ok)на машине с актуальным trust store; - при подключении по IP с
-servernameпоказывается тот же сертификат, что и при подключении по доменному имени.
Если что-то из этого не сходится, вы почти всегда сможете локализовать проблему: либо сервер отдаёт не ту цепочку, либо клиентская среда не доверяет нужным корням.
Итог
openssl s_client — самый быстрый способ глазами увидеть, что сервер реально отдаёт в TLS-рукопожатии, и понять, где ломается построение цепочки. На практике большинство «странных» HTTPS-ошибок сводится к трём вещам: забыли intermediate certificate, собрали неверный fullchain, или проверяют без SNI и диагностируют не тот виртуальный хост.
Держите цепочку на сервере полной (без root), проверяйте с -servername, и воспринимайте verify return code как подсказку в контексте trust store конкретного клиента — тогда поиск причины займёт минуты, а не часы.


