現場のSREが語る、ALBと「見えないポート番号」の正体 ― X-Forwarded-Port を使いこなす
インフラの現場で「なぜかアプリケーション側で正しくURLが生成されない」「特定のポート宛のトラフィックだけリダイレクトループする」といった怪奇現象に遭遇したことはないだろうか?
AWSのALB(Application Load Balancer)を使っていると、クライアントからのパケットは一度ALBで終端され、バックエンド(EC2やFargate)へは別のコネクションで再構築される。このとき、クライアントが「どのポートにアクセスしたか」という情報は、デフォルトのままではバックエンドに届かない。
ここで登場するのが X-Forwarded-Port ヘッダーだ。今回は、この地味だが極めて重要なヘッダーの仕様と、マルチテナント構成における実務的な罠について、ネットワークの裏側を覗きながら解説しよう。
—
1. なぜ「ポート番号」が伝わらないのか?
まずは通信フローを整理しておこう。
1. Client → https://example.com:8443/ にリクエスト
2. ALB → 8443 でパケットを受信し、ターゲットグループへ転送
3. Target → ALBからのリクエストを受信
このとき、ターゲット(App)から見ると、接続元は「ALBのIPアドレス」であり、接続先ポートは「Appがリッスンしている80番(あるいは8080番など)」になってしまう。クライアントが本来アクセスした 8443 という情報は、ALBが意図的にヘッダーを付与しない限り、アプリケーション層まで到達しないのだ。
ALBが提供する「伝言板」
ALBは、プロキシとして振る舞う際に以下の3つのヘッダーを付与してくれる。
X-Forwarded-For: クライアントのIPアドレスX-Forwarded-Proto: クライアントが使用したプロトコル(http/https)X-Forwarded-Port: クライアントが接続した宛先ポート
これらを活用することで、アプリケーションは「自分は今、どの入り口から叩かれているのか」を認識し、適切なリダイレクト先やベースURLを動的に生成できるようになる。
—
2. 実践:X-Forwarded-Port を確認する
まずは、自分の環境で実際にこのヘッダーがどう見えているかを確認しよう。Python(Flask)を使った最もシンプルな検証コードがこれだ。
from flask import Flask, request
app = Flask(__name__)
@app.route('/')
def debug_headers():
# ALBが付与したヘッダーを取得
port = request.headers.get('X-Forwarded-Port')
proto = request.headers.get('X-Forwarded-Proto')
return f"クライアントが接続したポート: {port}, プロトコル: {proto}"
# 実行時は gunicorn 等のWSGIサーバーで起動してください
これをALB配下にデプロイし、curl で叩いてみると挙動がよくわかる。
# 8443ポートでリクエストを投げる(ALB側でリスナー設定が必要)
curl -I https://example.com:8443/
# 応答確認:
# X-Forwarded-Port: 8443 が返ってくるはずだ
—
3. マルチテナント構成での「危ない落とし穴」
この X-Forwarded-Port が真価を発揮するのは、単一のALBで複数のサービスや、異なるポート設定を持つテナントを収容する場合だ。
例えば、1つのドメインに対して「管理画面(8443)」と「一般API(443)」を運用しているとしよう。アプリケーション側で「もしポートが8443なら、管理用の認証フローへ回す」といったロジックを組む際、このヘッダーが信頼の拠り所となる。
ここで注意すべき「信頼性」の問題
SREとして警告したいのは、「このヘッダーはクライアントから改ざん可能である」という事実だ。
もしインターネットから直接バックエンドにアクセスできる構成なら、悪意あるユーザーが X-Forwarded-Port: 8443 を偽装してリクエストを送れば、アプリケーションはそれを信じ込んでしまう。
対策:
1. Security Groupの徹底: バックエンドのセキュリティグループは、ALBのSGからのみ通信を許可する。
2. 信頼済みプロキシ設定: アプリケーション(あるいはNginx等のリバースプロキシ)側で、X-Forwarded-* ヘッダーを信頼するIPレンジをALBのIP範囲に限定する。
—
4. 設定のポイントとトラブルシュート
現場でよくある失敗は、「ALBのリスナー設定でポートを割り当てたのに、アプリが認識しない」というケースだ。
チェックリスト
- ALBリスナー: 該当ポート(例: 8443)でリスナーが作成されているか?
- ターゲットグループ: ターゲットグループのプロトコルバージョンは適切か?(gRPCやHTTP/2の場合、ヘッダーの扱いが変わることもある)
- アプリケーションのフレームワーク: Express.jsやRailsなどは、
trust proxy設定をONにしないと、これらのヘッダーを無視、あるいは上書きしてしまうことがある。
特にNode.js(Express)の場合、以下のような設定が必須だ。
// ExpressでALBのヘッダーを正しく扱うための設定
app.set('trust proxy', true);
app.get('/', (req, res) => {
// これを有効にしないと、req.protocol等が正しく解決されない
console.log(req.headers['x-forwarded-port']);
res.send('Port info processed.');
});
—
まとめ:ネットワークの透明性を確保せよ
X-Forwarded-Port は、決して目立つヘッダーではない。しかし、大規模な分散システムにおいて、リクエストの「文脈」を正しくバックエンドに届けるための生命線だ。
SREの視点から言えば、「ネットワーク機器(ALB)とアプリケーション(コード)の間で、コンテキストを共有する」という意識が、障害を未然に防ぐ鍵となる。
もし、貴方の管理するシステムで「ポート番号がズレて誤動作する」という怪奇現象に遭遇したら、まずは tcpdump やログ収集基盤で、ALBが渡しているヘッダーと、アプリが読み取っている値の「ギャップ」を追ってみてほしい。パケットは常に、嘘をつかない答えを運んでいるのだから。
コメント