サーバーがどのTLSバージョンや暗号スイートに対応しているか、SNI(Server Name Indication)でホスト名を切り替えられるか、といった接続の条件を調べたいときは、openssl s_clientのオプションを使う。

$ openssl s_client -connect example.com:443 -servername example.com -tls1_2 -brief < /dev/null
CONNECTION ESTABLISHED
Protocol version: TLSv1.2
Ciphersuite: ECDHE-ECDSA-CHACHA20-POLY1305
Peer certificate: CN = example.com
Hash used: SHA256
Signature type: ECDSA
Verification: OK
Server Temp Key: X25519, 253 bits
DONE

s_clientの基本的な使い方は、別の記事で説明している。

参考: 【openssl】指定したURLの証明書をコマンドで確認する

実行例はUbuntu 24.04のOpenSSL 3.0.13のもので、接続先はexample.comである。< /dev/nullは標準入力を空にして、接続後にすぐ終了させる指定である。

接続結果を要約して表示する

-briefを付けると、接続したTLSバージョンや暗号スイート、検証結果を短く表示する。

$ openssl s_client -connect example.com:443 -brief < /dev/null
CONNECTION ESTABLISHED
Protocol version: TLSv1.3
Ciphersuite: TLS_AES_256_GCM_SHA384
Peer certificate: CN = example.com
Hash used: SHA256
Signature type: ECDSA
Verification: OK
Server Temp Key: X25519, 253 bits
DONE

-briefを付けない場合は、証明書のPEMを含む長い出力になる。ProtocolとCipherの行だけを見たい場合は、grepで絞り込む。

$ openssl s_client -connect example.com:443 -tls1_2 < /dev/null 2>&1 | grep -E 'Protocol|Cipher '
New, TLSv1.2, Cipher is ECDHE-ECDSA-CHACHA20-POLY1305
    Protocol  : TLSv1.2
    Cipher    : ECDHE-ECDSA-CHACHA20-POLY1305

SNIでホスト名を指定する

1つのIPアドレスで複数のホスト名を扱うサーバーは、SNIで受け取ったホスト名に応じて返す証明書を切り替える。-servernameで、SNIに含めるホスト名を指定する。

$ openssl s_client -connect 104.20.23.154:443 -servername example.com -brief < /dev/null

-connectにホスト名を指定した場合、OpenSSL 3.xはそのホスト名を自動でSNIとして送信する。IPアドレスで接続する場合は、SNIが送られない。SNIを必要とするサーバーは、ハンドシェイクを拒否する。

$ openssl s_client -connect 104.20.23.154:443 -brief < /dev/null
error:0A000410:SSL routines:ssl3_read_bytes:sslv3 alert handshake failure:...:SSL alert number 40

-servernameでホスト名を指定すると、IPアドレス指定でも接続できる。DNSの切り替え前に、新しいサーバーのIPアドレスで証明書を確認したい場合に使う。

SNIを送りたくない場合は、-noservernameを指定する。SNIを必要とするサーバーでは、同じくハンドシェイクに失敗する。

$ openssl s_client -connect example.com:443 -noservername -brief < /dev/null
error:0A000410:SSL routines:ssl3_read_bytes:sslv3 alert handshake failure:...:SSL alert number 40

サーバーが知らないホスト名を-servernameで指定しても、失敗する場合がある。

$ openssl s_client -connect example.com:443 -servername wrong.invalid -brief < /dev/null
error:0A000410:SSL routines:ssl3_read_bytes:sslv3 alert handshake failure:...:SSL alert number 40

TLSのバージョンを指定する

-tls1_2や-tls1_3で、使うTLSのバージョンを1つに固定する。サーバーが特定のバージョンに対応しているか確認できる。

$ openssl s_client -connect example.com:443 -tls1_2 -brief < /dev/null 2>&1 | grep 'Protocol version'
Protocol version: TLSv1.2
$ openssl s_client -connect example.com:443 -tls1_3 -brief < /dev/null 2>&1 | grep 'Protocol version'
Protocol version: TLSv1.3

