2026年5月23日

2026年5月23日

Nginxのパフォーマンスチューニング入門:計測してから効く設定だけを変える

Nginxのチューニングで効果が出やすいのは、worker数と接続数の上限、keepalive、圧縮、静的ファイルの送出方法、そしてバッファの大きさです。ただし「ネットで見た大きな値」をそのまま貼るより、変更前に計測し、1項目ずつ変えて差を確かめるほうが安全で、結果も残ります。この記事では、主要な設定の意味と決め方、つまずきやすい点、確認方法を順に説明します(2026年10月現在の安定版Nginxを前提にしています)。

まず計測の土台をつくる

チューニングは「どこが遅いか」が分かってから始めます。CPUが余っているのに応答が遅いならPHP-FPMやDBが原因のことが多く、Nginxの設定をいじっても変わりません。逆に、静的ファイルの配信や同時接続数で詰まっているならNginx側の設定が効きます。

アクセスログに処理時間を出しておくと、Nginx自身の時間とアップストリーム(PHP-FPMやアプリ)の時間を分けて見られます。

http {
    log_format timed '$remote_addr [$time_local] "$request" $status '
                     '$body_bytes_sent rt=$request_time urt=$upstream_response_time';
    access_log /var/log/nginx/access.log timed;
}

$request_time が大きく $upstream_response_time もほぼ同じなら、遅いのはアップストリームです。静的ファイルで $request_time が大きい場合は、回線やディスク、クライアント側の遅さも疑います。

負荷をかけて比べるには、wrk や ab(Debian/Ubuntuでは apache2-utils パッケージ)を使います。本番サーバーに外から大きな負荷をかけるのは避け、検証環境か、アクセスの少ない時間帯に軽い負荷で試してください。

# 30秒間、4スレッド・100接続でトップページを叩く
wrk -t4 -c100 -d30s https://example.com/

# 静的ファイル単体の比較
wrk -t4 -c100 -d30s https://example.com/wp-includes/css/dist/block-library/style.min.css

設定を変えるたびに、同じ条件で同じコマンドを流し、リクエスト数/秒とレイテンシの分布を記録しておきます。変更は1回に1項目が原則です。

worker_processes と worker_connections

Nginxは1つのmasterプロセスと複数のworkerプロセスで動き、各workerがイベント駆動で多数の接続をさばきます。

worker_processes auto;
worker_rlimit_nofile 65535;

events {
    worker_connections 4096;
}

worker_processes auto はCPUコア数に合わせてworkerを起動します。コア数より増やしても速くはならないので、通常は auto のままで十分です。

worker_connections は1つのworkerが同時に持てる接続数の上限です。コンパイル時の既定値は512ですが、配布パッケージの nginx.conf には768や1024が書かれていることが多いです。注意したいのは、リバースプロキシやPHP-FPMへの中継では、クライアント側とアップストリーム側で1リクエストにつき2つの接続を使う点です。「worker数 × worker_connections = 同時に受けられるクライアント数」と単純には考えないでください。

接続数を上げるなら、ファイルディスクリプタの上限も合わせて上げます。worker_rlimit_nofile はworkerの上限を引き上げますが、systemdで起動している場合はユニットの LimitNOFILE も効きます。実際に効いているかは次のように確かめられます。

cat /proc/$(pgrep -f "nginx: worker" | head -n1)/limits | grep "open files"

ログに worker_connections are not enough が出ていなければ、無理に上げる必要はありません。multi_accept on や use epoll もよく紹介されますが、Linuxでは epoll が自動で選ばれるため書かなくても同じで、multi_accept は効果が環境次第です。入れるなら計測で差を見てからにします。

keepalive の設定

keepalive は1本のTCP(とTLS)接続で複数のリクエストを処理する仕組みで、TLSのハンドシェイクを減らせるため体感速度に効きます。

http {
    keepalive_timeout 30s;
    keepalive_requests 1000;
}

keepalive_timeout の既定値は75秒です。長すぎると、何もしていない接続がworker_connectionsを占有します。アクセスが多いサイトでは15〜30秒程度に縮めることがありますが、短くしすぎると再接続が増えるので、接続数の推移を見ながら決めます。keepalive_requests は1.19.10以降、既定値が1000になっています。

見落とされやすいのがアップストリームとのkeepaliveです。リバースプロキシ構成では、Nginxとバックエンドの間も毎回接続し直しているとオーバーヘッドになります。

upstream app {
    server 127.0.0.1:8080;
    keepalive 32;
}

server {
    location / {
        proxy_pass http://app;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
    }
}

upstream の keepalive は、各workerが保持するアイドル接続数の上限です。proxy_http_version 1.1 と Connection "" の指定がないと、接続が再利用されません。ここが抜けていて効いていない設定をよく見かけます。

sendfile と tcp_nopush

http {
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
}

