アプリサーバに直接トラフィックを当てると何が起きるか。遅いクライアントへのレスポンス送信でアプリプロセスが占有され、Keep-Aliveの管理コストが高くなり、静的ファイルもアプリが返すことになる。
リバースプロキシはこれらの問題を解決するためにアプリサーバの前段に置く中継サーバだ。
リバースプロキシの役割
クライアント
↓
[リバースプロキシ(Nginx)]
↓ ↓
[アプリサーバ] [静的ファイル]
リバースプロキシが担う主な役割は4つだ。
1. バッファリング
アプリサーバは高速なリバースプロキシにレスポンスを渡したらすぐ次のリクエストを処理できる。遅いクライアントへの送信はリバースプロキシが代わりに行う。
【バッファリングなし(アプリ直接)】
アプリ → クライアント(遅い回線)
アプリはクライアントへの送信が終わるまで占有される
【バッファリングあり(Nginxあり)】
アプリ → Nginx(高速転送)→ クライアント(遅い回線)
アプリはNginxへの送信が終わったらすぐ解放される
2. Keep-Alive の管理
HTTP/1.1 の Keep-Alive はコネクションを使い回す仕組みだが、多数のクライアントとのコネクションをアプリが管理するのはコストが高い。
Nginx がクライアントとの Keep-Alive を管理し、アプリとは短命なコネクションで通信することでアプリの負荷を下げられる。
3. URLルーティング
# パスによってバックエンドを振り分ける
location /api/ {
proxy_pass http://app-server:3000;
}
location /static/ {
root /var/www; # 静的ファイルはNginxが直接返す
expires 1y;
}
location / {
proxy_pass http://frontend-server:4000;
}
4. SSL終端
クライアントとの HTTPS 通信は Nginx で終端し、内部ネットワークは HTTP で通信する構成がシンプルで一般的だ。
server {
listen 443 ssl;
ssl_certificate /etc/ssl/certs/server.crt;
ssl_certificate_key /etc/ssl/private/server.key;
location / {
proxy_pass http://app-server:3000; # 内部はHTTP
}
}
Nginx の基本設定
# /etc/nginx/nginx.conf(主要設定の抜粋)
worker_processes auto; # CPUコア数に合わせる
worker_connections 1024; # 1ワーカーあたりの最大接続数
http {
# バッファリング設定
proxy_buffering on;
proxy_buffer_size 4k;
proxy_buffers 8 4k;
# タイムアウト設定
proxy_connect_timeout 10s;
proxy_send_timeout 30s;
proxy_read_timeout 30s;
# クライアントへのKeep-Alive
keepalive_timeout 65;
# バックエンドとのKeep-Alive(upstream)
upstream app_servers {
server 10.0.1.10:3000;
server 10.0.1.11:3000;
keepalive 32; # コネクションプールのサイズ
}
server {
listen 80;
location / {
proxy_pass http://app_servers;
proxy_http_version 1.1;
proxy_set_header Connection ""; # Keep-Aliveを有効にする
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
}
キャッシュ戦略
リバースプロキシにキャッシュを持たせることでアプリサーバへのリクエストを大幅に減らせる。
Nginx のプロキシキャッシュ
# キャッシュゾーンの定義
proxy_cache_path /var/cache/nginx
levels=1:2
keys_zone=app_cache:10m # キャッシュキーのメモリ(10MB)
max_size=1g # ディスク最大1GB
inactive=60m; # 60分アクセスなければ削除
server {
location /api/products {
proxy_cache app_cache;
proxy_cache_key "$scheme$request_method$host$request_uri";
proxy_cache_valid 200 10m; # 200レスポンスは10分キャッシュ
proxy_cache_valid 404 1m; # 404は1分
proxy_cache_bypass $http_pragma; # Cache-Control: no-cacheを尊重
# キャッシュ状態をレスポンスヘッダに付与(デバッグ用)
add_header X-Cache-Status $upstream_cache_status;
proxy_pass http://app_servers;
}
}
キャッシュすべきもの・すべきでないもの
| コンテンツ | キャッシュ | 理由 |
|---|---|---|
| 静的ファイル(CSS/JS/画像) | ◎ | 変更頻度が低い |
| GETのAPIレスポンス(カタログ等) | ○ | TTLを短く設定 |
| ユーザー固有データ | ❌ | 認証情報が漏れる |
| POST / PUT / DELETE | ❌ | 副作用があるリクエスト |
| セッション情報 | ❌ | ユーザーごとに異なる |
CDN との組み合わせ
オリジン(Nginx)の前段に CDN を置くことで、世界中のエッジから静的コンテンツを配信できる。
ユーザー(東京)
↓ キャッシュHITならエッジで返す
[CDNエッジ(東京)]
↓ キャッシュMISSのときだけ
[オリジン(Nginx)]
↓
[アプリサーバ]
AWS CloudFront での設定パターン
CloudFront Behavior 設定:
/static/*
→ Origin: S3バケット
→ Cache Policy: CachingOptimized(1年間)
→ Compress: Yes
/api/*
→ Origin: ALB(Nginx経由)
→ Cache Policy: CachingDisabled(APIはキャッシュしない)
→ Origin Request Policy: AllViewer
/*(デフォルト)
→ Origin: ALB
→ Cache Policy: TTL 0〜60秒(ページの鮮度に応じて)
キャッシュの無効化(Cache Invalidation)
デプロイ時に古いキャッシュを即座に消す方法だ。
# CloudFrontのキャッシュを無効化
aws cloudfront create-invalidation \
--distribution-id E1234ABCD \
--paths "/static/*" "/index.html"
# Nginxのプロキシキャッシュを手動削除
find /var/cache/nginx -type f -delete
ヘルスチェックとフェイルオーバー
upstream app_servers {
server 10.0.1.10:3000;
server 10.0.1.11:3000;
# ヘルスチェック(Nginx Plus / nginx-upstream-check-module)
# OSSのNginxではpassive failoverが基本
}
# Passiveフェイルオーバー: エラーが続いたら一時的に除外
server {
location / {
proxy_pass http://app_servers;
proxy_next_upstream error timeout http_503;
proxy_next_upstream_tries 2;
}
}
ALBを使っている場合はALBがヘルスチェックとフェイルオーバーを担当するため、Nginxの設定は不要になる。
まとめ
リバースプロキシの設計原則:
- アプリサーバはリバースプロキシの後ろに置く(バッファリング・Keep-Alive管理)
- 静的ファイルはNginxかS3/CDNが返す(アプリを通さない)
- GETのキャッシュはTTLを短めに設定(データの鮮度とヒット率のバランス)
- ユーザー固有データはキャッシュしない(情報漏洩リスク)
- デプロイ時のキャッシュ無効化を自動化する
リバースプロキシの設計思想はサーバ/インフラを支える技術の2章で詳しく解説されている。