У SSL-сертификата есть неприятная особенность: «у меня в браузере зелёный замок» ещё не означает, что всё настроено правильно. Часть клиентов может видеть сайт без ошибок, а часть — получать certificate verify failed: старые библиотеки, Java-приложения, мобильные клиенты, curl в контейнерах, почтовые роботы, платёжные шлюзы и мониторинги.
Часто причина не в самом доменном сертификате, а в неполной цепочке: сервер отдал только leaf-сертификат, но не отдал intermediate certificate. В этой инструкции разберём, что такое ssl chain, чем отличаются cert.pem, chain.pem и fullchain.pem, как настроить nginx ssl и apache ssl, зачем нужен ocsp stapling и как проверять всё через openssl s_client.
Материал можно использовать как чек-лист при запуске нового сертификата, после продления или при разборе инцидента. Если вы только планируете выпуск сертификата для сайта, обратите внимание на SSL-сертификаты: дальше всё равно придётся правильно установить не только сам сертификат, но и цепочку.
Коротко: из чего состоит цепочка SSL
Обычный публичный TLS-сертификат сайта не подписан напрямую корневым центром сертификации. Между вашим сертификатом и корнем почти всегда есть один или несколько промежуточных сертификатов — intermediate certificate. Клиент должен построить путь доверия: доменный сертификат → промежуточный сертификат → корневой сертификат, который уже есть в доверенном хранилище операционной системы или приложения.
Важный момент: корневой сертификат обычно не нужно отдавать с сайта. Он должен быть у клиента в trust store. Сервер обязан отдать свой доменный сертификат и все промежуточные сертификаты, которых клиенту может не хватать. Именно этот набор часто называют fullchain или bundle.
Правильный TLS-бандл для веб-сервера — это leaf-сертификат сайта плюс intermediate-сертификаты, расположенные в корректном порядке. Корневой сертификат в большинстве случаев в файл для Nginx или Apache не добавляют.
Типовые файлы после выпуска сертификата могут называться по-разному, но смысл обычно такой:
privkey.pem— приватный ключ. Его нельзя отдавать клиентам, нельзя класть в публичные репозитории и нельзя пересылать в чатах.cert.pem— сертификат конкретного домена, его ещё называют leaf или end-entity certificate.chain.pem— цепочка промежуточных сертификатов без leaf.fullchain.pem— leaf плюс chain; именно этот файл чаще всего нужен директивамssl_certificateв Nginx иSSLCertificateFileв современном Apache.
Если сертификат получен от коммерческого УЦ, в панели или письме могут быть отдельные файлы: доменный сертификат, intermediate CA, иногда CA bundle. Для Nginx и Apache важно не название, а содержимое и порядок PEM-блоков.
Как должен выглядеть PEM-файл fullchain
PEM-файл — это текстовый файл с одним или несколькими блоками сертификатов. Для fullchain.pem первым должен идти сертификат сайта, затем промежуточные сертификаты от ближайшего к сайту к более верхнему. Пример структуры:
-----BEGIN CERTIFICATE-----
сертификат сайта example.com
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
intermediate certificate, который подписал сертификат сайта
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
следующий intermediate certificate, если он есть
-----END CERTIFICATE-----
Порядок важен. Некоторые клиенты умеют перестраивать цепочку сами, но полагаться на это не стоит. Чем строже клиентская библиотека, тем выше шанс получить ошибку проверки, если сервер отдаёт цепочку в неправильном порядке или не отдаёт её полностью.
Проверить, сколько сертификатов лежит в файле, можно так:
grep -c "BEGIN CERTIFICATE" /etc/ssl/example.com/fullchain.pem
Посмотреть subject и issuer каждого сертификата удобнее через awk и openssl. Команда ниже разбивает bundle на отдельные файлы во временном каталоге:
mkdir -p /tmp/cert-split
awk 'BEGIN {n=0} /BEGIN CERTIFICATE/ {n++} {print > "/tmp/cert-split/cert" n ".pem"}' /etc/ssl/example.com/fullchain.pem
for f in /tmp/cert-split/cert*.pem; do echo "=== $f ==="; openssl x509 -in "$f" -noout -subject -issuer -dates; done
В выводе у первого сертификата subject должен соответствовать вашему домену или wildcard/SAN-набору, а issuer — следующему сертификату в цепочке. У следующего сертификата subject обычно совпадает с предыдущим issuer.

