Dev Study
← HTTPとTLSの仕組み
レッスン 12 / 12·約 8 分·第18期 HTTPとTLSの仕組み

どの版で話すかは、話し始める前に決まっている

リクエストがアプリケーションに届くまで
  1. ブラウザURL入力今ここ
  2. DNS名前→IP今ここ
  3. ルータ経路選択
  4. ロードバランサ振り分け
  5. Nginx受け口
  6. アプリ処理
  7. DB保存

前回は、共有する表が届く順序を要求することを見ました。最後に、どの版で話すかがいつ決まるのかを見ます。答えは「最初の HTTP のバイトより前」です。

まず結論

HTTPS で HTTP/2 と HTTP/1.1 のどちらを話すかは、TLS ハンドシェイクの中で ALPN という拡張が決めます。クライアントが候補を出し、サーバが1つ選んで返すだけで、ハンドシェイクで元々送るメッセージに載るので往復は増えません。

ただし TCP で始めるか QUIC で始めるかは、ハンドシェイクより前に決める必要があります。この知識を事前に共有する手段が Alt-Svc と HTTPS レコードです。共有していれば往復は要らず、していなければ試すか、知るための往復を払います。

? 読む前に予測

https の URL に HTTP/2 と HTTP/1.1 のどちらで話すかを決めるために、TLS ハンドシェイクへ追加される往復はいくつでしょうか。

当てることが目的ではありません。外した予測ほど記憶に残るので、直感で選んでください。

抽象原理

候補を出し、1つ選んで返す

同じ 443 番ポートで HTTP/1.1 も HTTP/2 も話せるなら、どちらで話すかをどこかで決めなければなりません。RFC 7301 は、その決め方に条件を付けています。

It is desirable to accomplish this negotiation without adding network round-trips between the client and the server, as each round-trip will degrade an end-user's experience.

仕組みは単純です。クライアントは候補を in descending order of preference(希望の高い順)に ClientHello へ載せ、選ぶのはサーバです。返す値は exactly one(1つだけ)で、選んだあとで別のプロトコルに変えることは SHALL NOT とされます。重なる候補が無ければ、サーバは致命的アラート no_application_protocol を返します。

RFC 7301 は返事を ServerHello に載せると書いていますが、これは TLS 1.2 の時代の記述です。TLS 1.3 の現行仕様 RFC 9846(2026年7月。TLS 1.3 を定めていた RFC 8446 を廃止した改訂版)の拡張の表では、ALPN は CH, EE、つまり ClientHello と EncryptedExtensions に載ります。そして EncryptedExtensions には、次の規定があります。

In all handshakes, the server MUST send the EncryptedExtensions message immediately after the ServerHello message.

どのハンドシェイクでも必ず送る封筒に、返事が相乗りしています。 ALPN のためのメッセージは増えず、往復も増えません。

ALPN は、元々交わすメッセージに候補と選択を載せる
ClientHello候補: h2, http/1.1(希望順)
サーバが1つ選ぶ
ServerHello
EncryptedExtensions選択: h2(暗号化済み)
続けて送られる
Certificate / CertificateVerify / Finished
最初の HTTP のバイト
HTTP/2 のプリフェイス
  • ・ALPN の有無でメッセージの並びは変わらない(実測)
  • ・重なる候補が無ければ致命的アラート 120 で終わる
具体実物

openssl s_client で合意を見る

OpenSSL 3.6.3 の s_server を2つ立て、片方だけに -alpn h2,http/1.1 を付けます。クライアントは openssl s_client -alpn h2,http/1.1 で接続しました。

s_client -alpn h2,http/1.1 の結果(grep で抽出。run1。run2 も同一)コマンド出力
=== run1 s_client -alpn h2,http/1.1 -> 18461 (server: no -alpn) 2026-09-24 11:06:22
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
No ALPN negotiated
Verify return code: 0 (ok)
=== run1 s_client -alpn h2,http/1.1 -> 18462 (server: -alpn h2,http/1.1) 2026-09-24 11:06:22
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
ALPN protocol: h2
Verify return code: 0 (ok)

