HTTP 421 Misdirected Request:再利用したHTTP/2接続が別のサーバーに届くとき

HTTP/2では1本の接続で複数のオリジンへのリクエストを送れるが、それでもサーバーは421で拒否できる。権限、接続の再利用、新しい接続での再試行についてRFC 9110とRFC 9113の定めをまとめた。

http2rfc-9110rfc-9113networkprotocol

証明書は有効で、TLSハンドシェイクも成功したのに、レスポンスが421 Misdirected Requestで返ってくる。サーバーが言っているのは、リソースがないということでも、権限がないということでもない。このリクエストが間違った接続で届いたという意味だ。なぜそうなるのかを理解するには、HTTP/2クライアントがどんなときに複数のホスト名へのリクエストを1本の接続で送れるのかを知っておく必要がある。

この記事はRFC 9110(HTTP Semantics、2022年6月)の4.3節、7.2節、7.3節、7.4節、9.2.2節、15.5.20節、17.1節、付録B.3と、RFC 9113(HTTP/2、2022年6月)の8.3.1節、9.1節、9.1.1節、10.1節、付録Bに基づく。ブラウザ、HTTPライブラリ、CDN、ロードバランサーが実際にいつ接続を再利用し、いつ421を返すかは製品やバージョン、設定によって異なるため、扱わない。RFCの規則ではなくこの記事の提案である部分は、そのように明記する。

421が意味するもの

RFC 9110 15.5.20節の定義はこうだ。421は、リクエストが対象URIについて権限のある(authoritative)レスポンスを生成できない、または生成する意思のないサーバーに向けられたことを示す。オリジンサーバー、またはオリジンサーバーの代わりに動くゲートウェイが、次の2つの場合に送る。

  • 対象URIが、サーバーに設定されたどのオリジンとも一致しないとき(4.3.1節)
  • 対象URIが、リクエストを受け取った接続のコンテキスト(connection context)と一致しないとき(7.4節)

同じ節には規則が2つある。

  • 421を受け取ったクライアントは、メソッドが冪等かどうかにかかわらず、別の接続でリクエストを再試行してよい(MAY)。例として挙げられているのは、対象リソースのオリジン専用に新しく開いた接続、または代替サービス(alternative service)だ。
  • プロキシは421レスポンスを生成してはならない(MUST NOT)。

つまり421はリソースについての判定ではなく、リクエストと接続の組み合わせについての判定だ。RFC 9110付録B.3も別の側から同じことを言っている。421は最初RFC 7540(最初のHTTP/2)で定義され、より広く当てはまるコードであるため中核のセマンティクス文書に移された。その際にヒューリスティックなキャッシュの対象からも外れた。このレスポンスは対象リソースではなく接続に固有のものだからだ。

オリジンと権限

規則が拠り所にしている用語はオリジンだ。RFC 9110 4.3.1節は、URIのオリジンをスキーム、ホスト、ポートの3つの値と定義する。スキームとホストは小文字に、ポートは先頭の0を取り除いた形に正規化し、ポートが省略されていればスキームのデフォルトポートを使う。だからhttps://Example.Com/happy.jsのオリジンは{ "https", "example.com", "443" }になる。スキーム、ホスト、ポートのどれか1つでも違えば別のオリジンだ。同じ主体が両方のオリジンを管理していても、そのオリジンに権限のあるサーバーが明示的にエイリアスを設けない限り、2つの名前空間は別物である。

権限(authority)とは、あるサーバーがそのオリジンに代わって応答してよいかという問題だ。httpsでは、クライアントがそのオリジンについて信頼する証明書の秘密鍵をサーバーが使えるかどうかに基づく(4.3.3節)。4.3.4節によれば、クライアントは証明書の識別情報がURIのオリジンサーバーと許容できる形で一致するかを検証しなければならない(MUST)。名前の比較そのものはTLS証明書の名前不一致で扱った。

4.3.3節は続けて、この記事にとって重要な線を引く。

  • HTTP/1.1以前では、クライアントはそのURIオリジンのホストに対して直接確立した安全な接続上でのみ、サーバーに権限を認める。
  • HTTP/2とHTTP/3では、すでに確立した安全な接続であっても、URIオリジンのホストがサーバー証明書にあるホストのいずれかと一致し、そのURIのためにそのホストへ接続を開けるとクライアントが判断すれば、サーバーに権限を認める。

RFCは、実際にはクライアントがDNSクエリを行い、オリジンのホストが確立済みの接続と同じサーバーIPアドレスを含むかを確認すると付け加えている。また、オリジンサーバーがそれに相当するORIGINフレーム(RFC 8336)を送ればこの制約を外せるとも書いている。RFC 8336はこの記事の出典に含まれないため、ORIGINフレームについてはこれ以上扱わない。