sendfile on は、ファイルの中身をユーザー空間にコピーせず、カーネル内でソケットへ送ります。静的ファイルの配信でCPU負荷を下げられます。tcp_nopush は sendfile と組み合わせたときだけ働き、レスポンスヘッダーとファイルの先頭を同じパケットにまとめて送ります。tcp_nodelay は既定でオンです。

注意点として、VirtualBoxの共有フォルダやNFSなど、一部のファイルシステム上では sendfile によって古い内容が返るなどの不具合が報告されています。開発環境でCSSを変更しても反映されないときは、sendfile を疑ってください。

gzip と brotli による圧縮

テキスト系のレスポンスは圧縮すると転送量が大きく減ります。

http {
    gzip on;
    gzip_comp_level 5;
    gzip_min_length 1024;
    gzip_vary on;
    gzip_proxied any;
    gzip_types text/plain text/css application/javascript application/json
               application/xml image/svg+xml;
}

gzip_types に text/html は書かなくても常に対象です。画像(JPEG、PNG、WebP)やwoff2フォントはすでに圧縮されているので含めません。gzip_comp_level は1〜9で、上げるほどCPUを使う割に縮み方は小さくなります。4〜6あたりが落としどころとされることが多いですが、CPUに余裕がないサーバーでは低めにします。gzip_proxied any は、CDNなどのプロキシ経由のリクエストにも圧縮を返す設定です。

brotliはgzipより高い圧縮率が期待できますが、Nginx本体には含まれていません。Googleが公開している ngx_brotli モジュールを組み込むか、ディストリビューションが提供する動的モジュールのパッケージを使います。読み込めている場合の設定例です。

# nginx.conf の先頭(モジュールのパスは環境によって異なる)
load_module modules/ngx_http_brotli_filter_module.so;

http {
    brotli on;
    brotli_comp_level 5;
    brotli_types text/plain text/css application/javascript application/json
                 application/xml image/svg+xml;
}

効いているかは、Accept-Encoding を付けてヘッダーを確認します。

curl -sI -H "Accept-Encoding: br, gzip" https://example.com/ | grep -i content-encoding

open_file_cache

静的ファイルが多いサイトでは、ファイルを開くたびの存在確認や情報取得を減らせます。

http {
    open_file_cache max=10000 inactive=30s;
    open_file_cache_valid 60s;
    open_file_cache_min_uses 2;
    open_file_cache_errors on;
}

キャッシュするのはファイルディスクリプタやサイズ、更新日時などの情報で、ファイルの中身ではありません。open_file_cache_valid の間は情報を再確認しないため、デプロイ直後に古いファイルのサイズで応答して表示が崩れることがあります。頻繁にファイルを差し替えるサイトでは、valid を短めにするか、デプロイ後にreloadする運用にします。

バッファの大きさ

バッファは「大きいほど速い」ものではありません。接続ごとに確保されるため、むやみに増やすとメモリを圧迫します。エラーログに具体的な症状が出てから調整するのが基本です。

http {
    client_max_body_size 64m;
    client_body_buffer_size 128k;
    large_client_header_buffers 4 16k;

    fastcgi_buffer_size 32k;
    fastcgi_buffers 16 16k;
}

client_max_body_size の既定は1MBで、WordPressのメディアアップロードで「413 Request Entity Too Large」が出たらここを見直します(PHP側の upload_max_filesize と post_max_size も合わせます)。client_body_buffer_size を超えるリクエストボディは一時ファイルに書かれ、a client request body is buffered to a temporary file という警告が出ます。頻発するなら少し上げます。

upstream sent too big header while reading response header from upstream が出る場合は、Cookieや長いヘッダーを返すアプリで fastcgi_buffer_size(プロキシなら proxy_buffer_size)が足りていません。ヘッダーを受け取るための領域なので、ここを上げます。

反映と確認の手順

設定を変えたら、構文を確認してからreloadします。reloadは既存の接続を切らずに新しい設定のworkerへ切り替えます。

sudo nginx -t && sudo systemctl reload nginx

nginx -t が失敗した状態でrestartすると、Nginxが起動せずサイトが止まります。本番の設定ファイルは変更前にコピーを取っておき、すぐ戻せるようにしておいてください。変更後は最初と同じ計測をもう一度行い、数値が改善したか、エラーログに新しい警告が出ていないかを見ます。

まとめ

  • 変更前に $request_time と $upstream_response_time で遅い箇所を切り分け、wrk などで同じ条件の計測を続ける
  • worker_processes auto を基本に、worker_connections はプロキシ時に2接続使う点とファイルディスクリプタ上限を合わせて考える
  • keepalive_timeout は長すぎないように。アップストリームのkeepaliveは proxy_http_version 1.1 と Connection "" が要る
  • sendfile と tcp_nopush は静的配信に効くが、共有フォルダやNFSでは不具合に注意
  • 圧縮はテキスト系に限定し、comp_level は中程度に。brotliは別モジュールが必要
  • open_file_cache とバッファは症状を見てから調整し、nginx -t を通してからreloadする

お気軽にご相談ください

お見積りへ お問い合わせへ
▲