Сообщение об ошибке
curl: (60) SSL certificate problem: self-signed certificate in certificate chain
Также выводится как
- curl: (60) SSL certificate problem: self signed certificate in certificate chain
- curl: (60) SSL certificate OpenSSL verify result: self-signed certificate in certificate chain (19)
OpenSSL 3.0 и новее выводят «self-signed», OpenSSL 1.1.1 — «self signed» без дефиса, поэтому в журналах и результатах поиска встречаются оба написания. Более новые версии curl (8.22.0 в нашей проверке) формулируют это как «SSL certificate OpenSSL verify result: self-signed certificate in certificate chain (19)». Сборки curl с Schannel в Windows сообщают об этой ситуации как «SEC_E_UNTRUSTED_ROOT».
curl (OpenSSL backend) — CURLE_PEER_FAILED_VERIFICATION, X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN (19)
Что это означает
curl прошёл по цепочке, которую отправил сервер, до самоподписанного корневого сертификата, а этого корневого сертификата нет в пакете сертификатов CA (CA bundle), которым пользуется curl. В отличие от ошибки «unable to get local issuer certificate», цепочка здесь полная: curl видит корневой сертификат, но у него просто нет причин ему доверять. На практике корневой сертификат почти всегда принадлежит корпоративному CA или прокси, который перехватывает TLS.
Почему это происходит
Прокси или антивирус с инспекцией TLS переподписывает соединения
частоZscaler, Netskope, Fortinet, Palo Alto и многие антивирусы терминируют TLS и предъявляют сертификаты, подписанные их собственным корневым сертификатом, который они отправляют в цепочке. Браузер доверяет этому корневому сертификату через операционную систему, но curl внутри WSL, Docker, Git Bash или CI-раннера читает собственный пакет сертификатов и отвергает этот корневой сертификат. В поле издателя сертификата указан производитель прокси или ваша компания, а не настоящий CA сайта.
Сервис использует частный корневой сертификат и отправляет его в цепочке
частоВнутренние сервисы, ingress-ресурсы Kubernetes и специализированные устройства часто используют сертификат, подписанный корпоративным или самостоятельно созданным корневым сертификатом, и включают этот корневой сертификат в отдаваемую цепочку. Отправка корневого сертификата не делает его доверенным: якорь доверия уже должен быть на клиенте.
Частный корневой сертификат установлен в другое хранилище
иногдаКорневой сертификат добавили в хранилище сертификатов Windows, связку ключей macOS или cacerts в Java, а этот curl читает пакет сертификатов OpenSSL, например /etc/ssl/certs/ca-certificates.crt или копию, которая поставляется с Git for Windows, Homebrew или conda.
Хранилище корневых сертификатов публичных CA на клиенте устарело
редкоИзредка сервер отправляет более новый публичный корневой сертификат, которого нет у старого, ни разу не обновлявшегося клиента. Обновление пакета ca-certificates решает проблему; если корневой сертификат действительно публичный, на других машинах сайт без ошибок пройдёт проверку.
Как исправить
- 1.
Выясните, чей это корневой сертификат
Посмотрите на последний сертификат цепочки. Если в субъекте указан производитель прокси или средств защиты либо название вашей компании, решение — доверять этому корневому сертификату на этой машине. Если там неожиданное имя, а сеть вам не подконтрольна, остановитесь: кто-то перехватывает соединение.
bash # Print the subject and issuer of every certificate the server (or the proxy) sends openssl s_client -connect api.example.com:443 -servername api.example.com -showcerts </dev/null 2>/dev/null \ | grep -E '^ *[0-9]+ s:|^ *i:' # The last entry has s: == i: — that self-signed root is what curl does not trust. - 2.
Добавьте корневой сертификат компании или прокси в системное хранилище
Получите корневой сертификат у ИТ-отдела (или экспортируйте его из хранилища доверенных сертификатов управляемой машины) и установите в системный пакет сертификатов, который использует curl. В контейнерах его нужно добавлять при сборке образа, в WSL — внутри дистрибутива Linux, а не только в Windows.
bash # Debian, Ubuntu, Alpine sudo cp corp-root-ca.pem /usr/local/share/ca-certificates/corp-root-ca.crt sudo update-ca-certificates # RHEL, Fedora, Amazon Linux sudo cp corp-root-ca.pem /etc/pki/ca-trust/source/anchors/ sudo update-ca-trust - 3.
Настройте доверие корневому сертификату для одной команды или одного окружения
--cacert и CURL_CA_BUNDLE заменяют пакет сертификатов curl по умолчанию, поэтому, если тот же процесс обращается и к публичным сайтам, указывайте в них файл, где есть и публичные корневые сертификаты, и частный.
bash curl --cacert corp-root-ca.pem https://api.example.com/ # Keep public roots too: build one bundle with both cat /etc/ssl/certs/ca-certificates.crt corp-root-ca.pem > /opt/certs/bundle.pem export CURL_CA_BUNDLE=/opt/certs/bundle.pem - 4.
Владельцам серверов: не рассчитывайте на отправку корневого сертификата
Отдавайте конечный сертификат и промежуточные, а частный корневой сертификат распространяйте на клиенты через систему управления конфигурациями. Включение корневого сертификата в цепочку только тратит байты и никогда не делает недоверенный корневой сертификат доверенным.
bash # Serve leaf + intermediates only; clients must already have the root cat api.example.com.crt intermediate.crt > fullchain.pem - 5.
Временная диагностика: посмотрите издателя с отключённой проверкой
-k отключает проверку сертификатов, чтобы в подробном выводе можно было увидеть субъект и издателя, которые предъявляет сервер. Используйте этот флаг только для того, чтобы определить корневой сертификат. Если оставить -k в скриптах, любой на пути трафика сможет читать и изменять его, включая токены и скачиваемые установщики.
bash # Diagnostic only: shows who issued the certificate even though curl distrusts it curl -sv -k -o /dev/null https://api.example.com/ 2>&1 | grep -E 'subject:|issuer:'
Связанные ошибки
curl: (60) SSL certificate problem: unable to get local issuer certificatecurl (OpenSSL backend)curl: (60) SSL certificate problem: self-signed certificatecurl (OpenSSL backend)verify error:num=19:self-signed certificate in certificate chainOpenSSL CLI (openssl s_client / openssl verify)self-signed certificate in certificate chainNode.js tls / https / fetch[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self-signed certifi…Python ssl module (OpenSSL)