mcpField
N
nginx-ops

nginx-ops

github.com/0xdarkmatterクラウド・DevOps7837 回インストール2 週間前に更新
リポジトリ

nginx-ops について

Nginx Operationsは、本番環境でNginxを設定・運用するためのリファレンススキルです。リバースプロキシ、TLS、ロードバランシング、性能調整に加え、実用的な構成例や設定上の注意点を扱います。

主な機能

  1. 01リバースプロキシの設定パターン
  2. 02SSL/TLSの導入と強化
  3. 03ロードバランシング戦略
  4. 04パフォーマンス調整ガイド
  5. 05DockerとSPAの設定例
  6. 06設定の落とし穴と継承ルール

活用シーン

  1. 01Nginxをリバースプロキシとして設定
  2. 02デプロイ環境のTLS設定
  3. 03シングルページアプリの配信
  4. 04プロキシやヘッダー動作の調査

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/0xdarkmatter/claude-mods --skill nginx-ops

インストール後、Claude Code で/nginx-opsを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

assets/
.gitkeep
references/
performance.md
reverse-proxy.md
ssl-security.md
scripts/
.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/最長のプレフィックスが適用されます(ただし、正規表現のチェック後)
評価アルゴリズム
  1. すべてのプレフィックスの location を確認し、最長の一致を記憶します
  2. 最長の一致に ^~ 修飾子がある場合は、それを使用して終了します
  3. 設定ファイルの順序で正規表現の location を確認し、最初の一致を適用します
  4. 正規表現に一致しない場合は、手順1の最長のプレフィックスを使用します
  5. = /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.mdTLS 設定、Let's Encrypt、HSTS、OCSP、セキュリティヘッダー、レート制限、mTLS約550
performance.mdワーカーのチューニング、圧縮、キャッシュ、HTTP/2+3、静的ファイル、監視約550

関連項目

コメント