ALPN を設定していないサーバは拡張を無視し(The server will ignore any protocol name that it does not recognize)、ハンドシェイクは成功しますが、版は決まりません。-trace で後者のメッセージを見ます。

s_client -trace(見出し行と ALPN の行だけを grep で抽出。末尾の Sent TLS Record 2行は省略。run1)追跡ログ
Sent TLS Record
ClientHello, Length=1549
extension_type=application_layer_protocol_negotiation(16), length=14
h2
http/1.1
Received TLS Record
ServerHello, Length=1206
Received TLS Record
Received TLS Record
EncryptedExtensions, Length=11
extension_type=application_layer_protocol_negotiation(16), length=5
h2
Received TLS Record
Certificate, Length=910
Received TLS Record
CertificateVerify, Length=75
Received TLS Record
Finished, Length=48
Sent TLS Record
Sent TLS Record
Finished, Length=48
ALPN protocol: h2

候補2つは ClientHello に、選ばれた h2 1つは EncryptedExtensions に入っています。長さは 14 = 2(リスト長)+ 3(h2)+ 9(http/1.1)、5 = 2 + 3 です。ALPN の有無でメッセージの並びは変わりませんでした。

サーバの候補を foo だけにすると、重なりが無くなります。

サーバ -alpn foo に s_client -alpn h2,http/1.1(grep で抽出。Verify return code の行と SSL_shutdown のエラー行は省略。run1。run2 も同一)コマンド出力
80DDEDF501000000:error:0A000460:SSL routines:ssl3_read_bytes:tlsv1 alert no application protocol:ssl/record/rec_layer_s3.c:918:SSL alert number 120
New, (NONE), Cipher is (NONE)
No ALPN negotiated

「設定していない」と「重ならない」は結果が違います。前者は合意なしで続き、後者は RFC 7301 §3.2 のとおり接続が終わります。

✓ ここまでの確認

RFC 7301 は「サーバは選んだプロトコルを ServerHello で返す」と書いています。TLS 1.3 の実測では、選択はどこに入っていましたか。

抽象原理

ALPN では決められないもの

ALPN が往復を足さずに済むのは、TLS ハンドシェイクがどうせ行われ、その中で話せるからです。では HTTP/3 はどうでしょうか。

HTTP/3 の TLS ハンドシェイクは QUIC 接続の中で行われます。つまり TCP で始めるか QUIC で始めるかは、ALPN より前に決めなければなりません。 RFC 9460 §7.1 は、HTTPS レコードの ALPN の集合について this informs the underlying transport protocol used (such as QUIC over UDP or TLS over TCP) と書いています。

この「前に決める」ための知識を、両端はまだ共有していません。手段は3つです。

  • 知らずに試す。 RFC 9114 §3.1 は A client MAY attempt access ... establishing a QUIC connection と書き、UDP が遮られていれば clients SHOULD attempt to use TCP-based versions of HTTP とします。
  • 応答で教わる。 Alt-Svc です。ただし教わるのは HTTP の応答を1回受け取ったあとで、RFC 9460 はこれを without waiting for a full HTTP connection initiation (multiple round trips) と対比しています。
  • DNS で先に教わる。 HTTPS レコードです。接続を始める前、名前解決の段階で分かります。

TCP の上の HTTP/2 にも、同じ構図の古い手段がありました。平文で HTTP/1.1 として話し始め、Upgrade で h2c へ切り替える方法です。RFC 9113 §3.1 はこれを never widely deployed and is deprecated としました。ただし §3.3 の事前知識による平文 HTTP/2 は残っていて、こちらは prior support for HTTP/2 is not a strong signal と注意されています。

具体実物

Alt-Svc と HTTPS レコードを並べて読む

ある公開サイトに curl -sI を1回送ったときの応答の1行と、同じドメインの HTTPS レコードです(2026-09-24 に1回ずつ読み取った結果。ドメイン名とアドレスのヒントは省略しています)。

応答ヘッダ(curl -sI、1回)と HTTPS レコード(Debian の dig 9.20)コマンド出力
alt-svc: h3=":443"; ma=86400
 
<ドメイン>. 377 IN HTTPS 1 . alpn="h3,h2" ipv4hint=(省略) ipv6hint=(省略)

