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-Proto や X-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: https と X-Forwarded-For: 203.0.113.9 を付けたリクエストを投げて確認しました。
$proxies 未設定 | $proxies = '*' | |
|---|---|---|
$request->isSecure() | false | true |
url('/probe') | http://... | https://... |
$request->ip() | 127.0.0.1 | 203.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-* を信用しない」「信頼するプロキシを明示する」「* の安全性はネットワーク側で担保する」という考え方は変わっていません。