nginxとは?読み方・仕組み・Apacheとの違いをコマンドで解説

実務レシピ

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の最大の特徴に直結しています。以下、その理由を実測で確かめていきます。

  1. nginxとは|Webサーバーとリバースプロキシを兼ねるソフトウェア
    1. nginxが担う4つの役割
  2. nginxが「速い」と言われる理由をApacheと並べて実測する
    1. まずプロセス構造の違いを見る
    2. 同時接続100本をかけたときの変化
    3. 自分のサーバーの最大同時接続数を計算する
  3. nginxのインストールと基本操作
  4. 設定ファイルの構造と読み方
    1. 設定の階層とディレクティブ
    2. 「編集したのに反映されない」ときの調べ方
  5. reloadとrestartの違いを実測する(最重要)
    1. reloadはリクエストを1本も落とさない
    2. 設定ミスがあるとき、reloadとrestartで結果が正反対になる
  6. リバースプロキシ・ロードバランサーとして動かす
    1. 交互にならないのはバグではない
    2. 1台落としても200を返し続ける
    3. バックエンドにはクライアントのIPが届かない
  7. nginxとApacheの違い
  8. よくあるエラーと対処
    1. 403と404はログのメッセージで原因が分かれる
    2. 起動できない(Address in use)
    3. 症状別の早見表
  9. よくある質問
    1. nginxの読み方は何ですか?
    2. nginxとApacheはどちらを選べばよいですか?
    3. nginxが本当に軽いのはどういう場面ですか?
    4. 設定を変えたあとはreloadとrestartのどちらを使いますか?
    5. ロードバランサーにしたのに振り分けが偏るのはなぜですか?
    6. nginxの設定ファイルはどこにありますか?
    7. nginxで.htaccessは使えますか?
    8. nginxはPHPを動かせますか?
  10. まとめ
  11. 関連記事
  12. 関連記事

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など静的ファイルを自分で読んで返すrootindex
リバースプロキシ受け取ったリクエストを後ろのアプリへ転送し、返事を中継するproxy_passfastcgi_pass
ロードバランサー複数台のバックエンドに振り分けて負荷を分散するupstream
SSL/TLS終端・キャッシュHTTPSの暗号化処理を肩代わりし、応答を一時保存して再利用するssl_certificateproxy_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.3Apache 2.4.68 (event MPM)
プロセス数(無負荷 → 負荷時)7 → 7(変化なし)4 → 11
スレッド合計(負荷時)7271
RSS合計(無負荷)約23.3 MB約9.5 MB
RSS合計(負荷時)約25.0 MB約51.4 MB
処理性能(ab -n 30000 -c 100 -k56,870 req/s43,059 req/s
1リクエストあたり平均1.758 ms2.322 ms

読み取れることが3つあります。

  1. nginxは負荷をかけてもプロセス構造が変わらない。 同時接続が何本になろうとワーカー数は固定で、メモリ消費が接続数に比例して増えません。これが「大量の同時接続に強い」の中身です。
  2. 無負荷時はApacheのほうが軽い。 23.3MB対9.5MBで、待機状態だけを見ればApacheの勝ちです。nginxが有利になるのは負荷がかかってからで、「常にnginxのほうが省メモリ」というのは正確ではありません。
  3. 静的ファイル配信の性能差は約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 autoCPUコア数と同じ数のワーカーを起動するという指定です。このマシンは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-availablesites-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(最上位)→ httpserverlocation の4段です。上の階層で書いた設定は下の階層に引き継がれ、下で同じ項目を書けばそこだけ上書きされます。「サイト全体はgzip有効、この1ディレクトリだけ無効」といった書き分けはこの仕組みで実現します。

ブロック適用範囲よく書く内容
mainnginx全体userworker_processespid
events接続処理worker_connections
httpHTTP全体gzipkeepalive_timeout、ログ形式
server1つのサイト(バーチャルホスト)listenserver_nameroot、SSL証明書
locationURLのパスごとproxy_passtry_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の違い

ここまでの実測を踏まえて整理します。

観点nginxApache
接続の処理方式イベント駆動(少数のスレッドが多数の接続を担当)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 useApacheなどがすでにポートLISTEN中sudo ss -tlnp | grep ':80 '
起動しない([emerg]その他)設定ファイルの構文ミスsudo nginx -t
404rootのパス誤り、ファイルが無いsudo tail /var/log/nginx/error.log
403indexが無い/読み取り権限が無い同上(メッセージで判別)
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に相当する仕組み自体がありません。リライトやアクセス制限はserverlocationブロックに書き、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 nginxreloadは失敗0件、restartは設定ミス時にサービス全停止という実測差がある
  • ロードバランサーのラウンドロビンはワーカーごとに独立したカウンタで動く。数回のcurlでは偏って見える
  • リバースプロキシを挟むとバックエンドにクライアントIPが届かない。proxy_set_header X-Real-IPが必須
  • 403・404・502はエラーログのメッセージで原因が特定できる。まず/var/log/nginx/error.logを見る

まずは自分のサーバーでnginx -vsudo nginx -T | grep 'configuration file'を実行し、どのバージョンがどの設定ファイルで動いているかを把握するところから始めてみてください。この2つが分かっていれば、トラブルのときに見るべき場所が最初から絞れます。

関連記事

Bash玄

はじめまして!Bash玄です。

エンジニアとしてシステム運用に携わる中で、手作業の多さに限界を感じ、Bashスクリプトを活用して業務を効率化したのがきっかけで、この道に入りました。「手作業は負け」「スクリプトはシンプルに」をモットーに、誰でも実践できるBashスクリプトの書き方を発信しています。

このサイトでは、Bashの基礎から実践的なスクリプト作成まで、初心者でもわかりやすく解説しています。少しでも「Bashって便利だな」と思ってもらえたら嬉しいです!

# 好きなこと
- シンプルなコードを書くこと
- コマンドラインを快適にカスタマイズすること
- 自動化で時間を生み出すこと

# このサイトを読んでほしい人
- Bashに興味があるけど、何から始めればいいかわからない人
- 定型業務を自動化したい人
- 効率よくターミナルを使いこなしたい人

Bashの世界に一歩踏み出して、一緒に「Bash道」を極めていきましょう!

Bash玄をフォローする

コメント