HTTP/2が1本の接続を複数のオリジンに使う条件

RFC 9113 9.1節は、HTTP/2の接続が持続的であるという前提から始まる。クライアントは、同じホストとポートの組に対してHTTP/2接続を2本以上開くべきではない(SHOULD NOT)。ここでのホストは、URI、選択された代替サービス、または設定されたプロキシから得られる。同じIPアドレスとTCPポートでも、SNI(Server Name Indication)の値やTLSクライアント証明書が異なれば複数の接続を開いてよいが(MAY)、同じ構成で複数の接続を作ることは避けるべきだ(SHOULD)。

9.1.1節(Connection Reuse)は、ホスト名の異なるリクエスト同士で接続を共有する条件を定める。

  • オリジンサーバーへ直接、またはCONNECTで作ったトンネルを通じて確立した接続は、URIのauthority要素が異なる複数のリクエストに再利用してよい(MAY)。オリジンサーバーが権限を持つ限り再利用できる(10.1節)。
  • TLSのないTCPなら、そのホストが同じIPアドレスに解決されたかどうかによる。
  • httpsリソースなら、URIのホストに対して有効な証明書があることが追加の条件になる。サーバーが提示した証明書は、クライアントがそのホストへ新しいTLS接続を確立するときに行うすべての検査を満たさなければならない(MUST)。1枚の証明書で複数のオリジンに対する権限を示すことができる。

RFC 9113付録Bは、この概念が421ステータスコードとともに現在はRFC 9110に含まれていると記し、これをconnection coalescing(接続の統合)と呼んでいる。

証明書が有効でも足りない理由

9.1.1節は問題を率直に書いている。デプロイ環境によっては、1本の接続を複数のオリジンに再利用すると、リクエストが間違ったオリジンサーバーに向かうことがある。例として挙げているのは、TLS終端を担うミドルボックスがSNI拡張を使ってオリジンサーバーを選ぶ場合だ。すると、サーバーがほかの点では権限を持っていても、クライアントはそのリクエストの本来の宛先ではないかもしれないサーバーにリクエストを送ることになる。

この状況をリクエストの流れで描くと次のようになる。名前とアドレスは説明のための例だ。

Certificate SAN: a.example.com, b.example.com
DNS: a.example.com -> 192.0.2.10
     b.example.com -> 192.0.2.10

client                 TLS terminator (192.0.2.10)        backends
  |-- TLS, SNI=a.example.com -->|-- route by SNI ------------> pool A
  |-- :authority=a.example.com  |--------------------------->  pool A  200
  |
  |  b.example.com: same cert, same IP -> client reuses the connection
  |-- :authority=b.example.com  |--------------------------->  pool A
  |<---------------------------- 421 Misdirected Request ----  pool A (not configured for b)
  |
  |-- new TLS, SNI=b.example.com -->|-- route by SNI --------> pool B
  |-- :authority=b.example.com      |----------------------->  pool B  200

クライアントから見れば、条件はすべて揃っている。証明書はb.example.comを含み、アドレスも同じなので、RFC 9113 9.1.1節の再利用条件を満たす。ところが終端装置はこの接続の行き先をハンドシェイク時にSNIを見て一度だけ決めているため、bへのリクエストもプールAに送る。プールAがbを処理するように設定されていなければ、まさに15.5.20節が述べる状態、つまり対象URIについて権限のあるレスポンスを生成できない状態だ。9.1.1節はその出口を定めている。クライアントに接続を再利用してほしくないサーバーは、そのリクエストに421で応答し、自分が権限を持たないことを示せる。

RFC 9110 4.3.3節は、この責任をオリジン側に置いている。証明書の秘密鍵の管理権限を与えられたすべてのサービスは、対応するhttps名前空間に等しく責任を持つか、少なくとも誤って届いたと見られるリクエストを拒否する準備ができていなければならない(7.4節)。

サーバーが確認するもの:対象URIと接続のコンテキスト

