Сертификат российского УЦ на nginx: установка по шагам
Сертификат от российского удостоверяющего центра по механике установки на nginx ничем принципиально не отличается от любого другого TLS-сертификата: тот же ssl_certificate, тот же приватный ключ, та же цепочка доверия. Разница в одном критичном месте — какому корневому центру доверяет клиент, который откроет сайт. Если это госсертификат, а браузер или приложение о нём не знает, соединение просто не установится, и на сервере всё будет выглядеть исправным. Разберём процесс по шагам и отдельно — этот нюанс.
Содержание
Что вы получите от удостоверяющего центра
После подачи заявки и прохождения проверки домена (и, как правило, организации) УЦ выдаёт вам набор файлов. Обычно это:
- сертификат сервера — файл вида
domain.crtилиdomain.pem, выписанный на ваш домен; - приватный ключ — либо вы его генерируете сами (тогда УЦ получает только CSR — запрос на подпись, без ключа), либо УЦ выдаёт готовую пару, что менее безопасно, но встречается у некоторых центров при упрощённой процедуре;
- промежуточные сертификаты — один или несколько файлов, связывающих ваш сертификат с корневым УЦ;
- корневой сертификат — сам по себе на сервер обычно не идёт (об этом ниже), но пригодится для настройки доверия на стороне клиентов.
Если вы генерировали ключ сами, порядок такой: сначала создаёте приватный ключ и CSR, отправляете CSR в УЦ, получаете обратно подписанный сертификат.
# генерация приватного ключа (RSA 2048, минимально приемлемый размер на конец 2026 года)
openssl genrsa -out domain.key 2048
# запрос на подпись сертификата (CSR) — на вопросы CN и SAN отвечайте точным доменным именем
openssl req -new -key domain.key -out domain.csr -subj "/CN=example.ru"
Если сайт должен отвечать сразу на несколько имён (example.ru и www.example.ru), SAN (Subject Alternative Name) нужно указать явно — через -addext в новых версиях OpenSSL или конфиг-файл с секцией [alt_names]. Без SAN современные браузеры сертификат не примут, даже если CN указан верно.
Ключ (domain.key) никуда, кроме сервера, отправлять не нужно — это единственный секрет во всей цепочке, всё остальное публично проверяемо.
Формируем цепочку сертификатов
Это самый частый источник ошибок при установке сертификата от небраузерного или не входящего в стандартные trust store УЦ. Клиент проверяет сертификат не изолированно, а строит цепочку от вашего сертификата до корня, которому доверяет. Если промежуточное звено отсутствует на сервере, часть клиентов (в первую очередь мобильные приложения и старые ОС, которые не умеют докачивать недостающие сертификаты через AIA-расширение) откажется устанавливать соединение. Подробнее о том, почему в этой ситуации curl может сработать, а браузер — ругаться, разобрано в статье про цепочку сертификатов изнутри.
Nginx ожидает на входе один файл — сертификат сервера, за которым подряд идут промежуточные сертификаты, от ближайшего к вашему домену до ближайшего к корню. Собирается это простой конкатенацией:
cat domain.crt intermediate1.crt intermediate2.crt > domain-fullchain.crt
Порядок важен: сначала сертификат сайта, затем промежуточные — именно в том порядке, в котором каждый следующий подписывает предыдущий. Если перепутать порядок или пропустить звено, часть клиентов всё же достроит цепочку сама (через AIA fetching), а часть — откажется; полагаться на такую «доборку» не стоит, поведение отличается от клиента к клиенту.
Корневой сертификат в fullchain включать не нужно — он должен быть в доверенном хранилище клиента (или не быть, что и есть основная проблема с российскими УЦ, разобранная ниже). Включение корня не ломает работу, но раздувает handshake без пользы.
Отдельно от общей цепочки полезно сохранить сертификат для OCSP stapling — файл ssl_trusted_certificate, который должен содержать промежуточные и корневой сертификаты (но не сам сертификат сервера):
cat intermediate1.crt intermediate2.crt root.crt > domain-trusted.crt
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНастройка nginx
Минимальный конфиг серверного блока с TLS выглядит так:
server {
listen 443 ssl;
server_name example.ru www.example.ru;
ssl_certificate /etc/nginx/ssl/domain-fullchain.crt;
ssl_certificate_key /etc/nginx/ssl/domain.key;
ssl_trusted_certificate /etc/nginx/ssl/domain-trusted.crt;
ssl_stapling on;
ssl_stapling_verify on;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers off;
root /var/www/example.ru;
index index.html;
}
Что за что отвечает:
ssl_certificate— путь к файлу с сертификатом сервера и промежуточными сертификатами (тем самымfullchain, который собрали выше). Это то, что nginx реально отправляет клиенту при handshake.ssl_certificate_key— путь к приватному ключу. Права на файл должны быть закрыты от посторонних (chmod 600, владелец — root или пользователь nginx-мастер-процесса).ssl_trusted_certificate— используется только для OCSP stapling, не отправляется клиенту напрямую. Nginx использует его, чтобы проверить и приложить к ответу статус отозванности сертификата, полученный от OCSP-респондера УЦ.
Если ключ защищён паролем (что для боевого сервера обычно неудобно, так как требует ручного ввода при каждом рестарте), можно либо снять пароль заранее (openssl rsa -in domain.key.enc -out domain.key), либо настроить ssl_password_file.
Права на директорию с сертификатами:
mkdir -p /etc/nginx/ssl
cp domain-fullchain.crt domain.key domain-trusted.crt /etc/nginx/ssl/
chmod 600 /etc/nginx/ssl/domain.key
chmod 644 /etc/nginx/ssl/domain-fullchain.crt /etc/nginx/ssl/domain-trusted.crt
После правки конфига — проверка синтаксиса и перезагрузка без разрыва текущих соединений:
nginx -t
systemctl reload nginx
Если nginx -t ругается на несовпадение ключа и сертификата — почти всегда это означает, что в спешке подставили не ту пару файлов (например, ключ от предыдущего сертификата). Сверить, что ключ и сертификат — пара, можно по совпадению модулей:
openssl x509 -noout -modulus -in domain-fullchain.crt | openssl md5
openssl rsa -noout -modulus -in domain.key | openssl md5
Хэши должны совпасть.
Проверка корректности установки
Первым делом — проверка самим openssl, без браузера, чтобы видеть сырой ответ сервера:
openssl s_client -connect example.ru:443 -servername example.ru -showcerts
В выводе смотрите на:
- Verify return code — в конце должно стоять
Verify return code: 0 (ok). Если тамunable to get local issuer certificate— значит, цепочка неполная (не хватает промежуточного сертификата) либо корень не входит в trust store машины, с которой вы проверяете (актуально при проверке с той же машины, где вы устанавливали российские корневые сертификаты, — на «чистой» машине без них ошибка будет другой природы, но выглядеть похоже). - Список Certificate chain — должен показывать сертификат сервера и все промежуточные звенья, в правильном порядке.
Для проверки полноты цепочки без установки российских корней в системе (то есть — что увидит клиент, у которого их нет) удобны внешние сервисы SSL-диагностики: они показывают путь построения цепочки и отсутствующие звенья. Но учитывайте: такие сервисы обычно не знают о специфических российских корневых УЦ и могут пометить цепочку как «нерабочую» даже при корректной установке — это ожидаемо, не баг конфигурации.
Локально быстрая проверка даты истечения и субъекта сертификата:
openssl x509 -in domain-fullchain.crt -noout -subject -issuer -dates
Полезно сверить subject (кому выдан) и issuer (кем выдан) — частая ошибка при копировании файлов между серверами — перепутать сертификаты разных доменов.
Если после reload сайт продолжает отдавать старый сертификат — проверьте, что действительно применился новый конфиг: nginx кэширует загруженные сертификаты только в рамках рабочих процессов, и после reload (а не полного restart) новые воркеры должны подхватить свежие файлы автоматически. Если этого не происходит — возможно, путь в ssl_certificate указывает не туда, где вы обновили файл (например, симлинк не туда смотрит), или конфиг лежит в файле, который не подключён через include. Похожий по симптомам, но самостоятельный сценарий — когда certbot продлевает сертификат, а nginx его не перечитывает; разбор такого инцидента с deploy-hook есть в статье «Сертификат обновлялся, а nginx не перечитывал его 60 дней».
Главный нюанс: доверие клиента к корневому УЦ
Это единственное отличие сертификата российского УЦ от сертификата условного международного центра, и оно не техническое, а организационное. Сертификат может быть выписан безукоризненно, цепочка может быть собрана идеально — но если корневой сертификат этого УЦ не находится в системном хранилище доверенных корней клиента, соединение всё равно будет отклонено как небезопасное.
Практически это значит:
- Актуальные версии большинства массовых браузеров (тех, что ориентированы на международные trust store — Mozilla, Google/Chromium корневая программа) на конец августа 2026 года по умолчанию не доверяют российским государственным корневым УЦ. Пользователь увидит предупреждение о недоверенном сертификате.
- Отечественные браузеры (в частности, те, что собираются с расчётом на российский рынок и партнёрские сборки Chromium/Firefox с добавленными корнями) и браузеры с предустановленными российскими корневыми сертификатами (через Госуслуги, КриптоПро или ручную установку) сайт откроют нормально.
- Мобильные и десктопные приложения, которые используют собственный TLS-стек или свой pinned trust store (а не системный), сертификату не будут доверять, даже если в браузере на этом же устройстве всё работает, — если явно не добавить корень в доверенные для приложения. Похожий эффект — но по другой причине, из-за выпавшего промежуточного звена, — разобран в кейсе «Убрали промежуточный сертификат — и мобильное приложение отвалилось»: там показано, насколько чувствительны мобильные клиенты к состоянию цепочки в принципе.
- Серверные интеграции (webhook-и, API-клиенты на других серверах, curl/wget в скриптах) будут падать с ошибкой проверки сертификата, если на машине, откуда идёт запрос, не установлен нужный корень.
Из этого следует практический вывод при выборе типа сертификата для конкретного сайта:
| Аудитория сайта | Стоит ли использовать сертификат российского УЦ |
|---|---|
| Внутренний B2G-сервис, доступ только из РФ, пользователи с настроенными корнями | Да, обычно оправдано и иногда обязательно по регламенту |
| Публичный сайт для широкой аудитории, включая зарубежных пользователей | Рискованно — часть аудитории увидит предупреждение браузера |
| API для мобильного приложения, которое вы контролируете | Можно, но нужно явно вшить (pin) корневой сертификат в приложение |
| Сайт с оплатой и вводом персональных данных для розничных клиентов | Нежелательно без параллельного сертификата от международно доверенного УЦ — доверие пользователя к «замочку» в адресной строке напрямую влияет на конверсию |
Если аудитория смешанная, практикуют двойную схему: сертификат от международно доверенного УЦ как основной (в том числе через ACME/Let's Encrypt) плюс отдельно сертификат российского УЦ для клиентов и госсистем, которым он предписан, — на поддомене или через SNI-роутинг с несколькими server блоками. Это усложняет эксплуатацию (два цикла продления вместо одного), но снимает вопрос доверия сразу для всей аудитории.
Проверить, установлен ли конкретный российский корневой сертификат в системе, откуда вы тестируете, можно так (для Linux, каталог доверенных сертификатов может отличаться в зависимости от дистрибутива):
# Debian/Ubuntu
ls /etc/ssl/certs/ | grep -i russian
# проверка соединения с указанием, что цепочка должна замкнуться на конкретный установленный корень
openssl verify -CAfile /path/to/russian_root.pem domain-fullchain.crt
Если без -CAfile команда возвращает unable to get local issuer certificate, а с явным указанием корня — OK, это подтверждает: цепочка и сертификат корректны, а проблема ровно в том, что корень не входит в системный trust store по умолчанию.
Частые ошибки при установке
- Указали в
ssl_certificateтолько сам сертификат сервера, без промежуточных. Работает в браузере, который умеет докачивать недостающие звенья через AIA, и не работает в curl или мобильном приложении без такого поведения. Решение — пересобратьfullchainи убедиться, что порядок сертификатов правильный. - Перепутали порядок промежуточных сертификатов. Некоторые клиенты всё равно построят цепочку (перебором), другие — нет. Проверяйте порядок через
openssl s_client -showcertsи сверяйтеissuerодного сертификата сsubjectследующего. - Забыли обновить
ssl_trusted_certificateпри продлении сертификата. OCSP stapling начинает отдавать статус для старого сертификата или не работает вовсе —ssl_stapling_verifyв логах nginx покажет ошибку. - Ключ сгенерирован с недостаточной длиной или устаревшим алгоритмом. На конец августа 2026 года RSA 2048 — минимально приемлемый вариант для большинства регламентов, но для новых установок разумно рассмотреть ECDSA (P-256), если конкретный УЦ его поддерживает, — компактнее и быстрее в handshake.
- Файлы сертификата и ключа не синхронизированы после ручного обновления. Путь к ключу указывает на старый файл после миграции между серверами.
nginx -tпройдёт (файлы физически существуют), а TLS handshake будет падать с ошибкой несовпадения ключа. Сверяйте модули, как показано выше.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Нужно ли устанавливать корневой сертификат УЦ на сам сервер с nginx?
Нет, на сервер в ssl_certificate корень не кладётся — только сертификат сайта и промежуточные звенья. Корень нужен на стороне клиента, в его доверенном хранилище, либо в ssl_trusted_certificate для целей OCSP stapling (там как раз уместны и промежуточные, и корневой).
Можно ли использовать сертификат российского УЦ вместе с Let's Encrypt на одном сервере?
Да, они не конфликтуют — это независимые пары сертификат/ключ. На одном nginx можно развести их по разным server блокам (по SNI-имени или по порту) либо чередовать по логике приложения, если нужно предъявлять разный сертификат разным категориям клиентов.
Почему curl отвечает нормально, а браузер показывает предупреждение о недоверенном сертификате?
Обычно это как раз про доверие к корню: если на машине, где запущен curl, вручную установлен нужный корневой сертификат (или указан --cacert), а в браузере — стандартный системный trust store без него, поведение будет отличаться при абсолютно одинаковой настройке сервера.
Что делать, если сайт должен одинаково хорошо открываться и у пользователей с российскими корнями, и у остальных?
Использовать сертификат от международно доверенного УЦ как основной. Если по регламенту или заказчику требуется именно российский УЦ для части трафика — разводить по SNI или поддомену, как описано выше, а не пытаться заменить один сертификат другим для всей аудитории сразу.
Как понять, что проблема именно в цепочке, а не в доверии к корню?
Прогоните openssl s_client -showcerts и openssl verify -CAfile <корень> — если с явно указанным корнем проверка проходит (OK), а без него нет, цепочка собрана правильно и вопрос ровно в доверии клиента к этому корню, а не в конфигурации сервера.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →