ERR_CERT_AUTHORITY_INVALID в браузере и openssl verify error в консоли — это не «магия TLS», а конкретная история про доверие: клиент строит цепочку сертификатов до корневого УЦ (Root CA) из своего trust store. Если цепочку построить не получается, появляются ошибки вроде unable to get local issuer certificate или self signed certificate in certificate chain.
Ниже — практический чек-лист: как быстро понять, что именно сломано (серверная цепочка или клиентское хранилище доверенных CA), как правильно собрать fullchain.pem, и как указать его в nginx ssl_certificate или apache SSLCertificateFile.
Как устроено доверие TLS: trust store, цепочка и кто что обязан отдавать
Упрощенно TLS-проверка выглядит так:
- Сервер отправляет «листовой» сертификат домена (Leaf) и обычно — один или несколько промежуточных (intermediate certificate).
- Клиент строит цепочку: Leaf → Intermediate → (возможен еще Intermediate) → Root.
- Root сертификаты сервер почти никогда не отправляет: они уже лежат у клиента в trust store (системный пакет
ca-certificates, хранилище Windows/macOS, Android, Firefox и т. п.).
Критическая деталь: сервер обязан отдавать промежуточные сертификаты. Если вы отдали только Leaf, часть клиентов «достроит» цепочку (из кэша intermediates), а часть — нет. Отсюда классический эффект «у меня открывается, у пользователей падает».
Типовые симптомы и что они обычно значат
Ориентируйтесь на формулировку:
ERR_CERT_AUTHORITY_INVALID(Chrome/Edge): чаще всего неполная цепочка или корпоративный/антивирусный MITM, подменяющий сертификат.unable to get local issuer certificate(OpenSSL): клиент не нашел issuer (обычно intermediate) в присланной цепочке и не смог найти его локально.self signed certificate in certificate chain: сервер отдает самоподписанный сертификат вместо нормального intermediate, либо в цепочку попал «чужой» root.certificate has expired: истек Leaf или intermediate (промежуточные тоже истекают).hostname mismatch: сертификат не на тот домен (CN/SAN), это отдельный класс проблем (не trust store).
Быстрая диагностика: что видит клиент и что реально отдает сервер
Начинайте с проверки того, что реально уходит по сети. Самый быстрый инструмент — openssl s_client.
1) Смотрим цепочку, которую присылает сервер
openssl s_client -connect example.com:443 -servername example.com -showcerts
Что смотреть в выводе:
- В блоке
Certificate chainдолжно быть несколько элементов: минимум Leaf и intermediate. - Если chain состоит из одного сертификата — вы почти наверняка отдаете не
fullchain.pem. - Строка
Verify return codeпоказывает результат на стороне именно этой машины с ее trust store (на другом клиенте результат может отличаться).
2) Проверяем локально: собирается ли цепочка при верификации
Если у вас есть файлы сертификатов на сервере, удобно проверить цепочку локально:
openssl verify -CAfile root-ca.pem -untrusted intermediate.pem cert.pem
Где:
cert.pem— листовой сертификат доменаintermediate.pem— промежуточный сертификат (или несколько, если склеить их в один файл)root-ca.pem— корневой (для теста можно указать явно, хотя обычно он уже есть в системе)
Если видите unable to get local issuer certificate, OpenSSL не смог построить цепь: intermediate не тот, не хватает звена, неверный порядок, или файл вообще не PEM.
3) Уточняем, какой именно issuer нужен
Проверьте Subject/Issuer у Leaf:
openssl x509 -in cert.pem -noout -subject -issuer
Issuer Leaf должен совпасть с Subject нужного intermediate. Если не совпадает — вы пытаетесь «пристегнуть» чужой intermediate (частая ошибка при ручной сборке).

