ELB配下のLaravelでhttpsが認識されない(TrustProxies)

ELB(Elastic Load Balancing)でTLSを終端し、その後ろに Laravel を置く構成にしたところ、アプリ側が HTTPS を認識してくれませんでした。

ELB とアプリの間は HTTP で通信しているので、アプリから見れば当然 HTTP です。ブラウザは HTTPS で来ているのに、Laravel はそれを知りません。

何が起きるか

最初に気づいたのは、ページ内のリンクやアセットのURLが軒並み http:// になっていたことでした。

url()asset() はリクエストのスキームを見てURLを組み立てます。アプリが「自分はHTTPで呼ばれている」と思っている以上、出てくるURLはすべて http:// です。そしてブラウザは https:// でページを開いています。

この食い違いが、いくつもの形で表に出てきます。

  • CSSやJSが読み込まれず、画面が崩れる
  • HTTPSへリダイレクトする処理を入れていると、リダイレクトが無限に往復する
  • メールに載せたURLが http:// になる
  • ページネーションのリンクが http:// になる

さらに厄介なのが $request->ip() です。すべてのアクセス元がロードバランサのIPとして記録されます。 アクセスログもレート制限も、実際のクライアントを見ていない状態になります。

CORSエラーと間違えやすい

アセットが読み込まれずコンソールにエラーが並ぶので、最初はCORSの問題かと思いました。実際には別物です。

  • 混在コンテンツ(mixed content) — HTTPSのページから http:// のリソースを読もうとしてブラウザが遮断する。同一オリジンかどうかは関係ない
  • CORS — 別オリジンへのリクエストに対して、相手が許可のヘッダーを返していない

今回は前者です。自分のサーバーのCSSを読むだけでも、ページがHTTPSでURLがHTTPなら遮断されます。CSSやJSのような能動的なリソースは問答無用でブロックされ、画像などは扱いがブラウザによって変わります。

どちらもコンソールに赤い行が出て、リソースが読めていないという見え方は同じです。ブロックされたURLが http:// で始まっているかどうかを見れば切り分けられます。http:// なら混在コンテンツで、原因はサーバー側のURL生成にあります。CORSの設定をいくら見直しても直りません。

原因

Laravel は X-Forwarded-ProtoX-Forwarded-For といったヘッダーを、既定では信用しません

これは正しい既定値です。これらのヘッダーはクライアントが自由に付けられるので、無条件に信じるとIPの詐称を許すことになります。信用してよいのは「送ってきた相手が信頼できるプロキシだと分かっている」場合だけで、Laravel はそれを知らないため無視します。

対処

app/Http/Middleware/TrustProxies.php$proxies を設定します。

class TrustProxies extends Middleware
{
    /**
     * The trusted proxies for this application.
     *
     * @var array|string
     */
    protected $proxies = '*';

    /**
     * The headers that should be used to detect proxies.
     *
     * @var int
     */
    protected $headers = Request::HEADER_X_FORWARDED_ALL;
}

変更は protected $proxies;protected $proxies = '*'; にするだけです。

実際の挙動

Laravel 5.8.38 で、X-Forwarded-Proto: httpsX-Forwarded-For: 203.0.113.9 を付けたリクエストを投げて確認しました。

$proxies 未設定$proxies = '*'
$request->isSecure()falsetrue
url('/probe')http://...https://...
$request->ip()127.0.0.1203.0.113.9

スキームだけでなく、クライアントIPの見え方も同時に変わります。

* にしてよいのか

ここが一番気になるところだと思います。結論から言うと、アプリケーションがロードバランサ経由でしか到達できないなら、* で問題ありません。

ELB は特定のIPを持ちません。スケールに応じてIPが変わるため、信頼するプロキシのIPを列挙して固定するという方法が取れません。だから * を使います。

ただし * は「どこから来た X-Forwarded-* でも信じる」という意味です。もしアプリケーションのポートがインターネットから直接叩ける状態なら、クライアントが自分で X-Forwarded-For を付けて送るだけでIPを詐称できてしまいます。

つまり、この設定の安全性はアプリ側ではなくネットワーク側で担保します。 セキュリティグループで、アプリケーションへのインバウンドをロードバランサからのみに限定してください。ここを閉じていれば * は安全で、開いているなら * は危険です。設定を入れる前に、そちらを先に確認するのが順序として正しいです。

まとめ

  • ELB配下で HTTPS が認識されないのは、Laravel が X-Forwarded-* を既定で信用しないため
  • TrustProxies$proxies'*' にすれば、スキームもクライアントIPも正しく解決される
  • '*' が安全なのは、アプリへの直接アクセスをセキュリティグループで塞いでいる場合に限る

一行の変更ですが、なぜその一行で済むのかを押さえておかないと、次に別の構成で同じ問題に当たったときに迷います。

追記(2026-09-16)

この記事はLaravel 5.8を前提に書いていますが、その後、設定の置き場所が変わりました。現行版で確認した内容を残しておきます。

Laravel 9以降fideloper/proxy はフレームワークに取り込まれ、外部パッケージは不要になりました(同パッケージの最終リリースは2022年2月です)。継承元が IlluminateHttpMiddlewareTrustProxies に変わります。

Laravel 11以降app/Http/Middleware/TrustProxies.php そのものが無くなりました。設定は bootstrap/app.php に書きます。

->withMiddleware(function (Middleware $middleware): void {
    $middleware->trustProxies(at: '*');
})

信頼するヘッダーも指定する場合は第2引数を使います。

$middleware->trustProxies(
    at: '*',
    headers: Request::HEADER_X_FORWARDED_FOR
        | Request::HEADER_X_FORWARDED_HOST
        | Request::HEADER_X_FORWARDED_PORT
        | Request::HEADER_X_FORWARDED_PROTO
        | Request::HEADER_X_FORWARDED_AWS_ELB,
);

ちなみに既定値にはすでに HEADER_X_FORWARDED_AWS_ELB が含まれているので、ELBを使うだけなら at: の指定だけで足ります。5.8の頃に書いていた HEADER_X_FORWARDED_ALL は廃止されました。

設定の書き方は変わりましたが、「既定では X-Forwarded-* を信用しない」「信頼するプロキシを明示する」「* の安全性はネットワーク側で担保する」という考え方は変わっていません。

コメントを書く

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください

Shares