Сообщение об ошибке
curl: (60) SSL: no alternative certificate subject name matches target hostname 'api.example.com'
Также выводится как
- curl: (60) SSL: no alternative certificate subject name matches target host name 'api.example.com'
curl 8.x выводит «target hostname», curl 7.x выводил «target host name». Хост в кавычках — то, что было указано в URL. Очень старые версии curl (до 7.62.0) использовали для этой проверки код завершения 51.
curl (OpenSSL backend) — CURLE_PEER_FAILED_VERIFICATION, hostname check in lib/vtls/openssl.c
Что это означает
Цепочка сертификатов доверенная, но ни одно из DNS-имён (или IP-адресов) в расширении «альтернативное имя субъекта» (SAN) сертификата не совпадает с хостом из URL. curl сравнивает хост из URL только с записями SAN; если расширение SAN есть, общее имя (CN) субъекта игнорируется. Имя в кавычках в конце сообщения — это хост, к которому curl попросили подключиться.
Почему это происходит
Этого имени нет в сертификате
частоСертификат для example.com покрывает www.example.com и api.example.com, только если каждое из этих имён есть в списке SAN. Для новых поддоменов и новых окружений (staging, eu.) сертификат нужно перевыпустить.
В URL указан IP-адрес, localhost или внутренний псевдоним
частоhttps://10.0.0.5, https://localhost или имя сервиса docker-compose ведут к нужному серверу, но запрашивают имя, на которое сертификат никогда не выпускали. IP-адреса совпадают только с записями SAN типа IP и никогда — с записями типа DNS.
Сервер вернул сертификат другого сайта
иногдаНа общих IP-адресах сертификат выбирается по SNI. Если для запрошенного имени нет подходящего блока server, сервер отвечает сертификатом по умолчанию, в котором указан другой сайт.
Wildcard покрывает меньше, чем от него ожидают
иногда*.example.com покрывает api.example.com, но не example.com и не v2.api.example.com: wildcard заменяет ровно одну метку домена.
Как исправить
- 1.
Сравните список SAN с URL
Выведите расширение subjectAltName сертификата, который отдаёт сервер. Решение — либо сертификат, в котором указано это имя, либо URL с одним из указанных имён.
bash # Names the served certificate actually covers echo | openssl s_client -connect api.example.com:443 -servername api.example.com 2>/dev/null \ | openssl x509 -noout -subject -ext subjectAltName - 2.
Перевыпустите сертификат со всеми используемыми именами
Запросите все имена хостов (и IP-адреса, если клиенты подключаются по IP) как записи SAN. Имя, указанное только в поле общего имени (CN), не работает ни с одним современным клиентом.
bash # ACME: list every name the certificate must cover sudo certbot certonly --nginx -d example.com -d www.example.com -d api.example.com # Private CA: put all names into subjectAltName (CN is ignored for matching) openssl req -new -key server.key -out server.csr -subj "/CN=api.example.com" \ -addext "subjectAltName=DNS:api.example.com,DNS:www.example.com,IP:10.0.0.5" - 3.
Сохраните имя, меняйте только адрес
Если нужно обратиться к конкретному адресу (узлу балансировщика нагрузки, туннелю, IP-адресу staging-окружения), --resolve или --connect-to сохраняют хост из URL, так что SNI и проверка имени хоста по-прежнему используют имя из сертификата.
bash # Keep the certificate's name in the URL and steer the connection to another address curl --resolve api.example.com:443:10.0.0.5 https://api.example.com/ curl --connect-to api.example.com:443:lb-internal.example.net:443 https://api.example.com/ - 4.
Владельцам серверов: проверьте маршрутизацию по SNI
Если сертификат в запросах с SNI и без него различается, добавьте для этого имени блок server (или правило ingress) и убедитесь, что для реальных имён хостов клиенты не получают сертификат по умолчанию.
bash # Compare what the server returns with and without SNI openssl s_client -connect 10.0.0.5:443 -servername api.example.com </dev/null 2>/dev/null | openssl x509 -noout -subject openssl s_client -connect 10.0.0.5:443 -noservername </dev/null 2>/dev/null | openssl x509 -noout -subject
Связанные ошибки
x509: certificate is valid for example.com, not api.example.comGo crypto/x509Hostname/IP does not match certificate's altnames: Host: api.example.com. is no…Node.js tls.checkServerIdentity()[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: Hostname mismatch,…Python ssl module (OpenSSL host check)No subject alternative DNS name matching api.example.com found.Java (JSSE HostnameChecker)fatal: unable to access 'https://git.example.com/team/app.git/': SSL: certifica…git over HTTPS with libcurl-gnutls (Debian, Ubuntu)