TT Lab
はじめる
学ぶ 学習パス コース

Tomcat & nginxの運用

プロキシ設定の六行と、その六行がないとき起きること

TT Labで続きを見る

一言でいうと

プロキシ設定での事故は、たいてい6行のヘッダー転送と、proxy_passのスラッシュ1つから起こり、どちらが抜けていても画面は問題なく表示されるため、発見が遅れます。

なぜ前にWebサーバーを置くのか

TomcatもHTTPを話せるのに、なぜ前にnginxを置くのでしょうか。SI現場の実際の理由は、性能よりも運用の利便性とポリシーです。

特に最後の項目が大きいです。ポリシーをアプリケーションに入れると、デプロイしないと変わりませんが、プロキシに入れるとreloadで変わります。運用組織がプロキシを好む理由です。

プロキシ設定の6行

location / {
    proxy_pass http://127.0.0.1:8080;

    proxy_set_header Host              $host;
    proxy_set_header X-Real-IP         $remote_addr;
    proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Forwarded-Host  $host;
    proxy_set_header X-Forwarded-Port  $server_port;

    proxy_connect_timeout 3s;
    proxy_send_timeout   30s;
    proxy_read_timeout   60s;
}

各行がないときに何が起きるかを知ってこそ、暗記ではなく理解したことになります。

そして重要なセキュリティ原則が1つあります。これらのヘッダーは、最前段のプロキシで必ず上書きしなければなりません。クライアントがX-Forwarded-For: 10.0.0.1を直接送れるからです。proxy_set_headerで上書きすれば、クライアントが送った値は無視されます(追記される変数は例外です)。

proxy_passのスラッシュ: 最もよくあるnginxのバグ

location /api/ {
    proxy_pass http://backend;      # 슬래시 없음 → /api/users/1 이 그대로 전달
}

location /api/ {
    proxy_pass http://backend/;     # 슬래시 있음 → /api 부분이 잘리고 /users/1 로 전달
}

URI部分があると(スラッシュを含む)、locationにマッチした部分が切り落とされます。バックエンドが/apiコンテキストを期待しているのにスラッシュを付けると、404が大量に出て、逆に、バックエンドがルートを期待しているのにスラッシュを外すと、/api/api/usersになります。

この1文字で半日を費やす人を、毎年見ます。迷ったら、アクセスログの$upstream_addrとバックエンドログのリクエストパスを一緒に見れば、すぐに判別できます。

リクエストサイズ: 413の正体

nginxのclient_max_body_sizeのデフォルト値は1MBです。ファイルアップロードがあるシステムでこの値を上げないと、3MBの添付をアップロードするとき、413 Request Entity Too Largeが出ます。そして、このエラーはnginxが返すので、アプリケーションログには何も残りません。開発者は「サーバーに何のログもありません」と言って、何日も迷います。

client_max_body_size 20m;
client_body_buffer_size 128k;
large_client_header_buffers 4 16k;

large_client_header_buffersは、ヘッダーが大きいときに使います。SSOを付けるとCookieが大きくなりますが、デフォルトのバッファーを超えると、400 Bad Requestが出ます。これも、ログがあいまいです。

圧縮と静的ファイル

gzip on;
gzip_comp_level 6;
gzip_min_length 1000;
gzip_types text/plain text/css application/json application/javascript text/xml;
gzip_vary on;

静的ファイルは、そもそもプロキシを通さないほうがよいです。

location /static/ {
    alias /app/static/;
    expires 7d;
    access_log off;
}

rootとaliasを混同するのも、常連です。location /static/に対して、root /app;なら/app/static/파일、alias /app/static/;なら/app/static/파일です(プレースホルダーはファイル名です)。同じに見えますが、location /s/にalias /app/static/;なら、/s/a.js→/app/static/a.jsです。locationのパスと実際のディレクトリ名が違うときに、aliasを使います。

管理画面の遮断

Tomcat ManagerやActuatorが外部に開いているのは、実際の事故につながります。プロキシで止めるのが、最も確実です。

location ~ ^/(manager|host-manager)/ { return 403; }
location /actuator/ {
    allow 10.0.0.0/8;
    deny  all;
    proxy_pass http://127.0.0.1:8080;
}

ここで学ぶ点: アプリケーション設定でも止められますが、プロキシで止めれば、アプリケーションをデプロイせずに、ポリシーを変更できます。セキュリティ点検の指摘が金曜日の午後に降りてきても、reloadで終わります。

アクセスログに何を残すか

デフォルトのcombinedフォーマットでは、障害分析ができません。最低限、この4つは入れます。

log_format labhub '$remote_addr - $remote_user [$time_local] '
                  '"$request" $status $body_bytes_sent '
                  'ua="$upstream_addr" us=$upstream_status '
                  'urt=$upstream_response_time rt=$request_time';

$request_timeは大きいのに$upstream_response_timeは小さいなら、問題はバックエンドではなく、クライアントのネットワークやレスポンスの転送です。この2つの値の差1つで、「バックエンドが遅い」という誤解を、何度でも解消できます。

現場での姿

ヘッダー6行が抜けたときに現れる症状は、互いに無関係に見えます。アクセスログのクライアントIPが、すべてプロキシIPの1つで記録され(X-Real-IP・X-Forwarded-Forの欠落)、ログインのあとのリダイレクトが内部アドレスやhttpに飛び(Host・X-Forwarded-Protoの欠落)、IPベースのアクセス制御や監査ログが、丸ごと無意味になります。

共通点は、普段は何も問題がないことです。そのため、サービスインの数週間後に、セキュリティ監査や障害分析で初めて発覚し、そのときにはすでに、そのログでは何も追跡できません。

proxy_passのスラッシュは、もっと静かです。末尾にスラッシュがあれば、locationのパスを除いて渡し、なければ付けたまま渡すのですが、開発環境ではパスがルートなので違いが表れず、本番でコンテキストパスが付いた瞬間に、404になります。