nginx-ops について
Nginx Operationsは、本番環境でNginxを設定・運用するためのリファレンススキルです。リバースプロキシ、TLS、ロードバランシング、性能調整に加え、実用的な構成例や設定上の注意点を扱います。
主な機能
- 01リバースプロキシの設定パターン
- 02SSL/TLSの導入と強化
- 03ロードバランシング戦略
- 04パフォーマンス調整ガイド
- 05DockerとSPAの設定例
- 06設定の落とし穴と継承ルール
活用シーン
- 01Nginxをリバースプロキシとして設定
- 02デプロイ環境のTLS設定
- 03シングルページアプリの配信
- 04プロキシやヘッダー動作の調査
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ 0xdarkmatter/ claude-mods --skill nginx-opsインストール後、Claude Code で/nginx-opsを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
assets/.gitkeepreferences/performance.mdreverse-proxy.mdssl-security.mdscripts/.gitkeep詳細
Nginx 運用
本番環境へのデプロイに向けた、包括的な Nginx 設定、リバースプロキシのパターン、SSL/TLS の強化、ロードバランシング戦略、パフォーマンス最適化です。
設定アーキテクチャのクイックリファレンス
nginx.conf (main context)
├── worker_processes auto;
├── worker_rlimit_nofile 65535;
│
├── events { # Connection handling
│ ├── worker_connections 4096;
│ └── multi_accept on;
│ }
│
├── http { # HTTP server settings
│ ├── include mime.types;
│ ├── default_type application/octet-stream;
│ ├── sendfile on;
│ ├── gzip on;
│ │
│ ├── upstream backend { # Load balancing pool
│ │ └── server 127.0.0.1:3000;
│ │ }
│ │
│ ├── server { # Virtual host
│ │ ├── listen 443 ssl;
│ │ ├── server_name example.com;
│ │ │
│ │ ├── location / { # Request routing
│ │ │ └── proxy_pass http://backend;
│ │ │ }
│ │ │
│ │ └── location /static/ {
│ │ └── root /var/www;
│ │ }
│ │ }
│ │
│ └── include /etc/nginx/conf.d/*.conf;
│ }
│
└── stream { # TCP/UDP proxying (optional)
└── server { ... }
}
ディレクティブの継承ルール
| ルール | 動作 | 例 |
|---|---|---|
| 下位への継承 | 子ブロックは親のディレクティブを継承します | http 内の gzip on; はすべての server ブロックに適用されます |
| 上書き | 子のディレクティブが親を上書きします | location 内の gzip off; は http レベルの gzip on; を上書きします |
| 配列ディレクティブ | 継承されません。再宣言が必要です | location 内の proxy_set_header は server のすべてのヘッダーを置き換えます |
| 上位への反映なし | 内側のブロックが外側に影響することはありません | location レベルの設定は server に影響しません |
重要: 配列型ディレクティブ(proxy_set_header、add_header、proxy_hide_header)は、子ブロックで再定義するとマージされず、完全に置き換えられます。location 内で proxy_set_header を1つ設定した場合、すべてを再宣言する必要があります。
リバースプロキシの判断ツリー
Need to proxy requests?
│
├─ Single backend server?
│ └─ Use simple proxy_pass
│ proxy_pass http://127.0.0.1:3000;
│
├─ Multiple backend servers?
│ │
│ ├─ Need session persistence?
│ │ ├─ By client IP → ip_hash
│ │ └─ By cookie → sticky cookie (Nginx Plus)
│ │
│ ├─ Backends have unequal capacity?
│ │ └─ Use weight parameter
│ │ server backend1:3000 weight=3;
│ │ server backend2:3000 weight=1;
│ │
│ ├─ Want fewest active connections?
│ │ └─ least_conn
│ │
│ ├─ Want even random distribution?
│ │ └─ random two least_conn
│ │
│ └─ Default (no special needs)?
│ └─ round-robin (default, no directive needed)
│
├─ WebSocket connections?
│ └─ Add Upgrade + Connection headers
│ proxy_set_header Upgrade $http_upgrade;
│ proxy_set_header Connection "upgrade";
│
├─ gRPC backend?
│ └─ Use grpc_pass grpc://backend;
│
└─ Streaming / Server-Sent Events?
└─ Disable buffering
proxy_buffering off;
SSL/TLS クイックスタート
Certbot を使った Let's Encrypt
# Install certbot
sudo apt install certbot python3-certbot-nginx # Debian/Ubuntu
sudo dnf install certbot python3-certbot-nginx # RHEL/Fedora
# Obtain certificate (nginx plugin - easiest)
sudo certbot --nginx -d example.com -d www.example.com
# Obtain certificate (webroot - no nginx restart)
sudo certbot certonly --webroot -w /var/www/html -d example.com
# Test auto-renewal
sudo certbot renew --dry-run
本番環境向けの最小限の SSL 設定
server {
listen 443 ssl http2;
server_name example.com;
# Certificates
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
# Modern TLS (1.2 + 1.3)
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
# HSTS (2 years)
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
# OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
ssl_trusted_certificate /etc/letsencrypt/live/example.com/chain.pem;
resolver 1.1.1.1 8.8.8.8 valid=300s;
resolver_timeout 5s;
# Session caching
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off;
root /var/www/example.com;
index index.html;
}
# HTTP → HTTPS redirect
server {
listen 80;
server_name example.com www.example.com;
return 301 https://example.com$request_uri;
}
Location のマッチング順序
Nginx は、設定ファイルに記述された順序ではなく、特定の優先順位に従って location ブロックを評価します。
| 優先順位 | 修飾子 | 種類 | 例 | 動作 |
|---|---|---|---|---|
| 1 | = | 完全一致 | location = /favicon.ico | 一致すると直ちに検索を終了します |
| 2 | ^~ | プレフィックス(正規表現なし) | location ^~ /static/ | このプレフィックスに一致すると検索を終了します(正規表現をスキップ) |
| 3 | ~ | 正規表現(大文字と小文字を区別) | location ~ \.php$ | 最初に一致した正規表現が適用されます |
| 3 | ~* | 正規表現(大文字と小文字を区別しない) | location ~* \.(jpg|png)$ | 最初に一致した正規表現が適用されます |
| 4 | (なし) | プレフィックス | location /api/ | 最長のプレフィックスが適用されます(ただし、正規表現のチェック後) |
評価アルゴリズム
- すべてのプレフィックスの
locationを確認し、最長の一致を記憶します - 最長の一致に
^~修飾子がある場合は、それを使用して終了します - 設定ファイルの順序で正規表現の
locationを確認し、最初の一致を適用します - 正規表現に一致しない場合は、手順1の最長のプレフィックスを使用します
= /pathは最初に確認され、一致すると直ちに適用されます
例
location = / { } # Only exact "/"
location / { } # Catch-all prefix
location /api/ { } # Prefix: /api/*
location ^~ /static/ { } # Prefix, skip regex: /static/*
location ~ \.php$ { } # Regex: any .php file
location ~* \.(gif|jpg)$ { } # Case-insensitive regex: images
| リクエスト URI | 一致した Location | 理由 |
|---|---|---|
/ | = / | 完全一致(優先順位1) |
/index.html | / | 最長のプレフィックス。正規表現には一致しません |
/api/users | /api/ | 最長のプレフィックス。正規表現には一致しません |
/static/logo.png | ^~ /static/ | ^~ によって正規表現のチェックをスキップします |
/app/index.php | ~ \.php$ | 正規表現がプレフィックスより優先されます |
/photos/cat.jpg | ~* \.(gif|jpg)$ | 正規表現がプレフィックスより優先されます |
よく使われる設定
SPA ルーティング(React、Vue、Angular)
server {
listen 80;
server_name app.example.com;
root /var/www/app/dist;
index index.html;
# Serve static files directly, fall back to index.html for SPA routes
location / {
try_files $uri $uri/ /index.html;
}
# Cache static assets aggressively
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2?)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}
WebSocket プロキシ
location /ws/ {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_read_timeout 86400s; # Keep WebSocket alive for 24h
proxy_send_timeout 86400s;
}
レート制限
# Define zone: 10MB shared memory, 10 requests/second per IP
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
server {
location /api/ {
# Allow burst of 20, process excess without delay up to burst
limit_req zone=api burst=20 nodelay;
limit_req_status 429;
proxy_pass http://backend;
}
}
Gzip 圧縮
http {
gzip on;
gzip_comp_level 5; # Balance CPU vs compression (1-9)
gzip_min_length 256; # Don't compress tiny responses
gzip_vary on; # Vary: Accept-Encoding header
gzip_proxied any; # Compress proxied responses too
gzip_types
text/plain
text/css
text/javascript
application/javascript
application/json
application/xml
application/xml+rss
image/svg+xml;
}
静的ファイルの配信
location /static/ {
alias /var/www/static/; # Note: alias, not root (includes /static/ path)
expires 30d;
add_header Cache-Control "public, no-transform";
# Disable access log for static files
access_log off;
# Enable open file cache
open_file_cache max=1000 inactive=20s;
open_file_cache_valid 30s;
open_file_cache_min_uses 2;
}
CORS ヘッダー
location /api/ {
# CORS headers
add_header Access-Control-Allow-Origin "https://app.example.com" always;
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always;
add_header Access-Control-Allow-Headers "Authorization, Content-Type" always;
add_header Access-Control-Max-Age 86400 always;
# Handle preflight requests
if ($request_method = OPTIONS) {
return 204;
}
proxy_pass http://backend;
}
Docker のパターン
Docker Compose でのリバースプロキシとしての Nginx
# docker-compose.yml
services:
nginx:
image: nginx:alpine
ports:
- "80:80"
- "443:443"
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- ./certs:/etc/nginx/certs:ro
depends_on:
- app
networks:
- webnet
app:
build: .
expose:
- "3000" # Internal only, not published to host
networks:
- webnet
networks:
webnet:
# nginx.conf for docker-compose (use service name as hostname)
upstream app_backend {
server app:3000; # Docker DNS resolves service name
}
server {
listen 80;
location / {
proxy_pass http://app_backend;
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;
}
}
静的アセットを使ったマルチステージビルド
# Stage 1: Build frontend
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Stage 2: Serve with nginx
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
# nginx.conf for containerized SPA
server {
listen 80;
root /usr/share/nginx/html;
index index.html;
# SPA routing
location / {
try_files $uri $uri/ /index.html;
}
# Health check endpoint
location /health {
access_log off;
return 200 "OK\n";
add_header Content-Type text/plain;
}
# Cache busted assets
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2?)$ {
expires 1y;
add_header Cache-Control "public, immutable";
}
}
よくある落とし穴
| 落とし穴 | 理由 | 対処法 |
|---|---|---|
proxy_pass の末尾のスラッシュ | proxy_pass http://backend は /api/users をそのまま保持しますが、proxy_pass http://backend/ は一致した location のプレフィックスを取り除きます | 意図に応じて使い分けます。プレフィックスを取り除く場合は / を付け、保持する場合は付けません |
| プロキシヘッダーの設定漏れ | バックエンドにはクライアントではなく nginx の IP アドレスが認識されます。認証、ログ記録、地域検出に支障が出ます | 必ず X-Real-IP、X-Forwarded-For、X-Forwarded-Proto、Host を設定します |
| バッファサイズエラー(502) | 大きなヘッダー(Cookie、JWT)がデフォルトのバッファサイズを超えます | proxy_buffer_size 8k; と proxy_buffers 4 16k; を増やします |
worker_connections の値が小さすぎる | デフォルトは 512 または 1024 です。クライアントごとに 2 つの接続(クライアント側と上流側)を使用します | worker_connections 4096; を設定し、worker_rlimit_nofile の値も引き上げます |
proxy_pass と try_files の併用 | 同じ location 内の try_files と proxy_pass は、期待どおりに動作しません | try_files $uri @backend; を使用し、プロキシ用に名前付きの location を設定します |
| 「if は危険」 | location 内の if は暗黙的なネストした location を作成し、ディレクティブの動作を損ないます | 変数に基づくロジックには map を使用し、if は return/rewrite のみに使用します |
| 動的な上流サーバー向けのリゾルバー | proxy_pass 内の変数(例:$upstream)は、起動時の DNS 解決を回避します | resolver 127.0.0.11 valid=30s;(Docker の場合)または resolver 1.1.1.1; を追加します |
index ディレクティブの設定漏れ | インデックスファイルではなくディレクトリにアクセスすると、403 Forbidden が返されます | server または location ブロックに index index.html; を追加します |
| ソケットへのアクセス許可が拒否される | Nginx のワーカープロセスが上流の Unix ソケットを読み取れません | nginx ユーザーがソケットのグループに属していることを確認し、ソケットに chmod 660 を設定します |
gzip による Content-Encoding の重複 | 上流側ですでに圧縮されているのに nginx が gzip 圧縮を行い、二重に圧縮されます | gzip_proxied を慎重に使用するか、proxy_set_header Accept-Encoding ""; を設定します |
add_header が継承されない | location 内で add_header を 1 つでも追加すると、親の add_header ディレクティブがすべて破棄されます | 子ブロック内ですべてのヘッダーを再宣言するか、共通ヘッダーに include を使用します |
alias と root の混同 | root は location のパスを追加し、alias は置き換えます。/img/ と root /data の組み合わせは /data/img/ に、alias /data/ は /data/ になります | location のパスをファイルシステムのパスに含めない場合は alias を使用します |
参照ファイル
| ファイル | 内容 | 行数 |
|---|---|---|
| reverse-proxy.md | 上流ブロック、負荷分散、プロキシキャッシュ、WebSocket/gRPC、タイムアウト、実環境の設定例 | 約650 |
| ssl-security.md | TLS 設定、Let's Encrypt、HSTS、OCSP、セキュリティヘッダー、レート制限、mTLS | 約550 |
| performance.md | ワーカーのチューニング、圧縮、キャッシュ、HTTP/2+3、静的ファイル、監視 | 約550 |
関連項目
- docker-ops - コンテナーオーケストレーション、docker-compose のパターン
- security-ops - アプリケーションセキュリティ、認証のパターン
- ci-cd-ops - デプロイパイプライン、ダウンタイムのないデプロイ
- Nginx 公式ドキュメント
- Mozilla SSL 設定ジェネレーター
- Nginx 設定ジェネレーター
コメント
Markdown 対応