はじめに
「Webサーバーって結局何をしているの?」
「ApacheとNginxの違いは?」
「リバースプロキシってよく聞くけど、何のために使うの?」
Web開発をしていると必ず出会うNginx。本記事では、Nginxの基礎概念から実践的な設定まで、なぜそうするのかを丁寧に解説します。
Webサーバーとは何か
Webサーバーの役割
Webサーバーは、HTTPリクエストを受け取り、HTTPレスポンスを返すプログラムです。ブラウザでhttps://example.comにアクセスすると、以下のことが起こります:
┌─────────────────────────────────────────────────────────────┐
│ HTTPリクエストの流れ │
│ │
│ ブラウザ Webサーバー │
│ │ │ │
│ │──── GET /index.html HTTP/1.1 ─────────────▶│ │
│ │ │ │
│ │ ファイルを探してレスポンスを生成 │ │
│ │ │ │
│ │◀─── HTTP/1.1 200 OK ──────────────────────│ │
│ │ Content-Type: text/html │ │
│ │ <html>...</html> │ │
│ │
└─────────────────────────────────────────────────────────────┘
Webサーバーの主な仕事は:
- 静的ファイルの配信: HTML、CSS、JavaScript、画像などをそのまま返す
- 動的コンテンツの処理: PHP、Pythonなどのアプリケーションに処理を委譲
- セキュリティ: HTTPS対応、アクセス制御
- パフォーマンス: キャッシュ、圧縮、同時接続の効率的な処理
なぜNginxが選ばれるのか
Webサーバーには主にApacheとNginxの2つの選択肢があります。
Apache HTTP Server
- 1995年登場の老舗
-
.htaccessで柔軟な設定が可能 - モジュールが豊富
- プロセス/スレッドベース:リクエストごとにプロセスやスレッドを生成
Nginx
- 2004年登場
- 高性能・軽量
- 設定がシンプル
- イベント駆動(非同期):少ないリソースで大量の同時接続を処理
なぜNginxの方が高速なのか?
Apacheは「1リクエスト = 1プロセス(またはスレッド)」モデルです。10,000同時接続があれば、10,000のプロセスが必要になり、メモリを大量に消費します。
Nginxは「イベント駆動」モデルです。少数のワーカープロセスが、非同期I/Oを使って大量のリクエストを効率的に処理します。これにより、同時接続数が増えてもメモリ使用量がほぼ一定です。
┌─────────────────────────────────────────────────────────────┐
│ 処理モデルの違い │
│ │
│ Apache(プロセスベース) │
│ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │
│ │Proc │ │Proc │ │Proc │ │Proc │ ...(接続数分必要) │
│ └─────┘ └─────┘ └─────┘ └─────┘ │
│ │
│ Nginx(イベント駆動) │
│ ┌──────────────────────────────────────┐ │
│ │ Worker Process │ │
│ │ ┌───┐ ┌───┐ ┌───┐ ┌───┐ ┌───┐ │ (少数で多数処理)│
│ │ │ │ │ │ │ │ │ │ │ │ ... │ │
│ │ └───┘ └───┘ └───┘ └───┘ └───┘ │ │
│ └──────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
どちらを選ぶべきか?
| ユースケース | 推奨 |
|---|---|
| 静的コンテンツ配信 | Nginx |
| リバースプロキシ | Nginx |
| 高トラフィックサイト | Nginx |
.htaccessが必要 |
Apache |
| レガシーPHPアプリ | Apache(またはNginx + PHP-FPM) |
現代のWebでは、Nginxをフロントに置き、バックエンドのアプリケーションサーバーにプロキシする構成が主流です。
環境構築
インストール
# Ubuntu/Debian
sudo apt update
sudo apt install nginx
# CentOS/RHEL
sudo yum install nginx
# macOS (Homebrew)
brew install nginx
# Docker
docker run -d --name nginx -p 80:80 nginx:alpine
基本操作
# 起動
sudo systemctl start nginx
# 停止
sudo systemctl stop nginx
# 再起動
sudo systemctl restart nginx
# 設定のリロード(ダウンタイムなし)
# 新しいワーカープロセスを起動し、古いプロセスを徐々に終了
sudo systemctl reload nginx
# 設定ファイルの文法チェック(適用前に必ず実行)
sudo nginx -t
# 状態確認
sudo systemctl status nginx
reloadとrestartの違い
-
restart: プロセスを完全に停止して再起動(一瞬ダウンタイムが発生) -
reload: 設定を再読み込みしつつ、処理中のリクエストは継続(ダウンタイムなし)
本番環境では基本的にreloadを使います。
ディレクトリ構造
/etc/nginx/
├── nginx.conf # メイン設定ファイル
├── conf.d/ # 追加設定ファイル(*.conf)
├── sites-available/ # 利用可能なサイト設定(Debian系)
├── sites-enabled/ # 有効なサイト設定(シンボリックリンク)
├── mime.types # MIMEタイプ定義
└── modules-enabled/ # 有効なモジュール
/var/log/nginx/
├── access.log # アクセスログ
└── error.log # エラーログ
/var/www/html/ # デフォルトのドキュメントルート
設定ファイルの基本
設定ファイルの構造
Nginxの設定はディレクティブと**コンテキスト(ブロック)**で構成されます。
# nginx.conf
# グローバルコンテキスト(最上位)
# Nginx全体の動作に関する設定
user nginx; # 実行ユーザー
worker_processes auto; # ワーカープロセス数(CPUコア数に合わせる)
error_log /var/log/nginx/error.log; # エラーログの場所
pid /run/nginx.pid; # プロセスIDファイル
# イベントコンテキスト
# 接続処理に関する設定
events {
worker_connections 1024; # 1ワーカーあたりの最大同時接続数
multi_accept on; # 複数の接続を一度に受け入れる
}
# HTTPコンテキスト
# HTTP通信に関する設定
http {
# 基本設定
include /etc/nginx/mime.types; # MIMEタイプ定義を読み込み
default_type application/octet-stream;
# ログフォーマット
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent"';
access_log /var/log/nginx/access.log main;
# パフォーマンス設定
sendfile on; # カーネル空間でファイルを直接送信
tcp_nopush on; # sendfileと組み合わせて効率化
keepalive_timeout 65; # Keep-Alive接続のタイムアウト
gzip on; # レスポンスを圧縮
# サーバー設定を読み込み
include /etc/nginx/conf.d/*.conf;
}
ディレクティブのルール
- セミコロン(
;)で終わる - 大文字小文字を区別する
- ブロック(
{})内に入れ子にできる
静的ファイルの配信
最もシンプルな使い方:HTMLファイルを配信する
# /etc/nginx/conf.d/static-site.conf
server {
# このサーバーブロックが受け付けるポート
listen 80;
# このサーバーブロックが受け付けるドメイン名
# 複数指定可能。_はすべてにマッチするワイルドカード
server_name example.com www.example.com;
# ドキュメントルート(ファイルを探す起点)
root /var/www/example.com/public;
# デフォルトのインデックスファイル
index index.html index.htm;
# ロケーションブロック:URLパスに応じた処理
location / {
# try_files: ファイルを順に探す
# $uri → $uri/ → 404エラー の順で探す
try_files $uri $uri/ =404;
}
# 静的アセットのキャッシュ設定
location ~* \.(css|js|jpg|jpeg|png|gif|ico|svg|woff|woff2)$ {
# ブラウザキャッシュを30日に設定
expires 30d;
# キャッシュ可能であることを明示
add_header Cache-Control "public, immutable";
}
# エラーページのカスタマイズ
error_page 404 /404.html;
error_page 500 502 503 504 /50x.html;
}
locationディレクティブの優先順位
locationは複数のマッチ方法があり、優先順位があります:
| 記法 | 意味 | 優先度 |
|---|---|---|
= /path |
完全一致 | 最高 |
^~ /path |
前方一致(正規表現より優先) | 高 |
~ /pattern |
正規表現(大文字小文字区別) | 中 |
~* /pattern |
正規表現(大文字小文字区別なし) | 中 |
/path |
前方一致 | 低 |
# 完全一致(最優先)
location = /favicon.ico {
log_not_found off;
}
# 正規表現より優先される前方一致
location ^~ /static/ {
root /var/www/static;
}
# 正規表現(大文字小文字区別なし)
location ~* \.(jpg|jpeg|png)$ {
expires 30d;
}
# 通常の前方一致(最後に評価)
location / {
try_files $uri $uri/ =404;
}
リバースプロキシ
リバースプロキシとは
リバースプロキシは、クライアントとバックエンドサーバーの間に立ち、リクエストを中継するサーバーです。
┌─────────────────────────────────────────────────────────────┐
│ リバースプロキシの構成 │
│ │
│ クライアント │
│ │ │
│ │ ① HTTPリクエスト │
│ ▼ │
│ ┌───────────────────────┐ │
│ │ Nginx(リバースプロキシ)│ │
│ │ ・SSL終端 │ │
│ │ ・キャッシュ │ │
│ │ ・負荷分散 │ │
│ └───────────────────────┘ │
│ │ │
│ │ ② プロキシリクエスト │
│ ▼ │
│ ┌───────────────────────┐ │
│ │ アプリケーションサーバー │ │
│ │(Node.js, Django等) │ │
│ └───────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
なぜリバースプロキシを使うのか?
- SSL終端: NginxでHTTPSを処理し、バックエンドはHTTPで通信(証明書管理が楽)
- 静的ファイルの分離: 静的ファイルはNginxが直接配信、動的コンテンツだけバックエンドへ
- 負荷分散: 複数のバックエンドサーバーにリクエストを分散
- キャッシュ: よく使うレスポンスをNginxでキャッシュして高速化
- セキュリティ: バックエンドを直接公開しない(攻撃対象を減らす)
基本的なプロキシ設定
# /etc/nginx/conf.d/proxy.conf
server {
listen 80;
server_name app.example.com;
location / {
# バックエンドサーバーにプロキシ
# proxy_pass の末尾スラッシュの有無で動作が変わる点に注意
proxy_pass http://localhost:3000;
# ========================================
# 重要なプロキシヘッダー設定
# ========================================
# クライアントの実際のホスト名をバックエンドに伝える
# これがないと、バックエンドはlocalhost:3000宛のリクエストと認識
proxy_set_header Host $host;
# クライアントの実際のIPアドレスを伝える
# ログやレート制限で正しいIPを使うために必須
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# 元のプロトコル(http/https)を伝える
# アプリがリダイレクトURLを生成するときに必要
proxy_set_header X-Forwarded-Proto $scheme;
# WebSocket対応
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# タイムアウト設定
proxy_connect_timeout 60s; # バックエンドへの接続タイムアウト
proxy_send_timeout 60s; # バックエンドへの送信タイムアウト
proxy_read_timeout 60s; # バックエンドからの読み取りタイムアウト
}
# 静的ファイルはNginxが直接配信(バックエンドに負荷をかけない)
location /static/ {
root /var/www/app;
expires 30d;
}
}
負荷分散(ロードバランシング)
なぜ負荷分散が必要か
1つのサーバーには処理能力の限界があります。アクセスが増えると、レスポンスが遅くなったり、サーバーがダウンしたりします。
負荷分散は、複数のサーバーにリクエストを分散することで:
- スケーラビリティ: 処理能力を水平に拡張
- 可用性: 1台がダウンしても他のサーバーで継続
- メンテナンス: ローリングアップデートが可能
upstreamによる負荷分散
# /etc/nginx/conf.d/load-balance.conf
# バックエンドサーバーのグループを定義
upstream backend {
# ========================================
# 負荷分散アルゴリズム
# ========================================
# デフォルト: ラウンドロビン(順番に分散)
# 何も指定しなければラウンドロビン
# 接続数が少ないサーバーを優先
# least_conn;
# IPアドレスでハッシュ(同じクライアントは同じサーバーへ)
# セッションを維持したい場合に有効
# ip_hash;
# バックエンドサーバーの定義
server 192.168.1.10:3000 weight=3; # 重み付け(3倍のリクエストを受ける)
server 192.168.1.11:3000 weight=2;
server 192.168.1.12:3000; # weight=1(デフォルト)
# バックアップサーバー(他がすべてダウンしたときのみ使用)
server 192.168.1.20:3000 backup;
# ヘルスチェック設定
# 3回失敗したら30秒間リクエストを送らない
server 192.168.1.10:3000 max_fails=3 fail_timeout=30s;
# Keep-Alive接続を維持(パフォーマンス向上)
keepalive 32;
}
server {
listen 80;
server_name app.example.com;
location / {
proxy_pass http://backend;
# Keep-Alive接続を使用
proxy_http_version 1.1;
proxy_set_header Connection "";
# 標準のプロキシヘッダー
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
負荷分散アルゴリズムの選び方
| アルゴリズム | 説明 | 適用場面 |
|---|---|---|
| ラウンドロビン(デフォルト) | 順番に分散 | 一般的な用途 |
| least_conn | 接続数が少ないサーバー優先 | 処理時間にばらつきがある場合 |
| ip_hash | 同じIPは同じサーバーへ | セッション維持が必要な場合 |
| hash | 任意のキーでハッシュ | 高度なカスタマイズ |
HTTPS対応
SSL/TLS証明書の設定
server {
# HTTPSポート(443)でリッスン
# sslオプションでSSL/TLSを有効化
# http2オプションでHTTP/2を有効化(パフォーマンス向上)
listen 443 ssl http2;
server_name example.com;
# ========================================
# SSL証明書の設定
# ========================================
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# ========================================
# SSL/TLSセキュリティ設定
# ========================================
# 使用するプロトコルバージョン
# TLS 1.2と1.3のみ許可(古いバージョンは脆弱性あり)
ssl_protocols TLSv1.2 TLSv1.3;
# 暗号スイートの設定
# 安全な暗号のみ使用
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
# サーバー側の暗号設定を優先
ssl_prefer_server_ciphers on;
# SSLセッションキャッシュ(ハンドシェイク高速化)
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
# OCSP Stapling(証明書検証の高速化)
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
# ========================================
# セキュリティヘッダー
# ========================================
# HSTS(HTTPSを強制)
# ブラウザに「今後はHTTPSでのみアクセス」と記憶させる
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# その他のセキュリティヘッダー
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
add_header X-XSS-Protection "1; mode=block";
location / {
root /var/www/example.com;
index index.html;
}
}
# HTTPからHTTPSへのリダイレクト
server {
listen 80;
server_name example.com;
# 301リダイレクト(恒久的な移動)
return 301 https://$server_name$request_uri;
}
Let's Encryptで無料SSL証明書
# Certbotのインストール
sudo apt install certbot python3-certbot-nginx
# 証明書の取得とNginx設定の自動更新
sudo certbot --nginx -d example.com -d www.example.com
# 証明書の更新テスト
sudo certbot renew --dry-run
# 自動更新の設定(cronまたはsystemdタイマー)
# Certbotはインストール時に自動で設定される
パフォーマンスチューニング
基本的な最適化
http {
# ========================================
# ファイル送信の最適化
# ========================================
# sendfile: ディスク→ネットワークを直接転送(コピーをスキップ)
sendfile on;
# sendfileと組み合わせてパケットを最適化
tcp_nopush on;
# 小さなパケットをすぐに送信
tcp_nodelay on;
# ========================================
# Gzip圧縮
# ========================================
gzip on;
gzip_vary on; # Varyヘッダーを付与(プロキシキャッシュ対応)
gzip_min_length 1024; # 1KB以上のファイルを圧縮
gzip_comp_level 5; # 圧縮レベル(1-9、5が良いバランス)
gzip_proxied any; # プロキシ経由のリクエストも圧縮
gzip_types text/plain text/css text/xml application/json application/javascript
application/xml application/rss+xml image/svg+xml;
# ========================================
# バッファ設定
# ========================================
# クライアントリクエストボディのバッファサイズ
client_body_buffer_size 16k;
# プロキシレスポンスのバッファ
proxy_buffering on;
proxy_buffer_size 4k;
proxy_buffers 8 16k;
# ========================================
# 接続設定
# ========================================
# Keep-Alive接続のタイムアウト
keepalive_timeout 65;
# 1つのKeep-Alive接続で処理するリクエスト数
keepalive_requests 100;
}
キャッシュ設定
http {
# プロキシキャッシュの保存場所
# levels: キャッシュファイルのディレクトリ階層
# keys_zone: キャッシュのメタデータを保持するメモリ領域
# max_size: キャッシュの最大サイズ
# inactive: この期間アクセスがなければ削除
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=my_cache:10m
max_size=10g inactive=60m use_temp_path=off;
server {
location / {
proxy_pass http://backend;
# キャッシュを有効化
proxy_cache my_cache;
# キャッシュキー
proxy_cache_key $scheme$request_method$host$request_uri;
# ステータスコードごとのキャッシュ時間
proxy_cache_valid 200 302 10m; # 成功レスポンスは10分
proxy_cache_valid 404 1m; # 404は1分
# キャッシュの使用条件
proxy_cache_use_stale error timeout invalid_header updating
http_500 http_502 http_503 http_504;
# キャッシュ状態をレスポンスヘッダーに追加(デバッグ用)
add_header X-Cache-Status $upstream_cache_status;
}
}
}
セキュリティ設定
server {
# ========================================
# アクセス制限
# ========================================
# 特定パスへのIP制限
location /admin {
allow 192.168.1.0/24; # 許可するIP
allow 10.0.0.0/8;
deny all; # それ以外は拒否
proxy_pass http://backend;
}
# Basic認証
location /private {
auth_basic "Restricted Area";
auth_basic_user_file /etc/nginx/.htpasswd;
proxy_pass http://backend;
}
# ========================================
# レート制限(DDoS対策)
# ========================================
# リクエストレート制限ゾーンの定義
limit_req_zone $binary_remote_addr zone=one:10m rate=10r/s;
location /api/ {
# レート制限を適用
# burst: 一時的に許可する超過リクエスト数
# nodelay: バースト内のリクエストを即座に処理
limit_req zone=one burst=20 nodelay;
proxy_pass http://backend;
}
# ========================================
# 不要なメソッドの拒否
# ========================================
if ($request_method !~ ^(GET|HEAD|POST|PUT|DELETE)$) {
return 444;
}
# ========================================
# 隠しファイルへのアクセス拒否
# ========================================
location ~ /\. {
deny all;
}
}
実践的な設定例
Node.jsアプリケーション
upstream nodejs {
server 127.0.0.1:3000;
keepalive 64;
}
server {
listen 80;
server_name app.example.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name app.example.com;
ssl_certificate /etc/letsencrypt/live/app.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/app.example.com/privkey.pem;
# 静的ファイル
location /static/ {
alias /var/www/app/public/;
expires 30d;
add_header Cache-Control "public, immutable";
}
# WebSocket
location /socket.io/ {
proxy_pass http://nodejs;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
}
# API
location / {
proxy_pass http://nodejs;
proxy_http_version 1.1;
proxy_set_header Connection "";
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;
}
}
トラブルシューティング
よくある問題と解決法
1. 502 Bad Gateway
- バックエンドサーバーが起動していない
- バックエンドのポートが間違っている
- ファイアウォールでブロックされている
# バックエンドの確認
curl -I http://localhost:3000
# ポートの確認
ss -tlnp | grep 3000
2. 504 Gateway Timeout
- バックエンドの処理が遅い
- タイムアウト設定が短い
# タイムアウトを延長
proxy_connect_timeout 300;
proxy_send_timeout 300;
proxy_read_timeout 300;
3. 403 Forbidden
- ファイルのパーミッション問題
- SELinuxの制限
# パーミッション確認
ls -la /var/www/html/
# Nginxユーザーでアクセス確認
sudo -u nginx stat /var/www/html/index.html
# SELinux確認
getenforce
ls -Z /var/www/html/
ログの確認
# アクセスログ
tail -f /var/log/nginx/access.log
# エラーログ
tail -f /var/log/nginx/error.log
# 特定のエラーを検索
grep "error" /var/log/nginx/error.log | tail -20
まとめ
Nginxは現代のWebインフラに欠かせないツールです。この記事で学んだ内容を整理すると:
- Webサーバーの基本: HTTPリクエストを受けてレスポンスを返す
- NginxとApacheの違い: イベント駆動で高効率
- 静的ファイル配信: rootとlocationの設定
- リバースプロキシ: バックエンドへの中継
- 負荷分散: upstreamで複数サーバーに分散
- HTTPS: SSL/TLS証明書の設定
- パフォーマンス: Gzip、キャッシュ、バッファ
- セキュリティ: アクセス制限、レート制限
まずはシンプルな静的サイトの配信から始めて、徐々にプロキシや負荷分散を追加していくのがおすすめです。設定を変更したら必ずnginx -tで文法チェックを忘れずに!