特定のバージョンだけを除外する場合は、-no_tls1_3のように指定する。範囲で指定する場合は、-min_protocolと-max_protocolを使う。

$ openssl s_client -connect example.com:443 -no_tls1_3 -brief < /dev/null 2>&1 | grep 'Protocol version'
Protocol version: TLSv1.2
$ openssl s_client -connect example.com:443 -min_protocol TLSv1.3 -brief < /dev/null 2>&1 | grep 'Protocol version'
Protocol version: TLSv1.3
$ openssl s_client -connect example.com:443 -max_protocol TLSv1.2 -brief < /dev/null 2>&1 | grep 'Protocol version'
Protocol version: TLSv1.2

サーバーが対応していないバージョンを指定すると、接続に失敗する。

古いバージョンの-tls1_1は、Ubuntu 24.04のOpenSSLが、クライアント側で無効にしているため、接続を試す前に失敗する。

$ openssl s_client -connect example.com:443 -tls1_1 -brief < /dev/null
error:0A0000BF:SSL routines:tls_setup_handshake:no protocols available:...

この失敗は、サーバーの設定ではなく、手元のOpenSSLの設定による。サーバーが古いバージョンに対応しているかを調べる場合は、別のツールやOpenSSLのビルドが必要になる。

暗号スイートを指定する

TLS 1.2以前の暗号スイートは-cipher、TLS 1.3の暗号スイートは-ciphersuitesで指定する。

$ openssl s_client -connect example.com:443 -tls1_2 -cipher ECDHE-RSA-AES128-GCM-SHA256 -brief < /dev/null 2>&1 | grep Ciphersuite
Ciphersuite: ECDHE-RSA-AES128-GCM-SHA256
$ openssl s_client -connect example.com:443 -tls1_3 -ciphersuites TLS_CHACHA20_POLY1305_SHA256 -brief < /dev/null 2>&1 | grep Ciphersuite
Ciphersuite: TLS_CHACHA20_POLY1305_SHA256

サーバーが対応していない暗号スイートだけを指定すると、接続に失敗する。特定の暗号スイートが無効になっているか確認する場合に使う。

ALPNでプロトコルを指定する

-alpnで、ALPN(Application-Layer Protocol Negotiation)に含めるプロトコルを指定する。サーバーがHTTP/2に対応しているか確認できる。

$ openssl s_client -connect example.com:443 -alpn h2,http/1.1 < /dev/null 2>&1 | grep ALPN
ALPN protocol: h2

h2はHTTP/2、http/1.1はHTTP/1.1を表す。ALPN protocolの行には、サーバーの選択結果が表示される。

STARTTLSで接続する

SMTPなど、平文で接続してからTLSに切り替えるプロトコルには、-starttlsを指定する。

$ echo QUIT | openssl s_client -connect smtp.gmail.com:587 -starttls smtp -brief
CONNECTION ESTABLISHED
Protocol version: TLSv1.3
...
Peer certificate: CN = smtp.gmail.com
Verification: OK

-starttlsには、smtpのほかにimap、pop3、ftp、xmpp、postgresなどを指定できる。使えるプロトコルはopenssl s_client -helpで確認できる。

-starttls smtpでは、接続後にSMTPのコマンドを受け付ける状態になる。echo QUITでSMTPのQUITコマンドを送って、接続を終了させる。

証明書を検証する

-verify_hostnameで、ホスト名が証明書に含まれているか検証する。ホスト名が一致しないと、hostname mismatchのエラーになる。

$ openssl s_client -connect example.com:443 -verify_hostname wrong.example -brief < /dev/null
depth=0 CN = example.com
verify error:num=62:hostname mismatch
CONNECTION ESTABLISHED
...

エラーがあっても、s_clientはデフォルトで接続を続ける。エラー時に接続を止める場合は、-verify_return_errorを付ける。