RFC 9110 7.4節は、リクエストの対象URIがわかるところまで解析したサーバーが何を決めるかを説明している。自分で処理する、別のサーバーへ転送する、別のリソースへリダイレクトする、エラーで応答する、接続を切る、のいずれかだ。判断にはリクエストや接続に関するどんな情報を使ってもよいが、要点は、サーバーがその対象URIを処理するよう設定されているか、そして接続のコンテキストがそのリクエストにふさわしいかである。

  • 誤って届いたリクエストの例として、Hostヘッダーフィールドの情報が接続のホストやポートと異なる場合を挙げている。信頼するゲートウェイからの接続なら想定できることだが、そうでなければ、セキュリティフィルターの回避、非公開コンテンツの取得、キャッシュ汚染を狙った試みかもしれない。
  • 信頼するゲートウェイからの接続でない限り、オリジンサーバーは対象URIのスキームごとの要件を満たさないリクエストを拒否しなければならない(MUST)。特にhttpsリソースへのリクエストは、その対象URIのオリジンについて有効な証明書で保護された接続で届いたものでなければ拒否しなければならない(MUST)。
  • 421レスポンスは、リクエストが誤って届いたように見えるためオリジンサーバーが拒否したことを示す。

4.3.3節はポートについての例も挙げている。1つのホストが443と8000で別々のサービスを運用しているなら、接続が保護された後でもオリジンサーバーは対象URIを確認する必要がある。ネットワーク上の攻撃者が、あるポート宛ての接続を別のポートで受けさせることができるからだ。この確認がなければ、https://example.com/fooへのレスポンスが、https://example.com:8000/fooの一見権限のあるレスポンスに差し替えられかねない。

HTTP/2では、リクエストのauthorityは:authority疑似ヘッダーフィールドで運ばれる(RFC 9113 8.3.1節、RFC 9110 7.2節)。:authorityがあるとき、受信者は対象URIを決めるのにHostヘッダーフィールドを使ってはならない(MUST NOT)。クライアントは:authorityと異なるHostヘッダーフィールドを生成してはならず(MUST NOT)、サーバーは両者が別の対象を指していればそのリクエストを不正な形式として扱うべきだ(SHOULD)。したがって、サーバーが接続と照らし合わせる対象URIは:authorityから来る。

クライアントの再試行:許されることと許されないこと

中心となる規則は15.5.20節のMAYだ。421を受け取ったクライアントは、メソッドの冪等性にかかわらず別の接続で再試行してよい。読み違えやすい点がいくつかある。

  • MUSTではなくMAYだ。RFCは再試行を許しているだけで、求めてはいない。
  • 再試行は別の接続で行う。421を返したばかりのその接続に同じリクエストを送り直すことは、この規則が許している内容ではない。RFCが挙げる例は、対象リソースのオリジン専用に新しく開いた接続、または代替サービスだ。
  • 15.5.20節は、非冪等なメソッドまで許す理由を述べていない。421をオリジンサーバーが誤って届いたリクエストを拒否したものと説明する7.4節とは整合するが、リクエストが何の効果も生じなかったとRFCが明言しているわけではない。421以外の場合には、RFC 9110 9.2.2節の一般規則が適用される。クライアントは、リクエストの意味が実際に冪等だと知る手段か、元のリクエストが適用されなかったことを検出する手段がない限り、非冪等なリクエストを自動的に再試行すべきではない(SHOULD NOT)。メソッド全般の規則はHTTPメソッドの安全性と冪等性で扱った。
  • 15.5.20節の許可は「クライアント」を主語に書かれている。プロキシは非冪等なリクエストを自動的に再試行してはならないという9.2.2節の規則(MUST NOT)とどう組み合わさるかは、RFC 9110には書かれていない。この記事の保守的な読み方は、中継者が421をそのMUST NOTを越える根拠にしないことだ。
  • 新しい接続で送り直したリクエストにも421が返った場合にどうするかは、15.5.20節にはない。9.2.2節は別途、失敗した自動再試行をさらに自動で再試行すべきではないと述べている(SHOULD NOT)。

次の関数はこの判断をまとめたものだ。規則は上の各節から取り、関数の形と試行回数の数え方はこの記事で決めた。

interface RetryPlan {
  retry: boolean;
  newConnection: boolean; // 再利用せず、このオリジン用の接続を新しく開く
}

function onMisdirected(
  status: number,
  attempt: number,         // 最初の送信は0、最初の自動再試行は1
  bodyReplayable: boolean, // リクエストボディを最初から送り直せるか
): RetryPlan {
  if (status !== 421) return { retry: false, newConnection: false };
  // 15.5.20: 冪等かどうかにかかわらず別の接続で再試行してよい(MAY)
  // 9.2.2: 失敗した自動再試行をさらに自動で再試行しない(SHOULD NOT)
  if (attempt >= 1 || !bodyReplayable) return { retry: false, newConnection: false };
  return { retry: true, newConnection: true };
}