Настройка SSL chain в Nginx
В Nginx цепочка задаётся директивой ssl_certificate. Для публичного сайта почти всегда указываем именно fullchain.pem, а не одинокий cert.pem. Приватный ключ указывается отдельно через ssl_certificate_key.
server {
listen 443 ssl http2;
server_name example.com www.example.com;
ssl_certificate /etc/ssl/example.com/fullchain.pem;
ssl_certificate_key /etc/ssl/example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
root /var/www/example.com/public;
index index.html index.php;
}
Если в ssl_certificate случайно указан только доменный сертификат, браузеры на десктопе могут всё равно открыть сайт: они иногда подтягивают недостающий intermediate из кэша или по AIA. Но серверные клиенты, контейнеры и минимальные окружения часто не делают этого. Поэтому «открывается в Chrome» — не достаточная проверка.
Перед применением конфигурации проверьте синтаксис и перезагрузите Nginx:
nginx -t
systemctl reload nginx
На системах без systemd используйте штатный сервисный механизм. Например, во FreeBSD это обычно:
service nginx configtest
service nginx reload
Если Nginx не стартует с ошибкой про ключ, проверьте, что сертификат и ключ действительно пара. Это частая проблема после ручной замены файлов:
openssl x509 -noout -modulus -in /etc/ssl/example.com/fullchain.pem | openssl md5
openssl rsa -noout -modulus -in /etc/ssl/example.com/privkey.pem | openssl md5
Хэши должны совпасть. Для ECDSA-ключей команда с rsa не подойдёт; универсальнее сравнивать публичные ключи:
openssl x509 -in /etc/ssl/example.com/fullchain.pem -pubkey -noout | openssl pkey -pubin -outform DER | openssl sha256
openssl pkey -in /etc/ssl/example.com/privkey.pem -pubout -outform DER | openssl sha256
Настройка SSL chain в Apache
В Apache 2.4.8 и новее цепочку можно класть в тот же файл, который указан в SSLCertificateFile. Это похоже на подход Nginx: используем fullchain.pem и отдельный приватный ключ в SSLCertificateKeyFile.
<VirtualHost *:443>
ServerName example.com
ServerAlias www.example.com
DocumentRoot /var/www/example.com/public
SSLEngine on
SSLCertificateFile /etc/ssl/example.com/fullchain.pem
SSLCertificateKeyFile /etc/ssl/example.com/privkey.pem
Protocols h2 http/1.1
</VirtualHost>
В старых конфигурациях можно встретить отдельную директиву SSLCertificateChainFile. На современных Apache она обычно не нужна, а в некоторых сборках считается устаревшей. Если вы поддерживаете старый сервер, проверьте версию Apache и документацию конкретного пакета, но для актуальных Debian/Ubuntu, AlmaLinux/Rocky Linux/CentOS Stream/Oracle Linux и Fedora правильнее хранить leaf и intermediate в fullchain.pem.
Проверка и перезагрузка зависят от семейства ОС.
Debian/Ubuntu
apache2ctl configtest
systemctl reload apache2
RHEL-based: AlmaLinux, Rocky Linux, CentOS Stream, Oracle Linux
apachectl configtest
systemctl reload httpd
Fedora
apachectl configtest
systemctl reload httpd
FreeBSD
apachectl configtest
service apache24 reload
Если Apache пишет, что не может прочитать файл ключа или сертификата, проверьте права доступа. Приватный ключ должен быть доступен процессу старта веб-сервера, но не должен быть читаем всеми пользователями. Типовой вариант — владелец root, права 600 или 640 с аккуратно выбранной группой.
OCSP Stapling: зачем он нужен и что реально ускоряет
ocsp stapling — это механизм, при котором веб-сервер заранее получает у центра сертификации подписанный ответ о статусе сертификата и «прикрепляет» его к TLS-handshake. Клиенту не нужно самому идти к OCSP-responder УЦ, а значит меньше задержек и меньше зависимость от доступности внешнего сервиса проверки.
OCSP отвечает на вопрос: не отозван ли сертификат. Это не замена цепочке сертификатов. Если ssl chain сломан, включение stapling не исправит certificate verify failed. Сначала настраиваем корректный fullchain, затем включаем OCSP Stapling.
Для stapling веб-серверу обычно нужен не только файл, который он отдаёт клиенту, но и доверенная цепочка для проверки OCSP-ответа. В Nginx за это отвечает ssl_trusted_certificate. В Apache — настройки SSLUseStapling и кэш stapling.
Включаем OCSP Stapling в Nginx
Минимальная рабочая конфигурация для Nginx выглядит так:
server {
listen 443 ssl http2;
server_name example.com www.example.com;
ssl_certificate /etc/ssl/example.com/fullchain.pem;
ssl_certificate_key /etc/ssl/example.com/privkey.pem;
ssl_trusted_certificate /etc/ssl/example.com/chain.pem;
ssl_stapling on;
ssl_stapling_verify on;
resolver 127.0.0.53 valid=300s ipv6=off;
resolver_timeout 5s;
}
Здесь есть несколько нюансов. В ssl_certificate указываем fullchain.pem, потому что этот файл отправляется клиенту. В ssl_trusted_certificate часто указывают chain.pem — промежуточные сертификаты, которые Nginx использует для проверки OCSP-ответа. На практике допустимо указать и bundle с цепочкой, если он корректный, но не нужно подставлять туда приватный ключ или случайный набор CA.
Директива resolver нужна, чтобы Nginx мог разрешить имя OCSP-responder из сертификата. Если сервер использует локальный DNS-резолвер, укажите его адрес. На Debian/Ubuntu с systemd-resolved часто встречается 127.0.0.53, но в контейнерах и минимальных системах адрес может быть другим. Проверьте /etc/resolv.conf и сетевую схему сервера.
cat /etc/resolv.conf
nginx -t
systemctl reload nginx
После включения stapling первый запрос может ещё не показать прикреплённый OCSP-ответ: Nginx должен сходить к responder и закэшировать ответ. Обычно достаточно подождать несколько секунд и повторить проверку.
Включаем OCSP Stapling в Apache
В Apache stapling включается на уровне SSL-конфигурации. Обязательно настройте кэш, иначе stapling будет работать нестабильно или Apache выдаст предупреждение.
SSLUseStapling on
SSLStaplingCache shmcb:/var/run/apache2/stapling_cache(128000)
Для Debian/Ubuntu путь /var/run/apache2 обычно подходит. В RHEL-based системах логичнее использовать каталог Apache runtime:
SSLUseStapling on
SSLStaplingCache shmcb:/run/httpd/stapling_cache(128000)
Во VirtualHost остаётся обычная SSL-настройка с fullchain.pem:
<VirtualHost *:443>
ServerName example.com
ServerAlias www.example.com
SSLEngine on
SSLCertificateFile /etc/ssl/example.com/fullchain.pem
SSLCertificateKeyFile /etc/ssl/example.com/privkey.pem
SSLUseStapling on
</VirtualHost>
Если на сервере много виртуальных хостов, удобнее включать SSLUseStapling глобально в SSL-конфиге, а не копировать в каждый vhost. Но следите, чтобы все сертификаты имели корректную цепочку и чтобы сервер мог выполнять исходящие DNS- и HTTP-запросы к OCSP-responder УЦ.

