nginx(エンジンエックス)とは、Webサーバーとリバースプロキシの機能を1つにまとめたオープンソースのソフトウェアです。ブラウザからのHTTPリクエストを受け取って画像やHTMLを返す「Webサーバー」としてはもちろん、後ろにいる別のサーバーへ処理を転送する「リバースプロキシ」「ロードバランサー」としても使われます。
ただ、言葉の説明だけでは「Apacheと何が違うのか」「なぜ速いと言われるのか」が腑に落ちません。この記事ではUbuntu 24.04の実機とDockerを使い、nginxとApacheに同時接続100本をかけて、プロセス数・スレッド数・メモリ・処理性能を実際に測ります。まずは自分のサーバーで動いているnginxを見るところから始めましょう。
nginx -v
systemctl status nginx --no-pager | head -8
nginx version: nginx/1.24.0 (Ubuntu)
● nginx.service - A high performance web server and a reverse proxy server
Loaded: loaded (/usr/lib/systemd/system/nginx.service; enabled; preset: enabled)
Active: active (running) since Sat 2026-08-01 09:15:45 UTC; 22h ago
Docs: man:nginx(8)
Main PID: 1366 (nginx)
Tasks: 7 (limit: 18819)
Memory: 9.8M (peak: 10.2M)
サービスの説明文にa high performance web server and a reverse proxy serverとある通り、この2つがnginxの正体です。注目してほしいのはメモリ使用量9.8MBという数字で、これがnginxの最大の特徴に直結しています。以下、その理由を実測で確かめていきます。
nginxとは|Webサーバーとリバースプロキシを兼ねるソフトウェア
nginxは、OSとアプリケーションの中間で動くミドルウェアの一種で、その中の「Webサーバー」に分類されます。ロシアのエンジニアIgor Sysoev氏が開発し、2004年にオープンソースとして公開されました。2019年にF5社が約6.7億ドルで買収し、現在はF5のもとで開発が続いています。
読み方は「エンジンエックス」です。engine x(エンジン・エックス)を縮めた綴りで、「エヌジンクス」でも「ンギンクス」でもありません。公式サイトのURLもnginx.orgで、日本語圏では「エンジンエックス」と読むのが一般的です。
シェアについては調査機関によって数字が異なりますが、2026年時点の主要な調査ではnginxがおおむね3割前後で首位、Apacheは2割台という位置関係です。「Apacheに次ぐ2位」という説明を見かけたら、それは数年前の情報である可能性が高いので注意してください。
nginxが担う4つの役割
nginxは1つのソフトウェアで複数の顔を持ちます。同じサーバー上で同時に兼務させることもできます。
| 役割 | やっていること | 主な設定ディレクティブ |
|---|---|---|
| Webサーバー | HTML・画像・CSSなど静的ファイルを自分で読んで返す | root、index |
| リバースプロキシ | 受け取ったリクエストを後ろのアプリへ転送し、返事を中継する | proxy_pass、fastcgi_pass |
| ロードバランサー | 複数台のバックエンドに振り分けて負荷を分散する | upstream |
| SSL/TLS終端・キャッシュ | HTTPSの暗号化処理を肩代わりし、応答を一時保存して再利用する | ssl_certificate、proxy_cache |
実務でいちばん多いのは、「静的ファイルは自分で返し、プログラムの実行が必要なリクエストだけ後ろへ渡す」という組み合わせです。画像やCSSのように誰が見ても同じものはnginxが直接返し、ログイン後のページのように毎回内容が変わるものはPHP-FPMやNode.jsに任せます。この構成の全体像はミドルウェアとは?種類・具体例とサーバーでの確認コマンドで扱っています。
nginxが「速い」と言われる理由をApacheと並べて実測する
nginxの解説記事には必ず「イベント駆動だから軽い」と書いてあります。ただ、その言葉だけでは何がどう軽いのか分かりません。nginxとApacheを同じ条件で立てて、同時接続100本をかけたときの中身を実際に測ってみます。
Dockerで両方を1台ずつ用意します。返すファイルは同じものを共有し、条件を揃えます。
mkdir -p /tmp/nxdemo && cd /tmp/nxdemo
echo "<h1>static</h1>" > index.html
docker network create --subnet 172.31.0.0/24 nxnet
# nginx
docker run -d --name nx-a --network nxnet --ip 172.31.0.10 -p 18100:80 \
-v /tmp/nxdemo/index.html:/usr/share/nginx/html/index.html:ro nginx:alpine
# Apache
docker run -d --name ap-a --network nxnet --ip 172.31.0.11 -p 18101:80 \
-v /tmp/nxdemo/index.html:/usr/local/apache2/htdocs/index.html:ro httpd:alpine
docker exec nx-a nginx -v
docker exec ap-a httpd -v
docker exec ap-a httpd -V | grep -i MPM
nginx version: nginx/1.31.3
Server version: Apache/2.4.68 (Unix)
Server MPM: event
Apache側は現在の標準であるevent MPMです。よく比較に使われる古いprefork(1接続=1プロセス)ではなく、Apacheにとって有利な条件で比べます。
まずプロセス構造の違いを見る
負荷をかける前の状態を、プロセス数・スレッド数(NLWP)・メモリ(RSS)の3点で観察します。
docker top nx-a -o pid,nlwp,rss,comm
docker top ap-a -o pid,nlwp,rss,comm
# nginx
PID NLWP RSS COMMAND
137299 1 5996 nginx ← マスター
137392 1 2992 nginx ← ワーカー
137393 1 2992 nginx
137394 1 2992 nginx
137395 1 2992 nginx
137396 1 2992 nginx
137397 1 2948 nginx
# Apache (event MPM)
PID NLWP RSS COMMAND
137467 1 3520 httpd ← 親
137531 27 2060 httpd ← 子(27スレッド)
137532 27 2060 httpd
137533 27 2064 httpd
ここに構造の違いがはっきり出ています。nginxのワーカーはNLWP(スレッド数)が「1」、つまり1プロセス1スレッドです。対してApacheの子プロセスはそれぞれ27スレッドを抱えています。
これが「イベント駆動」と「スレッド駆動」の違いの正体です。Apacheは接続を処理するためにスレッドを割り当てますが、nginxは1本のスレッドが多数の接続を並行して面倒を見ます。1つの接続がデータ待ちで止まっている間、そのスレッドは別の接続の処理に回ります。接続の数が増えてもスレッドを増やす必要がありません。
同時接続100本をかけたときの変化
Apacheに同梱されているab(Apache Bench)で、同時接続100本の負荷をかけながら同じ観察をします。
# 負荷を流しっぱなしにする
docker run --rm -d --name ldgen --network nxnet httpd:alpine \
ab -n 200000 -c 100 -k http://172.31.0.10/
sleep 5
docker top nx-a -o pid,nlwp,rss,comm
# nginx : 同時接続100本の最中
PID NLWP RSS COMMAND
137299 1 5996 nginx
137392 1 3260 nginx
137393 1 3260 nginx
137394 1 3260 nginx
137395 1 3260 nginx
137396 1 3260 nginx
137397 1 3260 nginx
プロセス数もスレッド数もまったく変わりません。 7プロセス、全部が1スレッドのままです。メモリだけがワーカーあたり約270KB増えました。
同じことをApacheに対して行います。
# Apache (event MPM) : 同時接続100本の最中
PID NLWP RSS COMMAND
137467 1 3520 httpd
137531 27 5308 httpd
137532 27 5740 httpd
137533 27 5784 httpd
138040 27 5000 httpd ← 以下、負荷で新しく生えた子プロセス
138068 27 4656 httpd
138069 27 4548 httpd
138124 27 4452 httpd
138125 27 4528 httpd
138127 27 4416 httpd
138133 27 4656 httpd
子プロセスが3個から10個に増え、それぞれが27スレッドを持っています。数字を並べると差が明確です。
| 項目 | nginx 1.31.3 | Apache 2.4.68 (event MPM) |
|---|---|---|
| プロセス数(無負荷 → 負荷時) | 7 → 7(変化なし) | 4 → 11 |
| スレッド合計(負荷時) | 7 | 271 |
| RSS合計(無負荷) | 約23.3 MB | 約9.5 MB |
| RSS合計(負荷時) | 約25.0 MB | 約51.4 MB |
処理性能(ab -n 30000 -c 100 -k) | 56,870 req/s | 43,059 req/s |
| 1リクエストあたり平均 | 1.758 ms | 2.322 ms |
読み取れることが3つあります。
- nginxは負荷をかけてもプロセス構造が変わらない。 同時接続が何本になろうとワーカー数は固定で、メモリ消費が接続数に比例して増えません。これが「大量の同時接続に強い」の中身です。
- 無負荷時はApacheのほうが軽い。 23.3MB対9.5MBで、待機状態だけを見ればApacheの勝ちです。nginxが有利になるのは負荷がかかってからで、「常にnginxのほうが省メモリ」というのは正確ではありません。
- 静的ファイル配信の性能差は約1.3倍。 5.7万 req/s対4.3万 req/sで、nginxが3割ほど速い結果でした。ただし「10倍速い」というような差ではありません。この規模の差が効いてくるのは、そもそも毎秒数万リクエストを捌くようなサイトです。
個人ブログや社内システムのようにアクセスが穏やかな環境では、この性能差が体感できることはまずありません。nginxを選ぶ理由は速度よりも、後述するリバースプロキシ機能の使いやすさや設定の見通しの良さであることが多い、というのが実測を踏まえた現実的な結論です。
自分のサーバーの最大同時接続数を計算する
nginxが同時に受けられる接続数は、設定ファイルの2つの値の掛け算で決まります。
grep -E 'worker_processes|worker_connections' /etc/nginx/nginx.conf
nproc
ps -eo pid,comm | grep -c nginx
worker_processes auto;
worker_connections 1024;
6
7
worker_processes autoはCPUコア数と同じ数のワーカーを起動するという指定です。このマシンはnprocが6なので、ワーカーは6個。プロセス数7はマスター1つを足した数と一致します。
最大同時接続数はworker_processes × worker_connections なので、この設定では 6 × 1024 = 6,144接続です。「503が出る」「接続が詰まる」といった症状のときは、まずこの上限に達していないかを疑ってください。なおworker_connectionsを増やす場合は、OS側のファイルディスクリプタ上限(ulimit -n)も併せて引き上げないと効果がありません。1接続がディスクリプタを消費するためです。
nginxのインストールと基本操作
Ubuntu / Debian系ならパッケージ管理から入ります。
# Ubuntu / Debian
sudo apt update && sudo apt install -y nginx
# Rocky Linux / RHEL系
sudo dnf install -y nginx
ここでディストリビューション間の差に注意してください。Ubuntuのnginxはインストール直後から起動し、自動起動も有効になります。一方Rocky Linuxなどでは停止したままなので、自分で起動と自動起動の設定が必要です。aptの使い方はapt / apt-get コマンド|Debian・Ubuntu でパッケージを管理する完全ガイドにまとめています。
systemctl is-active nginx # 動いているか
systemctl is-enabled nginx # 自動起動が有効か
sudo systemctl enable --now nginx # 起動と自動起動をまとめて有効化
active
enabled
日常的に使うのは次の5つです。
| コマンド | 用途 | サービスへの影響 |
|---|---|---|
nginx -v | バージョン確認 | なし |
sudo nginx -t | 設定ファイルの構文チェック | なし |
sudo nginx -T | 読み込まれる設定を全部展開して表示 | なし |
sudo systemctl reload nginx | 設定の再読み込み | 無停止 |
sudo systemctl restart nginx | プロセスごと再起動 | 瞬断あり・失敗すると復旧しない |
nginx -vには1つ罠があります。バージョン情報は標準出力ではなく標準エラー出力に書かれるため、nginx -v | grep 1.24のようにパイプでつないでも何も表示されません。nginx -v 2>&1 | grep 1.24と書く必要があります。systemctl全般の使い方はsystemctlコマンドの使い方|起動・停止・自動起動を今すぐ確認を参照してください。
設定ファイルの構造と読み方
nginxの設定は/etc/nginx/の下に分散していて、初見ではどこを触ればよいか分かりにくい構造をしています。
ls /etc/nginx/
conf.d modules-enabled scgi_params
fastcgi.conf nginx.conf sites-available
fastcgi_params proxy_params sites-enabled
mime.types snippets uwsgi_params
押さえるべきは3つです。
| 場所 | 役割 |
|---|---|
nginx.conf | 全体の親玉。ワーカー数・ログ形式・gzipなどサーバー共通の設定 |
sites-available/ | サイトごとの設定を置いておく場所(Debian系の慣習) |
sites-enabled/ | そのうち有効にするものだけをシンボリックリンクで並べる場所 |
ls -l /etc/nginx/sites-enabled/
lrwxrwxrwx 1 root root 37 May 12 06:05 valet.conf -> /etc/nginx/sites-available/valet.conf
実体はsites-available側にあり、sites-enabledにはリンクだけが置かれています。サイトを一時的に止めたいときはファイルを消さず、sites-enabledのリンクを外すだけで済むのがこの仕組みの利点です。なおsites-available/sites-enabledはDebian系パッケージ独自の慣習で、公式版やRHEL系には存在せずconf.d/にまとめる形になります。lnの使い方はシンボリックリンクとは?意味とlnコマンドでの作成方法で解説しています。
設定の階層とディレクティブ
nginx.confを開くと、設定が入れ子のブロックになっているのが分かります。
grep -vE '^\s*#|^\s*$' /etc/nginx/nginx.conf | head -14
user 'mugi' 'mugi';
worker_processes auto;
pid /run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
sendfile on;
tcp_nopush on;
keepalive_timeout 65;
gzip on;
...
階層はmain(最上位)→ http → server → location の4段です。上の階層で書いた設定は下の階層に引き継がれ、下で同じ項目を書けばそこだけ上書きされます。「サイト全体はgzip有効、この1ディレクトリだけ無効」といった書き分けはこの仕組みで実現します。
| ブロック | 適用範囲 | よく書く内容 |
|---|---|---|
| main | nginx全体 | user、worker_processes、pid |
events | 接続処理 | worker_connections |
http | HTTP全体 | gzip、keepalive_timeout、ログ形式 |
server | 1つのサイト(バーチャルホスト) | listen、server_name、root、SSL証明書 |
location | URLのパスごと | proxy_pass、try_files、アクセス制限 |
「編集したのに反映されない」ときの調べ方
設定ファイルが分散しているぶん、読み込まれていないファイルを編集していたという事故が起こります。推測せず、nginx自身に聞くのが確実です。
sudo nginx -T | grep '^# configuration file'
# configuration file /etc/nginx/nginx.conf:
# configuration file /etc/nginx/mime.types:
# configuration file /etc/nginx/conf.d/default.conf:
includeで読み込まれたファイルまで展開されるので、編集したファイルがこの一覧に出てこなければ、そのファイルは読まれていません。-Tは設定を実際に読むためroot権限が必要で、一般ユーザーだとconfiguration file test failedで終わります。
reloadとrestartの違いを実測する(最重要)
ここが実務でいちばん事故が多い部分です。設定を変えたあとにrestartを使ってはいけない理由を、実際に測って確かめます。
reloadはリクエストを1本も落とさない
連続でリクエストを流し続けながらreloadをかけ、成功件数と失敗件数を数えます。
docker top nx-a -o pid,comm # reload前のPID
PID COMMAND
137299 nginx ← マスター
137392 nginx ← ワーカー
137393 nginx
137394 nginx
137395 nginx
137396 nginx
137397 nginx
# 6秒間リクエストを流し続けるループを裏で走らせる
docker exec nx-a sh -c 'ok=0; ng=0; end=$(( $(date +%s) + 6 ));
while [ $(date +%s) -lt $end ]; do
if wget -q -O /dev/null http://127.0.0.1/ 2>/dev/null; then ok=$((ok+1)); else ng=$((ng+1)); fi
done; echo "成功 $ok 件 / 失敗 $ng 件"' &
sleep 2
docker exec nx-a nginx -s reload
wait
成功 1433 件 / 失敗 0 件
docker top nx-a -o pid,comm # reload後のPID
PID COMMAND
137299 nginx ← マスターは同じPIDのまま
140782 nginx ← ワーカーは全部入れ替わった
140784 nginx
140785 nginx
140787 nginx
140788 nginx
140790 nginx
マスターのPID 137299は変わらず、ワーカーだけが6個すべて新しいPIDに入れ替わりました。 それでいてリクエストは1,433件すべて成功、失敗は0件です。
これがnginxのreloadの仕組みです。マスターが新しい設定を読んで新ワーカーを起動し、古いワーカーは処理中のリクエストを最後まで返してから静かに終了します。ポートを掴んでいるのはマスターなので、待ち受けが途切れる瞬間がありません。
設定ミスがあるとき、reloadとrestartで結果が正反対になる
次が本題です。セミコロンを1つ書き忘れた設定を置いてみます。
cat > bad.conf <<'CONF'
server {
listen 80;
root /usr/share/nginx/html ← セミコロンが無い
index index.html;
}
CONF
docker cp bad.conf nx-a:/etc/nginx/conf.d/bad.conf
docker exec nx-a nginx -t
nginx: [emerg] invalid number of arguments in "root" directive in /etc/nginx/conf.d/bad.conf:4
nginx: configuration file /etc/nginx/nginx.conf test failed
ファイル名と行番号まで教えてくれます。終了ステータスも1になるので、&&でつなげば自動化に組み込めます。この状態でreloadした場合とどうなるか。
docker exec nx-a nginx -s reload
curl -s -o /dev/null -w 'HTTP %{http_code}\n' http://localhost:18100/
nginx: [emerg] invalid number of arguments in "root" directive in /etc/nginx/conf.d/bad.conf:4
HTTP 200
reloadは拒否されましたが、サイトは200を返し続けています。 新しい設定の読み込みに失敗しても、動いている古いワーカーはそのまま働き続けるためです。エラーメッセージを見て直せば、誰にも迷惑をかけずに済みます。
同じ状態で再起動した場合はこうなります。
docker restart nx-a
sleep 3
docker inspect -f '{{.State.Status}}' nx-a
curl -s -m 3 -o /dev/null -w 'HTTP %{http_code}\n' http://localhost:18100/
docker logs nx-a 2>&1 | tail -2
exited
HTTP 000
nginx: [emerg] invalid number of arguments in "root" directive in /etc/nginx/conf.d/bad.conf:4
サービスが完全に停止しました。 HTTP 000はcurlが接続すらできなかったことを意味します。restartは古いプロセスを先に殺してから新しい設定で起動しようとするため、設定が壊れていると「殺したあとに起動できない」状態になります。
実際の運用ではこれがもっと厄介です。今回のようにコンテナだとdocker execすら通らなくなり、設定を直すのにコンテナの作り直しが必要になりました。物理サーバーでもSSHは生きているので直せますが、その間サイトは落ちたままです。結論はひとつです。
sudo nginx -t && sudo systemctl reload nginx
設定変更後は必ずこの1行を使ってください。 &&でつないでいるので、構文チェックに失敗すればreloadは実行されません。restartが必要なのは、worker_processesのように起動時にしか読まれない設定を変えたときや、nginx本体をアップデートしたときだけです。
リバースプロキシ・ロードバランサーとして動かす
nginxがWebサーバー以上に使われているのが、この用途です。バックエンドを2台立てて、nginxに振り分けさせてみます。
cd /tmp/nxdemo
echo "backend-1" > b1.html
echo "backend-2" > b2.html
docker run -d --name nx-b1 --network nxnet --ip 172.31.0.21 \
-v /tmp/nxdemo/b1.html:/usr/share/nginx/html/index.html:ro nginx:alpine
docker run -d --name nx-b2 --network nxnet --ip 172.31.0.22 \
-v /tmp/nxdemo/b2.html:/usr/share/nginx/html/index.html:ro nginx:alpine
振り分け役の設定です。upstreamで転送先のグループを定義し、proxy_passでそこへ渡します。
cat > lb.conf <<'CONF'
upstream app {
server 172.31.0.21:80;
server 172.31.0.22:80;
}
server {
listen 80;
location / {
proxy_pass http://app;
proxy_set_header X-Real-IP $remote_addr;
add_header X-Upstream $upstream_addr always;
}
}
CONF
docker run -d --name nx-lb --network nxnet --ip 172.31.0.20 -p 18110:80 \
-v /tmp/nxdemo/lb.conf:/etc/nginx/conf.d/default.conf:ro nginx:alpine
14回叩いて、どちらに振られたかを見ます。
for i in $(seq 1 14); do curl -s http://localhost:18110/ | sed 's/backend-//' | tr -d '\n'; echo -n " "; done; echo
1 1 1 1 1 1 2 1 2 1 2 1 2 1
交互にならないのはバグではない
デフォルトの振り分け方式はラウンドロビン(順番に1台ずつ)のはずなのに、最初の6回が全部backend-1に寄っています。しかもこの並びは、reloadしてから測り直しても毎回まったく同じでした。
理由はラウンドロビンのカウンタがワーカープロセスごとに独立しているからです。このnginxのワーカーは6個。起動直後は6個ともカウンタがゼロなので、各ワーカーが最初に受けたリクエストは全部backend-1へ行きます。6回目までで全ワーカーが1周し、7回目以降はカウンタが進んだワーカーがbackend-2へ回し始める、という流れです。先頭に並ぶ「1」の数がワーカー数の6と一致しているのが動かぬ証拠です。
ワーカーを1個に減らすと、きれいに交互になります。
docker exec nx-lb sh -c "sed -i 's/^worker_processes.*/worker_processes 1;/' /etc/nginx/nginx.conf && nginx -s reload"
for i in $(seq 1 10); do curl -s http://localhost:18110/; done
backend-1
backend-2
backend-1
backend-2
backend-1
backend-2
backend-1
backend-2
backend-1
backend-2
仮説どおりでした。「ロードバランサーを組んだのに偏っている」と思ったら、まずワーカー数を疑ってください。 数十リクエスト以上を流せば全体としては均等になります。逆に、数回のcurlで振り分けを検証しようとすると誤った結論に達します。
1台落としても200を返し続ける
バックエンドを1台止めて、クライアントから見た挙動を確認します。
docker stop nx-b2
for i in $(seq 1 6); do curl -s -o /dev/null -w '%{http_code}:' http://localhost:18110/; curl -s http://localhost:18110/; done
200:backend-1
200:backend-1
200:backend-1
200:backend-1
200:backend-1
200:backend-1
クライアントから見ればまったくの正常です。nginxは転送先が応答しないと判断すると、自動でもう1台へ回し直します。これが冗長化の効果で、稼働率の考え方は可用性とは?稼働率の計算と高可用性の作り方で扱っています。
ただし異常が起きた事実はエラーログにしか残りません。
docker logs nx-lb 2>&1 | grep -i error | tail -2
[error] upstream timed out (110: Operation timed out) while connecting to upstream,
client: 172.31.0.1, request: "GET / HTTP/1.1", upstream: "http://172.31.0.22:80/"
[error] connect() failed (113: Host is unreachable) while connecting to upstream,
client: 172.31.0.1, request: "GET / HTTP/1.1", upstream: "http://172.31.0.22:80/"
HTTPステータスだけを見る死活監視では、「2台のうち1台が死んだまま何週間も気づかない」という状態が起こり得ます。残り1台も落ちたときに初めてサイトが停止します。ロードバランサーを組んだら、必ずバックエンド個別の監視とエラーログの監視をセットで用意してください。ログの追い方はログ調査がサクサク進む:grep・awk・sedで原因特定する実務パターン集にまとめています。
バックエンドにはクライアントのIPが届かない
リバースプロキシを挟むと必ず遭遇する問題です。バックエンド側で接続元IPとヘッダーの両方を表示させてみます。
cat > echo.conf <<'CONF'
server {
listen 80;
location / {
default_type text/plain;
return 200 "接続元(remote_addr): $remote_addr
X-Real-IP ヘッダー : $http_x_real_ip
";
}
}
CONF
docker rm -f nx-b1
docker run -d --name nx-b1 --network nxnet --ip 172.31.0.21 \
-v /tmp/nxdemo/echo.conf:/etc/nginx/conf.d/default.conf:ro nginx:alpine
curl -s http://localhost:18110/
接続元(remote_addr): 172.31.0.20
X-Real-IP ヘッダー : 172.31.0.1
バックエンドから見た接続元は172.31.0.20、つまりロードバランサー自身です。実際のクライアントである172.31.0.1は消えています。TCP接続がプロキシで張り直されるため、当然といえば当然の結果です。
これを放置すると、アクセスログの接続元が全部同じIPになり、アクセス解析もIP制限も不正アクセスの追跡もできなくなります。対策が設定に入れておいた1行です。
proxy_set_header X-Real-IP $remote_addr;
これで本当のクライアントIPがヘッダーとして届くので、バックエンド側は$http_x_real_ipで取り出せます。複数のプロキシを経由する構成では、経路を積み重ねて記録するX-Forwarded-Forを使うのが標準です。
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $host;
X-Forwarded-Protoも忘れやすい項目です。nginxでHTTPSを終端して後ろへはHTTPで渡す構成の場合、これが無いとバックエンドのアプリは「HTTPで来た」と誤認し、リダイレクトループやCSSが読み込めない不具合の原因になります。
nginxとApacheの違い
ここまでの実測を踏まえて整理します。
| 観点 | nginx | Apache |
|---|---|---|
| 接続の処理方式 | イベント駆動(少数のスレッドが多数の接続を担当) | MPMによる(event / worker / prefork) |
| 同時接続増加時 | プロセス・スレッド数は変わらない | 子プロセスが増える |
| 静的ファイル配信 | 速い(実測 約5.7万 req/s) | 実測 約4.3万 req/s |
| 動的コンテンツ | 外部プロセス(PHP-FPM等)へ転送する前提 | モジュール(mod_php等)で自分でも実行できる |
| ディレクトリ単位の設定 | .htaccessに相当する仕組みが無い | .htaccessで管理者以外も設定変更できる |
| 設定の反映 | reloadで無停止(実測 失敗0件) | gracefulで同様のことができる |
| 設定ファイル | 階層が明快で1ファイルに集約しやすい | モジュールごとに設定が分かれ量が多い |
移行時にいちばん引っかかるのが.htaccessが使えない点です。Apacheはディレクトリに.htaccessを置けばリライトやアクセス制限を設定できますが、nginxにこの仕組みはありません。すべてnginx.conf系のファイルに書き、reloadで反映する必要があります。
これは不便に見えて、実は設計思想の違いです。.htaccessはリクエストのたびにディレクトリを遡って探索するため性能を落とします。nginxは起動時に設定を全部読み込む代わりに、リクエスト処理中はファイルを探しに行かないという割り切りをしています。速さの一因はここにもあります。
選び方の目安はこうです。レンタルサーバーのように利用者ごとに設定を任せたい、既存の.htaccess資産が多い、mod_phpで完結させたいならApache。リバースプロキシやロードバランサーを兼ねたい、同時接続数が多い、コンテナ構成で使いたいならnginxです。両方を「前段nginx・後段Apache」と並べる構成も一般的で、どちらか一方を選ばなければならないわけではありません。
よくあるエラーと対処
nginxのトラブルはほぼすべてエラーログに答えが書かれています。まずここを見る癖をつけてください。
sudo tail -20 /var/log/nginx/error.log # 動作中のエラー
sudo journalctl -u nginx -n 20 --no-pager # 起動・停止の失敗
使い分けは明確です。404や502といったリクエスト処理中のエラーはerror.log、起動できない・落ちたといったプロセスの生死はjournalctlを見ます。設定ミスで起動に失敗したケースはファイル側に何も残らないことが多いので、両方を確認してください。journalctlの詳細はjournalctlのオプション一覧|使い方をコマンド例で解説にまとめています。
403と404はログのメッセージで原因が分かれる
同じ403でも原因は複数あります。3パターンを実際に起こして、ログの違いを見比べます。
# ①存在しないファイル
curl -s -o /dev/null -w 'HTTP %{http_code}\n' http://localhost:18100/nothing.html
# ②indexが無いディレクトリ
docker exec nx-a mkdir -p /usr/share/nginx/html/sub
curl -s -o /dev/null -w 'HTTP %{http_code}\n' http://localhost:18100/sub/
# ③読み取り権限が無いファイル
docker exec nx-a sh -c 'echo hi > /usr/share/nginx/html/secret.html && chmod 000 /usr/share/nginx/html/secret.html'
curl -s -o /dev/null -w 'HTTP %{http_code}\n' http://localhost:18100/secret.html
HTTP 404
[error] open() "/usr/share/nginx/html/nothing.html" failed (2: No such file or directory)
HTTP 403
[error] directory index of "/usr/share/nginx/html/sub/" is forbidden
HTTP 403
[error] open() "/usr/share/nginx/html/secret.html" failed (13: Permission denied)
ブラウザには同じ403が出ますが、ログを見れば「一覧表示が禁止されている」のか「権限が足りない」のかが即座に分かります。②はindex.htmlを置くかautoindex on;を設定して解決、③はファイルの権限をnginx実行ユーザーが読める状態に直して解決です。権限の直し方はパーミッションとは?Linuxの権限の仕組みとchmodによる変更方法を参照してください。
起動できない(Address in use)
すでに80番を使っているプロセスがある状態でnginxを起動すると、こうなります。
nginx: [emerg] bind() to 0.0.0.0:80 failed (98: Address in use)
犯人を特定します。Apacheが動いたままだった、というのが定番です。
sudo ss -tlnp | grep ':80 '
-pで他人のプロセス名を見るにはroot権限が必要で、一般ユーザーだとプロセス名の欄が空になります。「ポートは埋まっているのに正体が分からない」ときはsudoの付け忘れを疑ってください。詳しくはLinuxのポート確認方法とポート競合の解消手順にまとめています。
症状別の早見表
| 症状 | よくある原因 | 最初に打つコマンド |
|---|---|---|
起動しない(Address in use) | ApacheなどがすでにポートLISTEN中 | sudo ss -tlnp | grep ':80 ' |
起動しない([emerg]その他) | 設定ファイルの構文ミス | sudo nginx -t |
| 404 | rootのパス誤り、ファイルが無い | sudo tail /var/log/nginx/error.log |
| 403 | indexが無い/読み取り権限が無い | 同上(メッセージで判別) |
| 502 Bad Gateway | 転送先が停止/ポート番号の指定ミス | sudo ss -tlnpで転送先を確認 |
| 504 Gateway Timeout | 転送先が応答しない/処理に時間がかかりすぎ | 転送先の負荷とファイアウォール |
| 設定を変えたのに反映されない | 読まれていないファイルを編集/reload忘れ | sudo nginx -T | grep 'configuration file' |
| 外部からだけ繋がらない | ファイアウォールが80/443を塞いでいる | sudo ufw status |
| 再起動後だけ動かない | 自動起動が無効のまま | systemctl is-enabled nginx |
502と504は原因が違います。502は「転送先まで届いたが接続を拒否された」(プロセス停止・ポート誤り)、504は「投げたが返事が来ない」(応答遅延・経路遮断)です。エラーコードから調べる先を絞り込めます。実サーバーでの追い方は500エラーの原因切り分け|Nginx・PHP-FPM・アプリを5分で特定する手順にまとめています。ファイアウォールの設定はufw – Ubuntuファイアウォールの有効化・ポート許可・確認コマンド一覧を参照してください。
検証に使ったコンテナは片づけておきます。
docker rm -f nx-a ap-a nx-lb nx-b1 nx-b2
docker network rm nxnet
よくある質問
nginxの読み方は何ですか?
「エンジンエックス」です。engine xを縮めた綴りに由来します。「エヌジンクス」「ンギンクス」と読まれることがありますが、公式の読み方はエンジンエックスです。表記は小文字のnginxが一般的で、F5の製品ブランドとしては大文字のNGINXが使われます。
nginxとApacheはどちらを選べばよいですか?
リバースプロキシやロードバランサーを兼ねたい、同時接続数が多い、コンテナで動かすならnginxです。.htaccessで利用者ごとに設定を任せたい、既存のApache資産が多い、mod_phpで完結させたいならApacheです。実測では静的ファイル配信でnginxが約1.3倍速い結果でしたが、アクセスが穏やかなサイトでこの差が体感できることはまずありません。既存環境が安定して動いているなら、性能だけを理由に乗り換える必要はありません。
nginxが本当に軽いのはどういう場面ですか?
同時接続数が多い場面です。 実測では、同時接続100本をかけてもnginxはプロセス数7・スレッド数7のまま変わらず、メモリは約25MBでした。同条件のApache(event MPM)は子プロセスが3個から10個に増え、スレッド合計271、メモリ約51MBです。一方、無負荷の待機状態ではApacheのほうが軽い(9.5MB対23.3MB)という結果も出ています。「常にnginxのほうが省メモリ」ではなく、「接続が増えても増え方が緩やか」が正確な理解です。
設定を変えたあとはreloadとrestartのどちらを使いますか?
原則reloadです。 実測では、リクエストを流し続けながらreloadしても1,433件すべて成功し、失敗は0件でした。さらに重要なのは設定ミスがあったときの挙動で、reloadなら拒否されるだけでサイトは200を返し続けますが、restartするとサービスが完全に停止(HTTP 000)します。sudo nginx -t && sudo systemctl reload nginxを定型句にしてください。restartが必要なのはworker_processesなど起動時にしか読まれない設定を変えたときと、nginx本体を更新したときだけです。
ロードバランサーにしたのに振り分けが偏るのはなぜですか?
ラウンドロビンのカウンタがワーカープロセスごとに独立しているためです。 ワーカーが6個ある環境では、起動直後の6リクエストが全部1台目に集まりました(1 1 1 1 1 1 2 1 2 1 2 1 2 1)。worker_processes 1にすると厳密に交互になることも確認済みです。数十リクエスト以上を流せば全体としては均等になるので、数回のcurlで振り分けを判断しないでください。
nginxの設定ファイルはどこにありますか?
Ubuntu / Debian系では/etc/nginx/nginx.confが親で、サイトごとの設定は/etc/nginx/sites-available/に置き、有効にするものだけsites-enabled/へシンボリックリンクを張ります。RHEL系や公式パッケージではsites-*が無く/etc/nginx/conf.d/にまとめます。実際に読み込まれているファイルを確実に知るにはsudo nginx -T | grep 'configuration file'を使ってください。編集したファイルがこの一覧に出てこなければ、そのファイルは読まれていません。
nginxで.htaccessは使えますか?
使えません。 nginxには.htaccessに相当する仕組み自体がありません。リライトやアクセス制限はserver/locationブロックに書き、reloadで反映します。これは性能面の設計判断でもあり、リクエストごとにディレクトリを遡って設定ファイルを探す処理が発生しないぶん高速です。Apacheから移行するときは、.htaccessの内容をnginxの設定へ書き換える作業が必要になります。
nginxはPHPを動かせますか?
nginx単体では実行できません。Apacheのmod_phpに相当するモジュールが無いためで、PHP-FPMという別プロセスを立ててfastcgi_passで転送する構成にします。「nginxが受け取り、PHP-FPMが実行し、DBに問い合わせる」というWeb3層の流れはミドルウェアとは?種類・具体例とサーバーでの確認コマンドで実際に組んで解説しています。
まとめ
- nginx(エンジンエックス)はWebサーバーとリバースプロキシを兼ねるソフトウェア。2004年公開、現在はF5が開発を継続
- ワーカーは1プロセス1スレッドで多数の接続を捌く。同時接続100本でもプロセス数7・スレッド数7のまま変わらなかった
- 負荷時のメモリは約25MB対Apache約51MB、静的配信は約5.7万 req/s対約4.3万 req/s。ただし無負荷時はApacheのほうが軽い
- 最大同時接続数は
worker_processes × worker_connections。実機では 6 × 1024 = 6,144 - 設定変更後は必ず
sudo nginx -t && sudo systemctl reload nginx。reloadは失敗0件、restartは設定ミス時にサービス全停止という実測差がある - ロードバランサーのラウンドロビンはワーカーごとに独立したカウンタで動く。数回のcurlでは偏って見える
- リバースプロキシを挟むとバックエンドにクライアントIPが届かない。
proxy_set_header X-Real-IPが必須 - 403・404・502はエラーログのメッセージで原因が特定できる。まず
/var/log/nginx/error.logを見る
まずは自分のサーバーでnginx -vとsudo nginx -T | grep 'configuration file'を実行し、どのバージョンがどの設定ファイルで動いているかを把握するところから始めてみてください。この2つが分かっていれば、トラブルのときに見るべき場所が最初から絞れます。
関連記事
- ミドルウェアとは?種類・具体例とサーバーでの確認コマンドを解説
- 500エラーの原因切り分け|Nginx・PHP-FPM・アプリを5分で特定する手順
- systemctlコマンドの使い方|起動・停止・自動起動を今すぐ確認
- Linuxのポート確認方法とポート競合の解消手順|netstat・lsof・ss完全ガイド
- 可用性とは?稼働率の計算と高可用性の作り方をコマンドで解説
- journalctlのオプション一覧|使い方をコマンド例で解説
- ufw – Ubuntuファイアウォールの有効化・ポート許可・確認コマンド一覧
- apt / apt-get コマンド|Debian・Ubuntu でパッケージを管理する完全ガイド
- ログ調査がサクサク進む:grep・awk・sedで原因特定する実務パターン集
- Linuxとは?OS・カーネル・ディストリビューションの違いを初心者向けに解説
- Linuxトラブルシューティング総合ハブ|まず試す簡易対処と詳細ガイド

コメント