- JAVA
SSLHandshakeExceptionの直し方|alert別の原因表
javax.net.ssl.SSLHandshakeException は、TLS の最初のやりとり(ハンドシェイク:使う版・暗号・証明書を決める手順)が失敗したときの例外です。 原因はメッセージでほぼ決まります。まず、Received fatal alert があるかどうかを見ます。
| メッセージ | 断ったのは | 主な原因 |
|---|---|---|
Received fatal alert: protocol_version |
相手(サーバー) | サーバーが TLS 1.0・1.1 しか使えない |
Received fatal alert: handshake_failure |
相手 | 共通の暗号スイートが無い。TLS 1.2 でクライアント証明書を出していない |
Received fatal alert: certificate_required |
相手 | TLS 1.3 でクライアント証明書を出していない |
Received fatal alert: unrecognized_name |
相手 | SNI(接続先の名前)がサーバーの設定と違う |
No appropriate protocol (protocol is disabled or cipher suites are inappropriate) |
自分(Java) | Java 側で使える TLS の版が無い |
No subject alternative DNS name matching 〈名前〉 found. |
自分 | 証明書の名前(SAN)が接続先と違う |
No subject alternative names matching IP address 〈IP〉 found |
自分 | IP で接続したが、証明書に IP の SAN が無い |
No subject alternative names present |
自分 | IP で接続したが、証明書に SAN が1つも無い |
No name matching 〈名前〉 found |
自分 | 証明書に SAN が無く、CN(名前の欄)も違う |
PKIX path validation failed: … validity check failed |
自分 | 証明書の期限切れ(またはまだ有効でない) |
PKIX path building failed: … unable to find valid certification path |
自分 | 自己署名・社内 CA など、信頼する CA に無い証明書 |
Received fatal alert: … は「相手から断られた」、それ以外は「Java が断った」という意味です。 相手から断られたときは、理由が相手側のログにしか残らないことが多いので、下の -Djavax.net.debug で自分が何を送ったかを確かめます。
確認環境:OpenJDK 1.8.0_504・11.0.32.1・17.0.20.1・21.0.10・25.0.4.1(Linux)。OpenSSL 3.0.13 の openssl s_server で、条件を変えた TLS サーバーを12通り立て、Java の SSLSocket(ホスト名の検証あり)で接続してメッセージを集めました。HttpsURLConnection と java.net.http.HttpClient でも、Java 21 で同じメッセージになることを確かめています。Java がサーバー側の場合は、Java 8・21 の SSLServerSocket に openssl s_client から接続して確かめました(確認日 2026年10月13日)。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「SSLHandshakeExceptionの直し方|alert別の原因表」
メッセージの読み方
Java 17 以降は、先頭に(アラート名)が付く
同じ失敗でも、Java 17 からメッセージの先頭に括弧付きのアラート名が付きました。
| 版 | メッセージ |
|---|---|
| Java 8・11 | Received fatal alert: handshake_failure |
| Java 17・21・25 | (handshake_failure) Received fatal alert: handshake_failure |
| Java 8・11 | No subject alternative DNS name matching app.example.test found. |
| Java 17・21・25 | (certificate_unknown) No subject alternative DNS name matching app.example.test found. |
Received が無いのに括弧がある場合、括弧の中は「Java が相手に送ったアラート」です。 上の2つ目は、Java が証明書を受け入れず、certificate_unknown を送って切断したことを表します。
原因は getCause() の奥にある
証明書の問題では、SSLHandshakeException の中に原因が何段も入っています。期限切れの例(Java 21)です。
javax.net.ssl.SSLHandshakeException: (certificate_expired) PKIX path validation failed: java.security.cert.CertPathValidatorException: validity check failed
cause: sun.security.validator.ValidatorException: PKIX path validation failed: java.security.cert.CertPathValidatorException: validity check failed
cause: java.security.cert.CertPathValidatorException: validity check failed
cause: java.security.cert.CertificateExpiredException: NotAfter: Wed Jan 01 09:00:00 JST 2025
いちばん奥の CertificateExpiredException: NotAfter: … に、証明書の期限の日時が出ます。 ログに Caused by まで出しておくと、原因がすぐ分かります。
原因別のメッセージ一覧(Java 8〜25 の実行結果)
openssl s_server の設定と、Java の結果です。Java 17・21・25 は、先頭の(アラート名)以外は Java 8・11 と同じ文面でした。
TLS の版・暗号スイート
| サーバーの設定 | Java 8 | Java 11 | Java 17・21・25 |
|---|---|---|---|
| 制限なし | 成功(TLSv1.3) | 成功(TLSv1.3) | 成功(TLSv1.3) |
TLS 1.0・1.1 だけ(-no_tls1_2 -no_tls1_3) |
Received fatal alert: protocol_version |
同左 | (protocol_version) Received fatal alert: protocol_version |
| Java 側を TLSv1.1 だけにした(サーバーは制限なし) | No appropriate protocol (protocol is disabled or cipher suites are inappropriate) |
同左 | 同左 |
TLS 1.2・DHE-RSA-CAMELLIA128-SHA だけ |
Received fatal alert: handshake_failure |
同左 | (handshake_failure) Received fatal alert: handshake_failure |
TLS 1.2・ECDHE-RSA-CHACHA20-POLY1305 だけ |
Received fatal alert: handshake_failure |
成功 | 成功 |
TLS 1.2・AES128-GCM-SHA256(TLS_RSA)だけ |
成功 | Received fatal alert: handshake_failure |
(handshake_failure) Received fatal alert: handshake_failure |
protocol_versionは「サーバーが TLS 1.2 より前の版しか使えない」ときに、サーバーから返ってきましたNo appropriate protocolは、Java 自身が使える版を持っていないときに、通信の前に出ました。TLS 1.0・1.1 は、確かめた5つの版すべてで既定で無効でした(下のjdk.tls.disabledAlgorithms)- 同じサーバーでも、Java の版で結果が逆になる組み合わせがありました。 ChaCha20 は Java 8u504 では使えず、TLS_RSA(鍵交換に RSA を使う暗号スイート)は Java 11.0.32.1 以降で無効になっていました
証明書
| 状況 | メッセージ(Java 8・11。17 以降は先頭に括弧付きのアラート名) | 17 以降の括弧 |
|---|---|---|
SAN が別の名前(other.example.test) |
No subject alternative DNS name matching app.example.test found. |
(certificate_unknown) |
| IP で接続、SAN に IP が無い | No subject alternative names matching IP address 127.0.0.1 found |
(certificate_unknown) |
| IP で接続、SAN が1つも無い | No subject alternative names present |
(certificate_unknown) |
| SAN が無く、CN も違う | No name matching foo.example.test found |
(certificate_unknown) |
| 期限切れ | PKIX path validation failed: java.security.cert.CertPathValidatorException: validity check failed |
(certificate_expired) |
| 自己署名(信頼していない) | PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target |
(certificate_unknown) |
SAN(Subject Alternative Name)は、証明書の中の「この証明書が使える名前の一覧」です。 確かめた5つの版では、SAN が無く CN が接続先と同じ証明書は、名前で接続すると成功しました。 SAN が無い証明書は、Java では通っても、他のクライアント(ブラウザなど)では通らないことがあります。証明書を作り直すときは SAN を入れます。
# SAN 付きの証明書要求を作る例(OpenSSL 3 系)
openssl req -newkey rsa:2048 -nodes -keyout server.key -out server.csr \
-subj "/CN=app.example.test" \
-addext "subjectAltName=DNS:app.example.test,IP:10.0.0.5"
# サーバーが返す証明書の SAN と期限を確かめる
openssl s_client -connect app.example.test:443 -servername app.example.test </dev/null 2>/dev/null \
| openssl x509 -noout -subject -ext subjectAltName -dates
自己署名や社内 CA の証明書で出る PKIX path building failed は、関連記事「PKIX path building failedの原因と直し方|Java証明書」で扱っています。openssl の使い方は、関連記事「opensslコマンドの使い方|目的別一覧と実行結果・エラー対処【3系】」で扱っています。
クライアント証明書・SNI
| サーバーの設定 | Java 8・11 | Java 17・21・25 |
|---|---|---|
| クライアント証明書を必須(TLS 1.3)、Java は出さない | Received fatal alert: certificate_required |
(certificate_required) Received fatal alert: certificate_required |
| クライアント証明書を必須(TLS 1.3)、Java が出す | 成功 | 成功 |
| クライアント証明書を必須(TLS 1.2)、Java は出さない | Received fatal alert: handshake_failure |
(handshake_failure) Received fatal alert: handshake_failure |
SNI で app.example.test 以外を拒否、Java が wrong.example.test を送る |
Received fatal alert: unrecognized_name |
(unrecognized_name) Received fatal alert: unrecognized_name |
| SNI で証明書を切り替え、Java が SNI を送らない | No subject alternative DNS name matching app.example.test found. |
(certificate_unknown) +同じ文面 |
- クライアント証明書の不足は、TLS 1.3 なら
certificate_required、TLS 1.2 ならhandshake_failureになりました。 TLS 1.2 では、暗号スイートの不一致と同じアラートなので、見分けがつきません - SNI(Server Name Indication)は、接続先のホスト名をサーバーに伝えるしくみです。 1つの IP で複数のサイトを動かしているサーバーは、SNI を見て証明書を選びます。SNI を送らないと、別のサイトの証明書が返ってきて、名前違いのエラーになりました(
-Djsse.enableSNIExtension=falseで再現)
クライアント証明書を Java から出すには、PKCS12 のキーストアを指定します。
java -Djavax.net.ssl.keyStore=client.p12 \
-Djavax.net.ssl.keyStorePassword=changeit \
-Djavax.net.ssl.keyStoreType=PKCS12 \
-jar app.jar
Java がサーバーのとき
Java の SSLServerSocket に openssl s_client から接続したときの、Java(サーバー)側の例外です。
| 状況 | Java 8 | Java 21 |
|---|---|---|
| 共通の暗号スイートが無い | no cipher suites in common |
(handshake_failure) no cipher suites in common |
| TLS 1.3 だけにしたサーバーに TLS 1.2 で接続 | Client requested protocol TLSv1.2 is not enabled or supported in server context |
(protocol_version) +同じ文面 |
| クライアント証明書を必須にし、相手が出さない | Empty client certificate chain |
(certificate_required) Empty client certificate chain |
接続した openssl s_client 側には、sslv3 alert handshake failure(アラート番号 40)・tlsv1 alert protocol version(アラート番号 70)と出ました。クライアントで handshake_failure を受け取ったら、サーバー側のログに no cipher suites in common などの本当の理由が出ていないかを確かめます。
デバッグ出力(javax.net.debug)の読み方
java -Djavax.net.debug=ssl:handshake -jar app.jar 2> tls-debug.log
Java 21 で、暗号スイートが合わないサーバー(DHE-RSA-CAMELLIA128-SHA だけ)に接続したときは、399行出ました。Java 8 では 84行でした。見るところは4か所です。
① 自分が送った ClientHello(送った版・暗号スイート・SNI)
javax.net.ssl|DEBUG|10|main|…|ClientHello.java:638|Produced ClientHello handshake message (
"ClientHello": {
"client version" : "TLSv1.2",
"cipher suites" : "[TLS_AES_256_GCM_SHA384(0x1302), TLS_AES_128_GCM_SHA256(0x1301), …]",
"extensions" : [
"server_name (0)": {
type=host_name (0), value=app.example.test
},
…
"supported_versions (43)": {
"versions": [TLSv1.3, TLSv1.2]
},
"client version": "TLSv1.2" は TLS 1.3 でも同じ値です。 実際に使える版は supported_versions の一覧で見ます。server_name が無ければ、SNI を送っていません。
② 相手から返ってきたアラート
javax.net.ssl|DEBUG|10|main|…|Alert.java:232|Received alert message (
"Alert": {
"level" : "fatal",
"description": "handshake_failure"
}
)
ServerHello(サーバーの返事)が出る前にアラートが来ていれば、サーバーは ClientHello の中身(版・暗号スイート)を受け入れられなかったと読めます。
③ Java が使わなかった暗号スイート
…|SSLContextImpl.java:388|Ignore disabled cipher suite: TLS_RSA_WITH_AES_128_GCM_SHA256
Ignore disabled cipher suite は、jdk.tls.disabledAlgorithms などで無効にされていて、送らなかった暗号スイートです。 サーバーが使える暗号スイートがここに並んでいれば、原因は Java 側の設定です。
④ 最後のエラー
javax.net.ssl|ERROR|10|main|…|TransportContext.java:375|Fatal (HANDSHAKE_FAILURE): Received fatal alert: handshake_failure (
Java 8 では、ログの書式が javax.net.ssl|FINE|…・javax.net.ssl|SEVERE|… のように、レベル名が違いました。
出す量を変える
-Djavax.net.debug=help で出した一覧(Java 21)の主なものです。
| 指定 | 出るもの |
|---|---|
ssl:handshake |
ハンドシェイクのメッセージ(まずこれ) |
ssl:handshake:verbose |
ハンドシェイクを詳しく |
ssl:trustmanager |
証明書の検証(信頼する証明書の一覧など) |
ssl:keymanager |
自分の証明書(クライアント証明書)の選択 |
ssl:record |
TLS のレコード(通信の単位)ごと |
all |
すべて(量が多い) |
jdk.tls.disabledAlgorithms の確認方法
Java が使わない TLS の版・暗号は、java.security ファイルの jdk.tls.disabledAlgorithms で決まっています。 5つの版の値です(同じプログラムで Security.getProperty("jdk.tls.disabledAlgorithms") を表示)。
| 版 | TLSv1・TLSv1.1 | TLS_RSA_* | SHA-1 署名(ハンドシェイク) |
|---|---|---|---|
| OpenJDK 1.8.0_504 | 無効 | 有効 | 有効 |
| OpenJDK 11.0.32.1 | 無効 | 無効 | 無効 |
| OpenJDK 17.0.20.1 | 無効 | 無効 | 無効 |
| OpenJDK 21.0.10 | 無効 | 無効 | 無効 |
| OpenJDK 25.0.4.1 | 無効 | 無効 | 無効 |
Java 21 の値です。
SSLv3, TLSv1, TLSv1.1, DTLSv1.0, RC4, DES, MD5withRSA, DH keySize < 1024, EC keySize < 224, 3DES_EDE_CBC, anon, NULL, ECDH, TLS_RSA_*, rsa_pkcs1_sha1 usage HandshakeSignature, ecdsa_sha1 usage HandshakeSignature, dsa_sha1 usage HandshakeSignature
OpenJDK のバグ管理では、TLS 1.0・1.1 の無効化は JDK-8202343(8u291・11.0.11 などにバックポート)、TLS_RSA の無効化は JDK-8245545 です。同じ「Java 11」「Java 8」でも、更新版(アップデート)の番号で値が違います。 本番と手元で結果が違うときは、まずこの値を比べます。
値を確かめる3つの方法
# 1. ファイルを見る(Java 9 以降は conf/security、Java 8 は jre/lib/security)
grep -A3 '^jdk.tls.disabledAlgorithms' "$JAVA_HOME/conf/security/java.security"
grep -A3 '^jdk.tls.disabledAlgorithms' "$JAVA_HOME/jre/lib/security/java.security"
# 2. 実際に使える版と暗号スイートを表示(確かめた版では 17 以降で使えた)
java -XshowSettings:security:tls -version
// 3. プログラムから表示(全版)
System.out.println(java.security.Security.getProperty("jdk.tls.disabledAlgorithms"));
Java 21 の -XshowSettings:security:tls は、Enabled Protocols に TLSv1.3・TLSv1.2 を、Enabled Cipher Suites に使える暗号スイートを表示しました。Linux のパッケージで入れた JDK では、java.security が /etc/java-21-openjdk/security/java.security などへのリンクになっていることがあります。
TLS 1.0・1.1 しか使えない相手に、一時的につなぐ
相手のサーバーを直せないときの回避策として、そのアプリだけで jdk.tls.disabledAlgorithms を上書きできます。JDK 全体の java.security を書き換えるより、影響を小さくできます。
# legacy.security(TLSv1・TLSv1.1・TLS_RSA_* などを外した値)
jdk.tls.disabledAlgorithms=SSLv3, RC4, DES, MD5withRSA, DH keySize < 1024, EC keySize < 224, 3DES_EDE_CBC, anon, NULL
java -Djava.security.properties=legacy.security -jar app.jar
TLS 1.0・1.1 だけのサーバーに、この設定で Java 8・11・21・25 から接続すると、TLSv1.1・TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA で接続できました。-Djava.security.properties が効くのは、java.security の security.overridePropertiesFile=true(Java 21 の既定)のときです。TLS 1.0・1.1 は安全でないため無効になっています。 回避策は期限を決めて使い、相手の TLS 1.2 以上への対応を進めます。
原因別の直し方
| メッセージ | 直し方 |
|---|---|
protocol_version |
相手を TLS 1.2 以上に対応させる。急ぎなら上の一時回避 |
No appropriate protocol |
jdk.tls.client.protocols・https.protocols・setEnabledProtocols で TLS 1.2 以上を外していないか確かめる |
handshake_failure(版は合っている) |
debug 出力の ClientHello の暗号スイートと、サーバーの設定を比べる。サーバー側のログも見る |
handshake_failure(TLS 1.2・mTLS) |
クライアント証明書のキーストアを指定する |
certificate_required |
同上(TLS 1.3) |
unrecognized_name |
接続先の名前(URL のホスト名)を、サーバーに設定された名前にする |
No subject alternative …・No name matching … |
証明書の SAN に接続先の名前・IP を入れる。または、証明書にある名前で接続する |
validity check failed |
証明書を更新する。サーバーとクライアントの時刻も確かめる |
PKIX path building failed |
信頼する CA をトラストストアに入れる。中間証明書の付け忘れも確かめる |
ホスト名の検証を無効にする(すべての証明書を信頼する)コードは、名前違いのエラーは消えますが、通信相手のなりすましを防げなくなります。 証明書か接続先の名前を直すのが本来の直し方です。証明書の自動更新は、関連記事「Let's Encrypt certbot自動更新|45日化とnginx設定手順」で扱っています。
よくある質問
Q. curl ではつながるのに、Java ではつながりません。
A. 使える TLS の版・暗号スイート・信頼する CA の一覧が、curl(OpenSSL)と Java で別々です。確かめた範囲では、TLS_RSA だけのサーバーには Java 8u504 だけがつながり、ChaCha20 だけのサーバーには Java 8u504 だけがつながりませんでした。-Djavax.net.debug=ssl:handshake で Java が送った内容を見ます。
Q. Java の版を上げたら、急に handshake_failure が出ました。
A. 新しい版で jdk.tls.disabledAlgorithms に追加された暗号(TLS_RSA など)しか、相手が使えない可能性があります。上の表のように、版ごとの値を比べます。
Q. タイムアウトや接続拒否と、どう見分けますか。
A. SSLHandshakeException は、TCP の接続はできたあとの失敗です。接続そのものができないときは ConnectException や SocketTimeoutException になります。こちらは関連記事「SocketTimeoutExceptionの直し方|Read/connectの違い」で扱っています。HTTP クライアントの使い方は、関連記事「Java HttpClientの使い方|GET・POSTでJSON送信」で扱っています。
まとめ
Received fatal alert: …は相手が断った、それ以外は Java が断った- Java 17 以降は、メッセージの先頭に (アラート名) が付く
protocol_versionは相手が TLS 1.0・1.1 だけ、No appropriate protocolは Java 側で使える版が無いhandshake_failureは暗号スイートの不一致か、TLS 1.2 でのクライアント証明書の不足。TLS 1.3 ではcertificate_required- 名前違いは
No subject alternative …・No name matching …。証明書の SAN を確かめる -Djavax.net.debug=ssl:handshakeで ClientHello・アラート・無視した暗号スイートを見るjdk.tls.disabledAlgorithmsは更新版ごとに違う。-XshowSettings:security:tls(17 以降)かSecurity.getPropertyで確かめる
参考・出典
確認日はいずれも 2026年10月13日です。
- Java SE 21 API「SSLHandshakeException」:https://docs.oracle.com/en/java/javase/21/docs/api/java.base/javax/net/ssl/SSLHandshakeException.html
- Java SE 21「Java Secure Socket Extension (JSSE) Reference Guide」:https://docs.oracle.com/en/java/javase/21/security/java-secure-socket-extension-jsse-reference-guide.html
- Java SE 21「Troubleshooting Security」:https://docs.oracle.com/en/java/javase/21/security/troubleshooting-security.html
- OpenJDK「JDK-8202343 Disable TLS 1.0 and 1.1」:https://bugs.openjdk.org/browse/JDK-8202343
- OpenJDK「JDK-8245545 Disable TLS_RSA cipher suites」:https://bugs.openjdk.org/browse/JDK-8245545
- OpenSSL 3.0「openssl-s_server」:https://docs.openssl.org/3.0/man1/openssl-s_server/
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「SSLHandshakeExceptionの直し方|alert別の原因表」
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。