307 を調べていると、必ずといってよいほど一緒に目にする番号が 308 です。307 が「一時的な移動で、メソッドを変えない」なら、308 は「恒久的な移動で、メソッドを変えない」という関係にあります。つまり、307 を恒久版にしたものが 308 だと考えると、全体像がつかみやすくなります。

ただし 308 は 301 に比べて新しい番号で、導入のしかたにはいくつか注意点があります。この記事では、308 の位置づけと、301 との違い、使うべき場面と避けるべき場面を整理します。

意味

リソースが新しい URL へ恒久的に移ったことを示します。

特徴

転送の前後で、要求メソッドと本文を変えません。

歴史

301 や 302 より後に定義された、比較的新しい番号です。

キャッシュ

RFC 9110 で、明示がなくても保存されうる応答に含まれます。

新しい番号ゆえの、導入前に知っておきたい不安

308 を採用するときに、まず気になるのは対応状況でしょう。

RFC 9110 は 308 について、301 と同じ意味でありながらメソッドの変更を認めない番号だと説明し、そのうえで「この番号は 2014 年に定義されたため、一部のソフトウェアは認識しないかもしれない」と注意を促しています。古いブラウザー、組み込み機器、年季の入った HTTP ライブラリなど、更新されていない環境では、308 を未知の番号として扱われる可能性があります。

もう一つの注意点は、恒久的な転送は取り消しにくいことです。308 は 301 と同様に、保存されうる応答です。設定を誤って公開すると、ブラウザーなどが転送を覚えてしまい、直したあとも古い行き先に誘導されることがあります。

さらに、サーバーソフトウェア側の対応時期も関わります。たとえば nginx の return ディレクティブの文書には、「308 はバージョン 1.13.0 から転送として扱われる」という趣旨の記載があります。1.13.0 より前の nginx を使っている環境では、期待どおりに動かない可能性があります。

不安材料はこのくらいで、現在の主要な環境では、対応状況が問題になる場面は多くありません。次の章から、メリットを見ていきます。

308 が生まれた背景

308 の登場の経緯は、307 とよく似ています。

301 は、恒久的な移動を表す番号として古くから使われてきました。しかし、302 と同じ事情で、実際のブラウザーは 301 を受け取ると POST を GET に切り替える動作をとります。RFC 9110 も、301 では、ユーザーエージェントが POST を GET に変えてもよい(MAY)と書いたうえで、メソッドを変えたくない場合は 308 を使えると案内しています。

つまり、一時的な移動で 302 の曖昧さを解消するために 307 が作られたのと同じ発想で、恒久的な移動における 301 の曖昧さを解消するために 308 が作られたのです。

一時的な移動 恒久的な移動
メソッドの変更が許される 302 301
メソッドを変えない 307 308

この 2 行 2 列の表が頭に入っていれば、5 つのリダイレクト番号の関係の大半は整理できます。

308 の強み

強み 1: 書き込み系の API を恒久的に移せる

API のドメインや URL 構造を恒久的に変える場面を考えます。旧 URL を 301 にすると、クライアントによっては POST が GET に化けて、更新が失われます。308 なら、メソッドと本文が新 URL にも届くため、既存のクライアントを壊さずに移行できます。

clientserver

POST https://api.old.example/v1/orders (本文あり)
308 Permanent Redirect / Location: https://api.example/v1/orders
POST のまま、本文も保たれます
POST https://api.example/v1/orders (本文あり)
201 Created

強み 2: 検索エンジンへの強いシグナル

Google 検索セントラルのクロールに関する文書では、308 は 301 と同等に扱われると説明されています。301 と 308 は、リダイレクト先を正規 URL として処理するための強いシグナルです。ウェブページの恒久的な移転で 308 を使っても、301 と比べて不利になることはありません。

強み 3: 意図が曖昧にならない

302 と 301 では、クライアントの実装によって動きが変わる余地があります。308 は、そうした揺れをなくします。「このメソッドのまま新しい場所へ行ってください。元の場所は今後使いません」という意思を、番号だけで正確に伝えられます。

強み 4: 常に GET の Web ページなら、301 と同じ

ページを閲覧するだけの転送(GET のみ)であれば、301 と 308 の動きは同じです。そのため、どちらを選んでも実害はありません。308 を選んでおけば、将来 POST などが加わった場合にも、設計を変えずに済みます。

301 と 308、どちらを使うか

判断基準は、次の表のとおりです。

状況 推奨 理由
通常のページの恒久的な引っ越し 301(または 308) どちらでも動作は同じ。301 のほうが対応環境が広い
書き込み系 API の恒久的な移転 308 メソッドと本文を保てる
対象に古いクライアントが含まれる 301 古い環境でも確実に転送として扱われる
http から https への恒久的な切り替え 301(または 308) フォームや API があるなら 308 が安全

迷ったら、まず「POST などを使う通信が、その URL に届くか」を確認してください。届かないなら、実績の多い 301 で十分です。届くなら、308 を選ぶ理由があります。

サーバーでの設定例

308 を返す設定は、各サーバーソフトウェアの公式文書で確認できる形に限って紹介します。詳しくは、nginx、Apache、PHP の各解説も参考にしてください。

nginx

return ディレクティブは、301・302・303・307・308 のいずれかのコードと、転送先の URL を受け付けます。

server {
    listen 80;
    server_name old.example.com;
    return 308 https://new.example.com$request_uri;
}

$request_uri は、元の要求の URI(クエリ文字列を含む)を表す変数です。これを付けることで、パスとクエリを保ったまま転送できます。

Apache

mod_alias の Redirect ディレクティブは、permanent や temp といった名前のほかに、数値のステータスコードを受け付けます。300〜399 の範囲では、転送先 URL の指定が必須です。