$ openssl s_client -connect example.com:443 -verify_hostname wrong.example -verify_return_error -brief < /dev/null
depth=0 CN = example.com
verify error:num=62:hostname mismatch
error:0A000086:SSL routines:tls_post_process_server_certificate:certificate verify failed:...

信頼するCAは、OSの信頼済みCAを使う。ca-certificatesが入っていない最小のコンテナなどでは、unable to get local issuer certificateになる。-CAfileで、CAの証明書を指定できる。

$ openssl s_client -connect example.com:443 -CAfile /etc/ssl/certs/ca-certificates.crt -brief < /dev/null

参考: 【openssl】証明書チェーンを検証する (verify)

OCSPステープリングを確認する

-statusを付けると、サーバーがOCSPの応答を添付(ステープリング)しているか確認できる。

$ openssl s_client -connect example.com:443 -status < /dev/null 2>&1 | grep -E 'OCSP (Response Status|response)'
OCSP response:
OCSP Response Data:
    OCSP Response Status: successful (0x0)

ステープリングに対応していないサーバーは、OCSP response: no response sentと表示する。

証明書を保存する

サーバーの証明書は、openssl x509にパイプで渡して、内容を確認したり、ファイルに保存したりできる。

$ openssl s_client -connect example.com:443 < /dev/null 2>/dev/null | openssl x509 -noout -subject -issuer -dates
subject=CN = example.com
issuer=C = US, O = SSL Corporation, CN = Cloudflare TLS Issuing ECC CA 3
notBefore=Sep 26 22:49:11 2026 GMT
notAfter=Dec 25 22:56:35 2026 GMT

-showcertsで、チェーンのすべての証明書が出力される。証明書ごとにファイルへ分割するには、awkを使う。

$ openssl s_client -connect example.com:443 -showcerts < /dev/null 2>/dev/null \
    | awk '/BEGIN CERT/{n++} n{print > "cert" n ".pem"}'
$ ls cert*.pem
cert1.pem  cert2.pem  cert3.pem  cert4.pem

cert1.pemがサーバー証明書、cert2.pem以降が中間CAなどの証明書である。チェーンの階層は、Certificate chainのs:(サブジェクト)の行でも確認できる。

$ openssl s_client -connect example.com:443 < /dev/null 2>&1 | sed -n '/Certificate chain/,/Server certificate/p' | grep -E '^ [0-9] s:'
 0 s:CN = example.com
 1 s:C = US, O = SSL Corporation, CN = Cloudflare TLS Issuing ECC CA 3
 2 s:C = US, O = SSL Corporation, CN = SSL.com TLS Transit ECC CA R2
 3 s:C = US, O = SSL Corporation, CN = SSL.com TLS ECC Root CA 2022

HTTPリクエストを送る

-quietを付けると、接続情報を表示せず、TLSの上でやり取りしたデータだけを標準出力に出す。HTTPのリクエストを標準入力に渡して、TLSの上で実際にリクエストを送れる。

$ printf 'GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n' \
    | openssl s_client -connect example.com:443 -quiet 2>/dev/null | head -1
HTTP/1.1 200 OK

バージョンによる違い

OpenSSL 3.6でも、ここまでの例は同じように動く。-briefの出力の先頭に、Connecting to [IPアドレス]という行が加わる。

macOS標準の/usr/bin/openssl(LibreSSL 3.3.6)では、以下が異なる。

  • -briefと-verify_hostnameに対応していない
  • -connectにホスト名を指定しても、SNIを自動で送らない。SNIを必要とするサーバーでは、-servernameの指定が必須になる
  • ProtocolとCipherの行は、通常の出力に表示される
$ /usr/bin/openssl s_client -connect example.com:443 -servername example.com < /dev/null 2>&1 | grep -E '^ 0 s:|Verify return'
 0 s:/CN=example.com
    Verify return code: 0 (ok)

macOSで、SNIの有無やTLSバージョンを詳しく調べる場合は、Homebrewなどでインストールしたopensslを使う。

参考

openssl-s_client - OpenSSL Documentation