Сертификат Минцифры: как убрать ошибку SSL
Коротко: если запрос к API падает с ошибкой unable to get local issuer certificate, self signed certificate in certificate chain, x509: certificate signed by unknown authority или PKIX path building failed, а в Яндекс Браузере адрес открывается, скорее всего сервер перешёл на сертификат НУЦ Минцифры. Его корню, Russian Trusted Root CA, curl, Python, Node.js и Java по умолчанию не доверяют. Отключать проверку не нужно: скачайте корень на gosuslugi.ru/crt и добавьте его туда, где его ищет ваш инструмент. В Debian, Ubuntu и Astra Linux хватает трёх команд:
curl -O https://gu-st.ru/content/lending/russian_trusted_root_ca_pem.crt
sudo cp russian_trusted_root_ca_pem.crt \
/usr/local/share/ca-certificates/russian_trusted_root_ca.crt
sudo update-ca-certificates
Python, Node.js, Java и Docker-образы системное хранилище не читают, им корень передают отдельно.
К своему инструменту: Linux · macOS · Windows · curl · Python · Node.js · Java · Go · PHP · git · Docker · Postman и другие клиенты
Как выглядит ошибка в разных инструментах#
| Инструмент | Что пишет |
|---|---|
| curl | SSL certificate problem: unable to get local issuer certificate, код 60 |
| Python (requests, httpx, urllib) | [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate |
| Node.js | UNABLE_TO_GET_ISSUER_CERT_LOCALLY или SELF_SIGNED_CERT_IN_CHAIN |
| Postman | Error: unable to get local issuer certificate или self signed certificate in certificate chain |
| Java | PKIX path building failed … unable to find valid certification path to requested target |
| Go и Tetiva | x509: certificate signed by unknown authority |
| PHP | SSL certificate problem: … в curl, error:0A000086:SSL routines::certificate verify failed в потоках |
| git на Debian | server verification failed: certificate signer not trusted |
У ошибки два варианта, и лечатся они одинаково. Если сервер присылает корень вместе с цепочкой, как sberbank.ru, OpenSSL пишет self-signed certificate in certificate chain. Если не присылает, как vtb.ru или platform-api2.max.ru, пишет unable to get local issuer certificate. curl с версии 8.17 формулирует иначе: SSL certificate OpenSSL verify result: unable to get local issuer certificate (20).
Почему ошибка появилась сейчас#
Российские компании переводят сайты и API на сертификаты Национального удостоверяющего центра. Летом 2026 года процесс ускорился: зарубежный удостоверяющий центр GlobalSign начал отзывать сертификаты российских организаций, в начале августа на сертификаты НУЦ перешли сайты крупных банков, а MAX с 19 июля попросил разработчиков слать запросы к Bot API на новый адрес и добавить сертификат Минцифры в доверенные. Тот же корень нужен для GigaChat API. Свои инструкции по переходу API опубликовали Т-Банк (сам портал открывается только с установленным корнем) и Сбер.
Браузеры ведут себя по-разному. В Яндекс Браузере корень НУЦ уже есть во внутреннем хранилище, системное он не меняет. Остальным браузерам нужен установленный сертификат. Поэтому типичная картина такая: в браузере всё открывается, а скрипт, сервер или CI на Linux падают.
Как убедиться, что дело в НУЦ#
Посмотрите, кто выдал сертификат сервера:
openssl s_client -connect platform-api2.max.ru:443 \
-servername platform-api2.max.ru -showcerts </dev/null
В начале вывода будет цепочка. Если в ней есть Russian Trusted Sub CA и Russian Trusted Root CA, это сертификат НУЦ:
0 s:CN=*.max.ru, O=MAX LLC, ...
i:C=RU, O=The Ministry of Digital Development and Communications, CN=Russian Trusted Sub CA
1 s:C=RU, O=The Ministry of Digital Development and Communications, CN=Russian Trusted Sub CA
i:C=RU, O=The Ministry of Digital Development and Communications, CN=Russian Trusted Root CA
Verify return code: 20 (unable to get local issuer certificate)
Сервер присылает свой сертификат и промежуточный Russian Trusted Sub CA, а корень по правилам TLS должен уже быть у клиента. Его нет, отсюда ошибка про «local issuer»: инструмент не нашёл у себя того, кто подписал промежуточный сертификат.
Если в цепочке нет Russian Trusted CA#
У той же ошибки есть и другие частые причины:
- Цепочка обрывается на сертификате сервера (есть
0 s:, но нет1 s:): сервер не присылает промежуточный. Браузеры часто это прощают, другие клиенты нет. Чинить нужно на сервере: в его файле сертификата должна быть вся цепочка. - Издатель ваша компания или средство защиты, например корпоративный прокси, который проверяет трафик: попросите корень у ИТ и поставьте его теми же шагами, что ниже.
- Издатель известный публичный центр: у инструмента устарел набор корней. Обновите
ca-certificates,certifiили саму среду выполнения.
Где скачать Russian Trusted Root CA#
Официальный источник один: страница gosuslugi.ru/crt. Для серверов и разработки нужен файл корневого сертификата Russian Trusted Root CA в формате PEM, обычно он называется russian_trusted_root_ca_pem.crt. Промежуточный ставить не нужно: сервер присылает его сам. К тому же промежуточных у НУЦ два, старый Russian Trusted Sub CA и новый 2024 года, а серверы сейчас присылают новый, так что старый файл промежуточного не поможет.
Для скриптов корень можно скачать с сервера Госуслуг напрямую:
curl -O https://gu-st.ru/content/lending/russian_trusted_root_ca_pem.crt
Если этот адрес сам отвечает ошибкой сертификата, скачайте файл в Яндекс Браузере и сверьте отпечаток.
Если файл .cer бинарный, то есть не начинается со строки -----BEGIN CERTIFICATE-----, это DER, переведите его в PEM:
openssl x509 -inform DER -in russian_trusted_root_ca.cer \
-out russian_trusted_root_ca_pem.crt
И проверьте, что это тот самый корень. Он действует до 27 февраля 2032 года, его отпечаток SHA-256:
D2:6D:2D:02:31:B7:C3:9F:92:CC:73:85:12:BA:54:10:35:19:E4:40:5D:68:B5:BD:70:3E:97:88:CA:8E:CF:31
openssl x509 -in russian_trusted_root_ca_pem.crt -noout -fingerprint -sha256
Какое хранилище у вашего инструмента#
После установки сертификата в систему ошибка может остаться: многие среды носят свой набор доверенных корней и системный не читают.
Системное хранилище читают curl в Linux и Windows, Go, PowerShell, urllib в Python на Linux и Windows и Tetiva. Свой набор корней носят Python requests и httpx, Node.js и Java, а у каждого Docker-образа хранилище своё, внутри образа. Поэтому порядок такой: сначала корень в систему, потом отдельно для тех, кто системное хранилище не читает.
Как установить сертификат Минцифры по шагам#
Linux
Debian, Ubuntu и Astra Linux:
sudo cp russian_trusted_root_ca_pem.crt \
/usr/local/share/ca-certificates/russian_trusted_root_ca.crt
sudo update-ca-certificates
Файл должен быть в PEM и с расширением .crt, иначе update-ca-certificates его не возьмёт. В ответ команда напишет 1 added.
RHEL, Fedora и их родственники:
sudo cp russian_trusted_root_ca_pem.crt /etc/pki/ca-trust/source/anchors/
sudo update-ca-trust
В Alpine сначала нужен пакет ca-certificates: в базовом образе есть файл с корнями, но нет команды update-ca-certificates. В Arch корень добавляют командой sudo trust anchor --store russian_trusted_root_ca_pem.crt.
Если прав администратора нет (виртуальный хостинг, чужой CI), соберите свой файл из системного набора и корня Минцифры и передавайте его вместо системного:
cat /etc/ssl/certs/ca-certificates.crt russian_trusted_root_ca_pem.crt > ~/ca-bundle.pem
В RHEL системный набор лежит в /etc/pki/tls/certs/ca-bundle.crt. Такой файл подходит для --cacert в curl, REQUESTS_CA_BUNDLE в requests, SSL_CERT_FILE в httpx и curl.cainfo в PHP.
macOS
sudo security add-trusted-cert -d -r trustRoot \
-k /Library/Keychains/System.keychain russian_trusted_root_ca_pem.crt
macOS попросит подтвердить изменение доверия в окне, поэтому по SSH и в CI эта команда не сработает. Системный /usr/bin/curl после этого корень видит: он проверяет сертификаты и по связке ключей. curl из Homebrew в свежих сборках тоже проверяет через систему, а старые берут свой файл сертификатов, и с ними надёжнее --cacert.
Windows
certutil -addstore -f Root russian_trusted_root_ca.cer
Команду запускают от администратора. Без прав добавьте ключ -user, тогда корень станет доверенным только для вашей учётной записи. certutil принимает файл с Госуслуг как есть, в PEM или DER. После этого корень видят curl.exe, PowerShell, Go и браузеры на Chromium.
curl на один запуск
curl --cacert russian_trusted_root_ca_pem.crt https://platform-api2.max.ru/me
То же делает переменная CURL_CA_BUNDLE. curl.exe в Windows по умолчанию проверяет по системному хранилищу и эту переменную не читает. --cacert он понимает, но тогда проверяет только по этому файлу, так что проще поставить корень в систему.
Python
requests и httpx проверяют сертификаты по пакету certifi и системное хранилище не читают. Варианты:
- requests: переменная
REQUESTS_CA_BUNDLEили параметрverify=. Она заменяет набор корней, а не дополняет его. Файл с одним корнем Минцифры оставит requests без всех остальных доверенных корней. Поэтому сначала добавьте корень в систему, а потом укажите системный файл целиком:REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt. - httpx: переменная
SSL_CERT_FILEс тем же системным файлом или контекстssl.create_default_context(cafile=...)в параметреverify. - Системное хранилище для всего приложения: пакет truststore, Python 3.10 и новее. Две строки при запуске приложения, и requests с httpx начнут проверять сертификаты через macOS, Windows или OpenSSL в Linux:
import truststore
truststore.inject_into_ssl()
Стандартный urllib читает системное хранилище в Linux и Windows. На macOS Python с python.org или из Homebrew в связку ключей не смотрит, там нужен truststore или SSL_CERT_FILE. pip с версии 24.2 на Python 3.10 и новее берёт системное хранилище сам.
Node.js
Node.js носит встроенный набор корней. Переменная NODE_EXTRA_CA_CERTS дополняет его и читается при старте процесса:
NODE_EXTRA_CA_CERTS=/path/russian_trusted_root_ca_pem.crt node app.js
Если корень уже в системе, можно запустить node --use-system-ca app.js. Флаг работает на всех трёх системах в Node.js 22.15 и новее в ветке 22, в 23.9 и новее и в 24.
Java
Корень добавляют в общее хранилище cacerts, пароль у него по умолчанию changeit. Ключ -cacerts есть начиная с JDK 9:
sudo keytool -importcert -cacerts -noprompt \
-alias russian_trusted_root_ca \
-file russian_trusted_root_ca_pem.crt -storepass changeit
Отдельный truststore через -Djavax.net.ssl.trustStore заменяет cacerts целиком: если положить в него только корень Минцифры, других доверенных корней в нём не будет. Поэтому сначала скопируйте cacerts и добавьте корень в копию. В Windows Java может взять системное хранилище: -Djavax.net.ssl.trustStoreType=Windows-ROOT.
Go
Go проверяет сертификаты по системе: в Linux читает файл с корнями и каталоги /etc/ssl/certs, в macOS и Windows спрашивает саму систему. Поэтому обычно хватает корня в системе. С Go 1.27 (для модулей с go 1.27 в go.mod) переменная SSL_CERT_FILE в macOS и Windows отключает системную проверку, и файл с одним корнем в ней ломает проверку для всех остальных сайтов. Вернуть старое поведение можно через GODEBUG=x509sslcertoverrideplatform=0. В коде корень можно добавить к системному пулу через x509.SystemCertPool() и AppendCertsFromPEM.
PHP
PHP берёт хранилище OpenSSL, и корня в системе обычно достаточно. Если файл задают явно в php.ini, через curl.cainfo и openssl.cafile, в нём должен быть полный набор корней, а не один корень Минцифры.
git
Для одного сервера корень удобнее подключить только к нему:
git config --global http.https://git.example.ru/.sslCAInfo \
/path/russian_trusted_root_ca_pem.crt
Git для Windows по умолчанию проверяет сертификаты через системное хранилище, ему хватает certutil.
Docker
У образа своё хранилище, и корень с машины туда не попадает. В Dockerfile на Debian:
FROM debian:trixie-slim
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates \
&& rm -rf /var/lib/apt/lists/*
COPY russian_trusted_root_ca_pem.crt /usr/local/share/ca-certificates/russian_trusted_root_ca.crt
RUN update-ca-certificates
В slim-образах пакета ca-certificates нет, его ставят отдельно. Python и Node.js в контейнере всё равно не увидят системное хранилище, им нужны настройки из разделов выше.
Ловушка для тех, кто работает на Mac с OrbStack: он сам подкладывает в контейнеры сертификаты, которым доверяет macOS. Локально в Docker всё работает, а в CI тот же образ падает.
Если внутри Node.js, как у n8n, пересобирать образ не обязательно: смонтируйте файл и задайте переменную в docker-compose.yml:
services:
n8n:
image: n8nio/n8n
environment:
NODE_EXTRA_CA_CERTS: /certs/russian_trusted_root_ca_pem.crt
volumes:
- ./russian_trusted_root_ca_pem.crt:/certs/russian_trusted_root_ca_pem.crt:ro
Если с ошибкой x509: certificate signed by unknown authority падает сам docker pull или docker login, корень нужен не образу, а демону Docker: поставьте его в систему и перезапустите Docker (sudo systemctl restart docker) или положите файл в /etc/docker/certs.d/<адрес реестра>/ca.crt.
Postman, Insomnia, Bruno и Tetiva
- Postman: Settings → вкладка Certificates → переключатель «CA certificates», выбрать PEM-файл.
- Insomnia: с конца 2023 года берёт системное хранилище. Свой корень можно добавить для коллекции в окне «Manage Certificates», блок «CA Certificate». Клиент будет использовать этот файл вместо системного хранилища, поэтому в нём нужен полный набор корней.
- Bruno: с версии 2.11 тоже доверяет системному хранилищу. Свой файл: Preferences → General → «Use Custom CA Certificate».
- Hoppscotch Desktop: Settings → Interceptors → Native → «CA Certificates».
- Tetiva: проверяет сертификаты по системному хранилищу, своего списка корней и переключателя «не проверять» в клиенте нет. Корень, установленный в систему по шагам выше, Tetiva видит сразу, а в Linux после перезапуска. Пока корня нет, запрос заканчивается такой ошибкой:
На macOS текст другой: x509: “*.max.ru” certificate is not trusted.
Почему не -k и не verify=False#
Флаги -k в curl, verify=False в Python и NODE_TLS_REJECT_UNAUTHORIZED=0 в Node.js отключают проверку целиком, а не только для НУЦ. С ними клиент примет сертификат от кого угодно, в том числе от того, кто перехватил трафик. Такой флаг легко оставить «временно» и забыть, особенно в CI. С добавленным корнем проверку можно оставить включённой, и она продолжит защищать запросы ко всем остальным сайтам.
Ещё одна ловушка: корень Минцифры, добавленный в общее системное хранилище, начинает действовать для всех сайтов, а не только для нужного API. Если это не подходит, держите отдельный файл с корнем и передавайте его только тем запросам, которым он нужен: --cacert в curl, verify= в Python, отдельный truststore в Java.
Шпаргалка#
- Признак: в цепочке
openssl s_clientестьRussian Trusted Sub CA. - Корень: Russian Trusted Root CA с gosuslugi.ru/crt, SHA-256 начинается с
D2:6D:2D:02. - curl на один запуск:
--cacert russian_trusted_root_ca_pem.crt. - Linux:
/usr/local/share/ca-certificates/иupdate-ca-certificatesили/etc/pki/ca-trust/source/anchors/иupdate-ca-trust. - Python:
REQUESTS_CA_BUNDLEилиverify=. Node.js:NODE_EXTRA_CA_CERTS. Java:keytool -importcert. - Не делать:
-k,verify=False,NODE_TLS_REJECT_UNAUTHORIZED=0.