Проверка через openssl s_client
openssl s_client — главный инструмент для быстрой проверки TLS с точки зрения клиента. Для сайтов с SNI обязательно указывайте -servername, иначе сервер может вернуть сертификат дефолтного vhost, и вы начнёте отлаживать не тот домен.
openssl s_client -connect example.com:443 -servername example.com -showcerts
В выводе смотрите на несколько мест. Во-первых, блок Certificate chain: там должно быть больше одного сертификата, если ваш leaf подписан intermediate CA. Во-вторых, строка в конце:
Verify return code: 0 (ok)
Если видите Verify return code: 21 (unable to verify the first certificate) или похожий код, сервер, скорее всего, не отдал промежуточный сертификат. Если видите self signed certificate in certificate chain, возможно, в bundle добавлен лишний корневой сертификат или используется непубличная цепочка, которой клиент не доверяет.
Проверить OCSP Stapling можно так:
openssl s_client -connect example.com:443 -servername example.com -status
Хороший признак — блок с OCSP-ответом и строкой:
OCSP Response Status: successful (0x0)
Если вместо этого выводится OCSP response: no response sent, stapling не сработал. Это не всегда критическая ошибка для обычного сертификата, но если вы используете Must-Staple, отсутствие stapled OCSP может привести к отказу у клиентов, которые строго проверяют расширение.
Для проверки конкретного CA-файла полезна команда openssl verify. Например, если у вас есть доменный сертификат и цепочка:
openssl verify -CAfile /etc/ssl/example.com/chain.pem /etc/ssl/example.com/cert.pem
Однако для публичных цепочек корректнее проверять против системного trust store плюс intermediate. На разных ОС пути отличаются. Примеры ниже дают направление, но пакет CA должен быть установлен и актуален.
Debian/Ubuntu
apt-cache policy ca-certificates
openssl version -d
RHEL-based и Fedora
rpm -q ca-certificates
openssl version -d
Если в контейнере нет пакета ca-certificates, приложение может падать с certificate verify failed даже при идеально настроенном сервере. Это отдельный класс ошибок: сервер отдаёт правильную цепочку, но клиенту не хватает доверенных корней.
Разбор частых ошибок certificate verify failed
Сервер отдаёт только leaf-сертификат
Симптом: браузер может открывать сайт, а openssl s_client показывает неполную цепочку и verify code не равен нулю. Исправление: заменить cert.pem на fullchain.pem в ssl_certificate для Nginx или в SSLCertificateFile для Apache, затем перезагрузить веб-сервер.
Неправильный порядок сертификатов в bundle
Симптом: в файле есть несколько PEM-блоков, но клиенты всё равно жалуются. Проверьте subject и issuer. Первым должен идти сертификат домена, затем intermediate, который его подписал. Не собирайте bundle «на глаз» из файлов с похожими названиями.
Сертификат выпущен не на тот домен
Симптом: цепочка валидна, но клиент пишет про hostname mismatch. Это не проблема intermediate CA. Проверьте SAN-поля:
openssl x509 -in /etc/ssl/example.com/fullchain.pem -noout -text | grep -A2 "Subject Alternative Name"
Современные клиенты ориентируются на SAN, а не только на CN. Если домена нет в SAN, сертификат нужно перевыпустить с правильным набором имён.
SNI попадает не в тот виртуальный хост
Симптом: без -servername или при ошибке в server_name сервер отдаёт дефолтный сертификат. Всегда проверяйте так:
openssl s_client -connect 203.0.113.10:443 -servername example.com -showcerts
Если по IP и SNI приходит правильный сертификат, а по доменному имени — нет, проверьте DNS. Если по SNI приходит чужой сертификат, ищите ошибку в порядке vhost, server_name, ServerName или в конфигурации reverse proxy перед веб-сервером.
OCSP Stapling не работает из-за DNS или egress-фильтра
Симптом: цепочка валидна, но -status показывает no response sent. Проверьте, может ли сервер резолвить имена и ходить наружу к OCSP-responder. В закрытых контурах часто разрешают входящий 443, но забывают про исходящие запросы веб-сервера.
getent hosts ocsp.example-ca.test
Адрес responder берите не из примера выше, а из поля AIA вашего сертификата:
openssl x509 -in /etc/ssl/example.com/cert.pem -noout -ocsp_uri
Если команда ничего не выводит, у сертификата может не быть OCSP URI. Тогда stapling для него не заработает, и это не ошибка Nginx или Apache.
Безопасная замена сертификатов без сюрпризов
При ручном обновлении сертификата лучше не редактировать рабочий fullchain.pem прямо на месте. Сначала положите новые файлы рядом, проверьте их, затем выполните атомарную замену или переключите симлинк. Это снижает риск ситуации, когда веб-сервер перечитал частично записанный файл.
install -d -m 0750 /etc/ssl/example.com-2026
install -m 0644 fullchain.pem /etc/ssl/example.com-2026/fullchain.pem
install -m 0600 privkey.pem /etc/ssl/example.com-2026/privkey.pem
openssl x509 -in /etc/ssl/example.com-2026/fullchain.pem -noout -subject -issuer -dates
ln -sfn /etc/ssl/example.com-2026 /etc/ssl/example.com-current
nginx -t
systemctl reload nginx
Для Apache последовательность такая же, меняется только команда проверки и reload. Если у вас несколько сервисов используют один сертификат — например, веб-сервер, почта, панель управления, API-шлюз — не забывайте перезагружать каждый сервис, который держит сертификат в памяти.
После замены всегда проверяйте снаружи, а не только локально. Локальная проверка может попасть на другой маршрут, другой vhost или обойти внешний балансировщик. Минимум — openssl s_client с SNI и проверка даты действия.
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -subject -issuer -dates
Если вы обслуживаете сайт на отдельном сервере, удобно держать сертификаты и конфигурацию веб-сервера под своим контролем. Для таких задач обычно подходит VDS, особенно когда нужны свои версии Nginx/Apache, отдельные правила firewall и автоматизация продления.
Чек-лист перед продакшеном
- В
ssl_certificateилиSSLCertificateFileуказанfullchain.pem, а не только leaf-сертификат. - Приватный ключ соответствует сертификату и хранится с ограниченными правами.
- Порядок PEM-блоков: доменный сертификат первым, затем intermediate certificate.
openssl s_client -connect host:443 -servername host -showcertsвозвращаетVerify return code: 0 (ok).- OCSP Stapling включён только после исправления цепочки, а
-statusпоказывает успешный OCSP-ответ или понятную причину его отсутствия. - DNS-резолвер на сервере работает, исходящие запросы к OCSP-responder не блокируются firewall-политиками.
- После reload проверен именно внешний домен, а не только локальный файл сертификата.
После базовой TLS-настройки полезно отдельно проверить HTTP-уровень: HSTS, заголовки защиты, кэширование и поведение reverse proxy. Например, можно продолжить с материалом про заголовки безопасности в Nginx и Apache.
Главная мысль простая: TLS-проблемы редко решаются одной директивой. Нужно отдельно проверить содержимое файлов, соответствие ключа, порядок цепочки, SNI, доверенное хранилище клиента и stapling. Если идти по этому порядку, ошибки вроде certificate verify failed перестают быть загадкой и обычно находятся за несколько минут.


