Введение #
При диагностике проблем с HTTPS, TLS или сертификатами стандартные инструменты вроде браузера или curl часто скрывают важные детали.
Особенно это заметно при работе с виртуальными хостами, reverse proxy, Kubernetes ingress и автоматическими сертификатами.
В таких случаях оптимальным инструментом становится openssl s_client — низкоуровневый TLS-клиент, позволяющий увидеть все этапы TLS-соединения и точно определить источник проблемы.
Что такое openssl s_client
#
openssl s_client — это консольная утилита, входящая в состав OpenSSL, которая позволяет:
- установить TCP + TLS соединение;
- получить цепочку сертификатов сервера;
- увидеть версию TLS и используемый cipher suite;
- вручную отправлять HTTP-запросы поверх TLS;
- диагностировать ошибки TLS-рукопожатия.
Фактически это «прозрачный» HTTPS-клиент без скрытой логики браузеров.
Проверка цепочки сертификатов #
В процессе TLS-handshake сервер передаёт клиенту свою сертификатную цепочку, которая обычно включает только серверный (leaf) сертификат и промежуточные (intermediate) сертификаты.
Корневой сертификат (root CA) сервер, как правило, не отправляет — он должен уже присутствовать в доверенном хранилище клиента.
Что именно присылает сервер #
Типичная корректная цепочка от сервера выглядит так:
- Серверный сертификат (leaf)
- Один или несколько промежуточных сертификатов (intermediate)
Корневой Root CA:
- не участвует в handshake
- не передаётся сервером
- используется клиентом локально для проверки подписи промежуточного сертификата
В браузере #
- Браузеры используют собственное хранилище доверенных корневых CA.
- Если сервер не прислал промежуточный сертификат, браузер может:
- автоматически докачать intermediate по AIA (Authority Information Access),
- сохранить его локально,
- построить полную цепочку доверия:
leaf → intermediate → root (локальный)
- Поэтому сайт может открываться в браузере без ошибок, даже если серверная цепочка неполная.
В OpenSSL (openssl s_client)
#
- OpenSSL НЕ докачивает промежуточные сертификаты.
- Для проверки используются:
- сертификаты, присланные сервером (leaf + intermediates),
- локальное trust store (
-CAfileили-CApath) — только для root CA.
- Root CA не берётся из сети, а ищется локально из базового каталога OpenSSL:
openssl version -d
OPENSSLDIR:/usr/lib/ssl
По умолчанию, -CAfile /usr/lib/ssl/cert.pem (симлинк на единое системное хранилище CA: /etc/ssl/certs/ca-certificates.crt)
Важно понимать разницу:
- Certificate chain — это цепочка, полученная от сервера
- verify depth / Verification: OK — результат проверки с использованием локальных доверенных root CA
Если сервер:
- не прислал промежуточный сертификат,
- и он отсутствует локально,
OpenSSL не сможет построить цепочку и завершит проверку ошибкой:
Verify return code: 21 (unable to verify the first certificate)
В curl #
Linux (OpenSSL / LibreSSL / GnuTLS)
- Поведение аналогично
openssl s_client:- используется только цепочка от сервера,
- плюс локальные доверенные root CA,
- промежуточные сертификаты не докачиваются автоматически.
Windows (Schannel:--ssl-backend schannel)
- Используется системное крипто-API Windows.
- Schannel:
- может автоматически докачивать промежуточные сертификаты,
- использует системное хранилище сертификатов,
- строит цепочку доверия аналогично браузеру.
⚠️ Итог
- Сервер не обязан и не должен отправлять root CA.
openssl s_clientне докачивает intermediate сертификаты и использует только цепочку от сервера + локальные root CA.- Браузеры и Windows Schannel могут автоматически получать промежуточные сертификаты по AIA.
- Поэтому сайт может работать в браузере, но падать в
openssl s_clientилиcurlна Linux при неполной серверной цепочке.
Корректное тестирование HTTPS с SNI #
Современные HTTPS-серверы используют SNI (Server Name Indication) для обслуживания нескольких доменов на одном IP-адресе.
Без SNI сервер может вернуть неверный сертификат.
openssl s_client -connect example.com:443
# или
openssl s_client -connect example.com:443 -servername example.com
Просмотр всех сертификатов, которые отправляет HTTPS сервер
openssl s_client -connect example.com:443 -servername example.com -showcerts </dev/null
Вывод:
*Certificate chain*
*0 s:/CN=example.com*
*i:/CN=Let's Encrypt Authority X3*
*Verify return code: 0 (ok)*
Коды проверки сертификата:
| Код | Значение |
|---|---|
| 0 | Сертификат корректен |
| 10 | Срок действия сертификата истёк |
| 20 | Неизвестный издатель |
| 21 | Невозможно проверить первый сертификат |
Просмотр сертификата в читаемом виде
openssl s_client -connect example.com:443 -servername example.com | openssl x509 -noout -text
Проверка срока действия сертификата
echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -dates
Проверка поддерживаемых TLS-версий
# TLS 1.2
openssl s_client -connect example.com:443 -servername example.com -tls1_2
# TLS 1.3
openssl s_client -connect example.com:443 -servername example.com -tls1_3
Проверка cipher suite
# Вывод текущего cipher
openssl s_client -connect example.com:443 -servername example.com
# Принудительное использование cipher suite (TLS 1.2)
openssl s_client -connect example.com:443 -servername example.com -cipher 'ECDHE-RSA-AES256-GCM-SHA384'
STARTTLS для протоколов с explicit TLS #
# SMTP
openssl s_client -starttls smtp -connect smtp.example.com:25
# IMAP
openssl s_client -starttls imap -connect imap.example.com:143
# POP3
openssl s_client -starttls pop3 -connect pop3.example.com:110
# FTP (explicit FTPS)
openssl s_client -starttls ftp -connect ftp.example.com:21
Принцип работы STARTTLS:
- Подключение по обычному TCP к порту сервиса.
- Отправка инициализационного handshake протокола (EHLO, CAPABILITY,..).
- Отправка команды STARTTLS.
- Переключение на TLS handshake.
- Проверка цепочки сертификатов, версии TLS, cipher suite, срока действия.
Пример тестирования HTTPS #
root@web1:~# openssl s_client -connect www.asterisker.com:443
CONNECTED(00000003)
depth=2 C = US, O = Internet Security Research Group, CN = ISRG Root X1
verify return:1
depth=1 C = US, O = Let's Encrypt, CN = E8
verify return:1
depth=0 CN = asterisker.com
verify return:1
---
Certificate chain
0 s:CN = asterisker.com
i:C = US, O = Let's Encrypt, CN = E8
a:PKEY: id-ecPublicKey, 256 (bit); sigalg: ecdsa-with-SHA384
v:NotBefore: Nov 25 20:50:13 2025 GMT; NotAfter: Feb 23 20:50:12 2026 GMT
1 s:C = US, O = Let's Encrypt, CN = E8
i:C = US, O = Internet Security Research Group, CN = ISRG Root X1
a:PKEY: id-ecPublicKey, 384 (bit); sigalg: RSA-SHA256
v:NotBefore: Mar 13 00:00:00 2024 GMT; NotAfter: Mar 12 23:59:59 2027 GMT
---
Server certificate
-----BEGIN CERTIFICATE-----
MIIDxzCCA0ygAwIBAgISBepzzJ46hVOTtMYnVzIIDlNyMAoGCCqGSM49BAMDMDIx
...
cP5Zp6Sj16QCMQC2tQMC57bmWZ2yTz79C5F3+rlRSHUKm4XsZIh60n3AADVeoYQA
TTIpOK9v5oAjCbs=
-----END CERTIFICATE-----
subject=CN = asterisker.com
issuer=C = US, O = Let's Encrypt, CN = E8
---
No client certificate CA names sent
Peer signing digest: SHA256
Peer signature type: ECDSA
Server Temp Key: X25519, 253 bits
---
SSL handshake has read 2464 bytes and written 400 bytes
Verification: OK
---
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
Server public key is 256 bit
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
Early data was not sent
Verify return code: 0 (ok)
---
Пример отправки HTTP-запроса вручную #
GET / HTTP/1.1
Host: example.com