Правильная сборка fullchain.pem: порядок, формат и частые ловушки
fullchain.pem — это файл, где сертификаты идут «сверху вниз»:
- сначала Leaf (сертификат домена)
- затем intermediate certificate (иногда несколько)
Root обычно не добавляют в fullchain.pem для веб-сервера. Отправка root редко помогает и иногда создает проблемы, когда клиент «выбирает» не тот trust anchor.
Собрать fullchain вручную
Если УЦ выдал отдельные файлы, соберите так:
cat cert.pem intermediate.pem > fullchain.pem
Проверьте, что в fullchain.pem действительно два (или больше) PEM-блока BEGIN CERTIFICATE.
Проверить, что fullchain соответствует приватному ключу
Частый сценарий после перевыпуска: сертификат новый, а ключ в конфиге старый. Тогда TLS может не подняться вовсе или будет отдаваться другой vhost/сертификат.
openssl x509 -in cert.pem -noout -modulus | openssl md5
openssl rsa -in privkey.pem -noout -modulus | openssl md5
Хэши должны совпасть. Для ECDSA ключей используйте проверку по публичному ключу:
openssl pkey -in privkey.pem -pubout -outform pem | openssl md5
openssl x509 -in cert.pem -pubkey -noout | openssl md5
Ловушка: “bundle” не тот или не полный
Разные УЦ и панели называют файлы по-разному: ca-bundle.pem, chain.pem, intermediate.pem. Важно не название, а содержимое и соответствие Issuer/Subject.
Правило: цепочка для сервера должна соответствовать Issuer вашего Leaf. Не подбирайте intermediate «по бренду УЦ» — подбирайте по совпадению Issuer/Subject.
Настройка Nginx: ssl_certificate и почему нельзя подсовывать только cert.pem
В Nginx в параметре ssl_certificate должен быть файл с цепочкой (то есть ваш fullchain.pem), а ssl_certificate_key — приватный ключ.
server {
listen 443 ssl;
server_name example.com;
ssl_certificate /etc/ssl/example/fullchain.pem;
ssl_certificate_key /etc/ssl/example/privkey.pem;
}
Проверка конфигурации и перезагрузка:
nginx -t
systemctl reload nginx
Если после исправления цепочки браузер все равно ругается, проверьте, не отдается ли другой сертификат на этом же IP:443 (SNI/дефолтный vhost). Здесь ключевой момент — всегда указывать -servername в openssl s_client.
Настройка Apache: SSLCertificateFile, SSLCertificateChainFile и нюансы версий
В Apache исторически использовались разные подходы:
- в новых версиях чаще кладут fullchain в
SSLCertificateFile - в старых конфигурациях intermediate указывали отдельно через
SSLCertificateChainFile
Практичный вариант (одним файлом):
<VirtualHost *:443>
ServerName example.com
SSLEngine on
SSLCertificateFile /etc/ssl/example/fullchain.pem
SSLCertificateKeyFile /etc/ssl/example/privkey.pem
</VirtualHost>
Проверка и reload:
apachectl configtest
systemctl reload apache2
Если у вас legacy-конфиг с SSLCertificateChainFile, следите, чтобы intermediate не дублировался: когда fullchain уже включает chain, отдельное указание chain иногда приводит к «лишним» сертификатам в цепочке.
Когда виноват не сервер: trust store клиента и обновление ca-certificates
Бывает обратная ситуация: сервер отдает корректную цепочку, но конкретная система все равно не доверяет УЦ. Типичные причины:
- очень старая ОС или контейнерный образ с древним пакетом CA
- минималистичный образ без набора CA
- корпоративная прокси/антивирус подменяет сертификаты, а его корневой сертификат не установлен в систему/браузер
Debian/Ubuntu: обновить trust store
apt-get update
apt-get install -y ca-certificates
update-ca-certificates
Это и есть тот самый ca-certificates update сценарий, который часто решает unable to get local issuer certificate в утилитах (curl, git, языковые рантаймы) внутри контейнеров и на старых серверах.
RHEL/Alma/Rocky/CentOS: актуализировать CA
yum install -y ca-certificates
update-ca-trust
Контейнеры и CI
Если ошибка проявляется только в CI, Docker build или в рантайме приложения, проверьте базовый образ. Часто лечится установкой ca-certificates и обновлением хранилища внутри образа. Если используется внутренний корпоративный CA, его сертификат нужно добавлять в trust store явно: одна серверная цепочка это не «починит».
По смежной теме (HSTS и выпуск/обновление сертификатов без сюрпризов) полезно держать под рукой разбор: HTTPS, Certbot и HSTS: типовые ошибки внедрения и проверки.

Мини-рунбук: как локализовать причину за 10 минут
Посмотрите, что отдает сервер:
openssl s_client -connect example.com:443 -servername example.com -showcertsЕсли в chain только один сертификат — соберите
fullchain.pemи укажите его в веб-сервере.Если chain выглядит полным, но конкретная машина падает с
unable to get local issuer certificate— обновляйтеca-certificatesна этой машине/в контейнере.Проверьте соответствие ключа и сертификата (modulus/public key).
Если проблема «плавающая», проверьте SNI и дефолтный vhost: часть запросов может попадать в другой конфиг/контейнер/балансер.
Частые ошибки, из-за которых ERR_CERT_AUTHORITY_INVALID возвращается снова
Перепутали файлы местами: в
ssl_certificateположили intermediate без leaf, или наоборот.Загрузили «не тот» chain: intermediate не соответствует issuer leaf.
Дублирование цепочки: fullchain уже содержит intermediate, но вы добавили его еще раз отдельной директивой/вторым файлом.
Не перезагрузили прокси-слой: CDN/балансер/ingress продолжает отдавать старую цепочку.
Старый trust store: особенно в контейнерах и минимальных образах.
Что считать «правильным результатом»
После исправлений вы должны увидеть:
- в
openssl s_client— цепочку минимум из Leaf + intermediate - в конце —
Verify return code: 0 (ok)на машине с актуальным trust store - в браузере — отсутствие
ERR_CERT_AUTHORITY_INVALIDи корректно определяемый Issuer
Если на одном устройстве все ок, а на другом нет — почти всегда это различие trust store или «вмешивающаяся» прокси. Тогда фокус смещается с сервера на клиентскую инфраструктуру.
Короткий вывод
Для веб-сервера главное — отдавать полную certificate chain (через fullchain.pem), а для клиентов — иметь актуальный trust store (ca-certificates). Свяжите эти две части диагностикой через openssl s_client и openssl verify, и ошибки вида ERR_CERT_AUTHORITY_INVALID и unable to get local issuer certificate перестанут быть загадкой, превращаясь в повторяемый рутинный сценарий исправления.