bodyReplayableはRFCの規則ではなく、実装上の前提だ。ストリーミングで送り出して捨てたボディは新しい接続に載せられない。この関数は、新しい接続をどこへ開くかも扱っていない。同じアドレスへ新しく開いた接続が同じ終端装置に届く可能性があるなら、クライアントはSNIが対象オリジンのホストに設定されることを確認する必要がある。RFC 9113 9.1節は、同じIPとポートでもSNIの値が異なれば複数の接続を開くことを認めている。

隣り合うレスポンスとの違い

  • 3xxリダイレクトは対象URIを変える。421はURIをそのままにして、リクエストを載せる接続だけを変える。HTTPリダイレクトを参照。
  • GOAWAYは接続全体を終了させるフレームで、終わっていないリクエストを再送できるかはその中のストリーム番号で決まる。421は1つのリクエストに対する通常のレスポンスで、接続そのものはほかのオリジンのリクエストを引き続き処理できる。HTTP/2のGOAWAY後の再送を参照。
  • 425 Too EarlyはTLS 1.3のearly dataのリプレイの危険に関するコードで、複数のオリジンが接続を共有する問題とは関係がない。TLS 1.3の0-RTTとHTTP 425を参照。
  • RFC 9110 15.5.20節により、プロキシは421を生成してはならない。RFC 9113 9.1.1節は、HTTP/2でプロキシを使うよう設定されたクライアントが、すべてのリクエストをそのプロキシへの1本の接続で送ると書いている。つまりプロキシ経由のリクエストはすべてプロキシへの接続を再利用し、この種の再利用は421が扱う問題ではない。

運用で確認すること

以下は上の条件から導いたこの記事の提案だ。RFCが定めた手順ではなく、測定データに基づくものでもない。

サーバーやゲートウェイの側

  • ルーティングを決める層に、リクエストの:authority(またはHost)とハンドシェイク時に決めたコンテキストを比較させる。バックエンドが処理するよう設定されていない名前のリクエストが来たら、別サイトのコンテンツを返したり404を返したりせず、421で応答する。404や200では、クライアントは「新しい接続で再試行せよ」という合図を受け取れない。
  • 421はオリジンサーバー、またはオリジンサーバーの代わりに動くゲートウェイから返す。プロキシが生成してはならない(RFC 9110 15.5.20節)。
  • キャッシュに421を保存させない。421はリソースではなく接続についてのレスポンスだ(RFC 9110付録B.3)。
  • そもそも複数のオリジンが接続を共有すること自体を減らしたいなら、再利用の条件を見る。httpsで再利用するには、ほかのホストについても有効な証明書が必要だ(RFC 9113 9.1.1節)。別々の場所へルーティングされる名前を1枚の証明書にまとめると、再利用の前提条件ができる。その条件が揃ったときに特定のクライアントが実際に接続を再利用するかどうかは、実装次第だ。

421を追跡するとき

  • リクエストの対象ホストと、接続を開くときに使ったホスト名(SNI)が異なるかを確認する。TLS終端装置がSNIを記録できるなら、:authorityと同じ行に残す。
  • その接続で提示された証明書が、リクエストのホストも含んでいるかを確認する。
  • 2つの名前が重なるアドレスに解決されるかを確認する。RFC 9110 4.3.3節は、これをクライアントが実際に行う確認として説明している。
  • 421の後にクライアントが新しい接続で再試行しているか、その再試行にも421が返っているかを確認する。2回目も失敗するなら、新しい接続が同じバックエンドに届いている可能性が高い。

まとめ

RFCから取ったもの

  • 421は、サーバーがこの接続上では対象URIについて権限のあるレスポンスを返せない、または返さないという意味だ。リソースではなく、リクエストと接続の組み合わせについてのものだ。
  • HTTP/2の接続は、サーバーが権限を持つ限り複数のオリジンに再利用できる。httpsなら、ほかのホストについても有効な証明書が必要だ。
  • TLS終端装置がSNIでルーティングしていると、再利用した接続が本来の宛先ではないサーバーに届くことがある。サーバーは421で拒否できる。
  • クライアントは421の後に別の接続で再試行してよく、非冪等なメソッドでも構わない。プロキシは421を生成してはならず、421はヒューリスティックなキャッシュの対象ではない。

この記事の提案

  • ルーティング層が、:authorityが接続と合わないときに421を返すようにする。
  • 新しい接続での再試行は1回だけ、ボディを送り直せるときに限る。
  • 421を追跡するときは、SNI、:authority、証明書の名前、DNSの応答をまとめて見る。

出典と確認日

いずれも2026-10-27に確認した。

ブログへ