Redirect 308 "/old" "https://new.example.com/new"

mod_rewrite を使う場合は、R フラグにコードを指定します。

RewriteEngine On
RewriteRule "^/old/(.*)$" "https://new.example.com/new/$1" [R=308,L]

Apache のソースコードでも、307 と 308 は HTTP_TEMPORARY_REDIRECT、HTTP_PERMANENT_REDIRECT として定義されています。

PHP

PHP の header() 関数は、第 3 引数で応答コードを指定できます。

<?php
header('Location: https://new.example.com/new', true, 308);
exit;

PHP のマニュアルによれば、Location ヘッダーを送ると、201 か 3xx がすでに設定されていない限り、302 が返されます。そのため、308 を返すには第 3 引数を明示する必要があります。また、リダイレクト後に後続の処理が動かないよう、exit を呼ぶのが基本です。

運用上の注意

取り消しを前提にしない

308 は、保存されうる応答です。設定を公開する前に、一時的な転送(307)で動作を確認し、問題がないことを確かめてから切り替える流れにしましょう。

転送先を最終地点にする

転送が連鎖すると、速度が落ちるだけでなく、ループの原因にもなります。旧 URL から最終的な URL へ直接転送する設計にします。

確認方法

設定後は、curl -I で番号と Location を確認します。

curl -I https://old.example.com/page

HTTP/2 308 と、意図した location: が見えれば設定は成功です。

http から https への切り替えと、HSTS の関係

恒久的な転送の代表例が、http のアクセスを https へ振り向ける設定です。この場面では、308(またはこれまで定番だった 301)の転送と、HSTS(HTTP Strict Transport Security)を組み合わせるのが一般的です。

HSTS は、RFC 6797 で定められた仕組みです。サーバーが https の応答に Strict-Transport-Security ヘッダーを付けると、ブラウザーはそのドメインを、指定された期間(max-age、秒単位)のあいだ「https でしかアクセスしない相手」として記憶します。includeSubDomains を付けると、サブドメインにも適用されます。

Strict-Transport-Security: max-age=31536000; includeSubDomains

仕様には、2 つの重要な規定があります。

  • http の応答に付いた HSTS ヘッダーは、無視される。 RFC 6797 は、安全でない通信で受け取ったこのヘッダーを、ユーザーエージェントが無視しなければならない(MUST)としています。そのため、HSTS を有効にするには、https の応答で送る必要があります。
  • 記憶したあとは、ブラウザーが通信前に https へ書き換える。 該当するドメインへの http アクセスは、サーバーへ送られる前に、ブラウザーの内部で https に変換されます。

この 2 つの仕組みは、役割が異なります。サーバーの 308(または 301)は、初めて訪れた利用者や、HSTS をまだ記憶していないクライアントを https へ導きます。HSTS は、一度訪れたあとのクライアントに対して、最初の http 通信そのものを発生させません。両方を設定することで、移行の隙間を埋められます。

切り替えの進め方

  1. https の証明書を整え、https でサイトが正しく表示されることを確認します。
  2. http から https への転送を、まず一時的な番号(307 など)で設定し、パスやクエリが保たれることを確かめます。
  3. 問題がなければ、恒久的な番号(301 または 308)に切り替えます。POST を受ける URL があるなら、308 が適しています。
  4. https の応答に、短い max-age で HSTS ヘッダーを付けます。問題がないことを確認しながら、期間を段階的に延ばします。

HSTS は、max-age の期間のあいだブラウザーに記憶されるため、後戻りしにくい設定です。転送の確認を終えてから、短い期間で始めるのが定石です。

正規化のための転送も、同じ考え方で

「www あり」と「www なし」、末尾のスラッシュの有無、大文字と小文字の揺れなど、同じ内容が複数の URL で表示される状態を一本化する転送も、恒久的な移動の一種です。ページを閲覧するだけの GET の通信であれば 301 と 308 のどちらでも構いませんが、POST を受ける URL が含まれるサイトでは、308 にしておくと、フォームの送信先が正規化の対象になったときでも入力内容が失われません。

採用で失敗しやすいパターン

308 に限らず、恒久的な転送では次のような失敗が起こりがちです。事前に知っておくと避けられます。

  • クエリ文字列を落としてしまう。 転送先に固定の URL を書くと、?id=10 のような部分が消えます。nginx なら $request_uri、Apache の RewriteRule なら後方参照など、元のパスとクエリを引き継ぐ書き方にします。
  • トップページへ一括で転送してしまう。 旧サイトの全ページを新サイトのトップに集めると、利用者は探していたページに着けません。対応するページへ個別に転送するのが基本です。
  • 転送先でさらに別の転送が走る。 旧 URL から最終 URL まで、転送が 2 回、3 回と連なると、表示が遅くなります。旧 URL から最終 URL へ直接転送するように、設定を整理します。
  • テスト環境の URL を転送先にしてしまう。 恒久的な転送は保存されるため、誤った転送先が長く残ります。公開前に、転送先が本番の URL であることを必ず確認します。

どれも、設定の段階で気をつければ防げるものばかりです。

308 は「恒久版の 307」と覚えておく

308 の要点を整理します。

  • 308 は、恒久的な移動を、メソッドを変えずに伝える番号です。
  • 301 の曖昧さを解消するために作られ、307 と対になっています。
  • 通常のページの転送なら 301 と同じ動きで、書き込み系の API の移転で真価を発揮します。
  • 古い環境では認識されない可能性があるため、対象の範囲を確認してから採用します。
  • 恒久的な転送は取り消しにくいため、公開前に一時的な転送で確認します。

リダイレクト番号の全体像は、301・302・303・307・308 の違いでも確認できます。