言っている内容はほぼ同じで、「このサーバは h3 も話せる」です。ma=86400 は秒単位で24時間の有効期限を示します(RFC 7838 は ma が無い場合も24時間とします)。

違うのはいつ知れるかです。alt-svc の行は、TCP の上で最初の応答を受け取ったあとに届きます。HTTPS レコードは接続前に届きます。

どちらも強制ではありません。RFC 7838 は recipients of Alt-Svc can ignore the header field と書き、RFC 9114 §3.1.1 でも QUIC の接続を試すことは MAY です。HTTPS レコードを使っても、ハンドシェイク内の交渉は proceeds as specified in [ALPN], without regard to the SVCB ALPN set(RFC 9460 §7.1.2)で、最後に決めるのは ALPN です。

この1サイトの設定を一般化しないでください。ブラウザがどの手段をどの順で使うかも、RFC の外の話です。

✓ ここまでの確認

「HTTP/3 は、まず TCP で接続して Alt-Svc を受け取ってからでないと始められない」という説明の扱いとして適切なのはどれですか。

⇄ 2つを見比べる
具体例 A

ALPN(アプリケーションの版)

候補は ClientHello に、選択は EncryptedExtensions に載ります。どちらも ALPN が無くても送るメッセージです。決める場を両端が共有しているので、往復は増えません。

載る場所追跡ログ
ClientHello : h2, http/1.1
EncryptedExtensions : h2
具体例 B

Alt-Svc(輸送の選択)

TCP か QUIC かは、ハンドシェイクを始める前に決める必要があります。Alt-Svc で知るには、まず別の接続で HTTP の応答を1回受け取る必要があります。HTTPS レコードなら、この知識を DNS の段階で先に渡せます。

届くのは最初の応答の中コマンド出力
alt-svc: h3=":443"; ma=86400

どちらも「どのプロトコルで話すか」を決める仕組みです。一方は往復0で済み、もう一方は往復を払います。何がこの差を生んでいるのでしょうか。

決めるための情報を交わす場が、決める時点ですでに開いているかどうかを比べてください。

この章のまとめ

  • ALPN: クライアントが希望順に候補を出し、サーバが1つ選ぶ。TLS 1.3 では選択は EncryptedExtensions に載る。
  • EncryptedExtensions は全ハンドシェイクで必ず送られる。ALPN は相乗りするだけで、往復は増えない。
  • ALPN を設定していなければ合意なしで続き、重なりが無ければアラート 120 で終わる。
  • TCP か QUIC かは ALPN より前に決める。直接 QUIC を試す(MAY)、Alt-Svc で教わる、HTTPS レコードで先に知る、のいずれか。どれも強制ではない。
  • h2c への Upgrade は非推奨。TLS 上では h2c を使えない。

このコースの芯は「通信の仕組みは、両端が何を共有しているかで決まる。共有していれば省略でき、共有していなければ往復が要る」でした。版の合意も同じです。決める場を共有していれば往復0、共有していなければ、試すか教わるための往復を払います。

自分の現場で確かめる

読んだだけでは身につきません。手元の環境で実際に見てみてください。

環境:

理解度チェック

読んだ直後は「分かった」と感じやすいものです。実際に解いて確かめてください。

問題 1 / 3正解: 0

クライアントが ALPN で `h2,http/1.1` を提示しました。サーバ側の状況と結果の組み合わせとして、実測と RFC 7301 に合うのはどれですか。

用語集(4語)
ALPN
TLS 拡張(RFC 7301)。クライアントが希望順に候補を出し、サーバが1つだけ選ぶ。TLS 1.3 では選択を EncryptedExtensions で返す。
Alt-Svc
HTTP の応答ヘッダ(RFC 7838)で、別の接続先や別のプロトコルを広告する。受け取った側は無視してよい。HTTP の応答を1回受け取るまで分からない。
HTTPS レコード
DNS の型 65(RFC 9460)。対応する ALPN などを名前解決の段階で知らせる。既定の ALPN は http/1.1。
h2c
TLS を使わない HTTP/2 の識別子。Upgrade による切り替えは RFC 9113 で非推奨。TLS 上で送ること・選ぶことは MUST NOT。