mcpField
R
reverse-proxy

reverse-proxy

github.com/bagelholeクラウド・DevOps1.2k481 回インストール23 週間前に更新
リポジトリ

reverse-proxy について

reverse-proxy は、運用担当者向けのリバースプロキシ設定スキルです。nginx と Traefik の例を通じて、バックエンドへのルーティング、TLS、レート制限、アクセス制御を説明します。

主な機能

  1. 01nginx と Traefik のプロキシ設定
  2. 02パスに基づく複数サービスへのルーティング
  3. 03TLS 終端とトラフィック転送
  4. 04レート制限、アクセス制御、セキュリティヘッダーの設定
  5. 05圧縮設定とプロキシ障害の調査

活用シーン

  1. 01公開ドメインのトラフィックをバックエンドへ転送
  2. 02単一ドメイン配下で複数サービスをルーティング
  3. 03管理ページのアクセス制限と TLS 設定

インストール

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

$npx skills add https://github.com/bagelhole/devops-security-agent-skills --skill reverse-proxy

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

詳細

リバースプロキシ

リバースプロキシを設定して、トラフィックをルーティングし、TLS を終端し、レート制限を適用して、クライアントとバックエンドサービスの間のゲートウェイとして機能させます。

使用するタイミング

  • パブリックドメインから 1 つ以上のバックエンドサービスにトラフィックをルーティングする場合。
  • エッジで TLS を終端し、プレーン HTTP をバックエンドに転送する場合。
  • レート制限、CORS、セキュリティヘッダー、アクセス制御を追加する場合。
  • パスベースのルーティングを使用して、複数のサービスを単一のドメインに集約する場合。
  • WebSocket のアップグレード、gRPC プロキシ、または HTTP/2 パススルーを処理する場合。

前提条件

  • 既知のホストとポートで稼働しているバックエンドサービス。
  • TLS 証明書(Let's Encrypt、ACM、または開発用の自己署名証明書)。
  • nginx 1.25+ または Traefik 3.x がインストールされていること。
  • ドメインをプロキシサーバーに向ける DNS レコード。

nginx リバースプロキシ

リダイレクト付きの基本的な HTTPS プロキシ
# /etc/nginx/sites-available/app.example.com
server {
    listen 80;
    server_name app.example.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;
    server_name app.example.com;

    # TLS configuration
    ssl_certificate     /etc/letsencrypt/live/app.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/app.example.com/privkey.pem;
    ssl_protocols       TLSv1.2 TLSv1.3;
    ssl_ciphers         ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384;
    ssl_prefer_server_ciphers on;
    ssl_session_cache   shared:SSL:10m;
    ssl_session_timeout 10m;

    # Security headers
    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
    add_header X-Frame-Options DENY always;
    add_header X-Content-Type-Options nosniff always;
    add_header Referrer-Policy strict-origin-when-cross-origin always;

    # Proxy to backend
    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        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;

        # Timeouts
        proxy_connect_timeout 5s;
        proxy_read_timeout 60s;
        proxy_send_timeout 60s;

        # Buffering
        proxy_buffering on;
        proxy_buffer_size 4k;
        proxy_buffers 8 4k;
    }
}
パスベースで複数のサービスにルーティング
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;

    # Frontend SPA
    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
    }

    # API backend
    location /api/ {
        proxy_pass http://127.0.0.1:8080/;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_read_timeout 120s;
    }

    # WebSocket endpoint
    location /ws/ {
        proxy_pass http://127.0.0.1:8080;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_read_timeout 86400s;  # 24h for long-lived connections
    }

    # Static assets with caching
    location /static/ {
        alias /var/www/static/;
        expires 30d;
        add_header Cache-Control "public, immutable";
    }
}
レート制限
# Define rate limit zones in http block
http {
    # 10 requests/second per IP
    limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;

    # 1 request/second for login
    limit_req_zone $binary_remote_addr zone=login_limit:10m rate=1r/s;

    # Connection limit per IP
    limit_conn_zone $binary_remote_addr zone=conn_limit:10m;
}

server {
    listen 443 ssl http2;
    server_name app.example.com;

    # Apply rate limit to API
    location /api/ {
        limit_req zone=api_limit burst=20 nodelay;
        limit_req_status 429;
        proxy_pass http://127.0.0.1:8080;
    }

    # Strict rate limit on auth endpoints
    location /api/auth/ {
        limit_req zone=login_limit burst=5;
        limit_req_status 429;
        proxy_pass http://127.0.0.1:8080;
    }

    # Connection limit
    location / {
        limit_conn conn_limit 100;
        proxy_pass http://127.0.0.1:3000;
    }
}
Gzip と Brotli による圧縮
http {
    gzip on;
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml image/svg+xml;
    gzip_min_length 256;
    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 5;

    # Brotli (requires ngx_brotli module)
    # brotli on;
    # brotli_types text/plain text/css application/json application/javascript text/xml application/xml image/svg+xml;
    # brotli_comp_level 6;
}
Certbot を使用した Let's Encrypt
# Install certbot with nginx plugin
sudo apt install certbot python3-certbot-nginx

# Obtain and install certificate
sudo certbot --nginx -d app.example.com -d www.example.com

# Auto-renewal is configured via systemd timer
sudo systemctl status certbot.timer

# Manual renewal test
sudo certbot renew --dry-run

Traefik リバースプロキシ

静的設定
# traefik.yml
entryPoints:
  web:
    address: ":80"
    http:
      redirections:
        entryPoint:
          to: websecure
          scheme: https
  websecure:
    address: ":443"

certificatesResolvers:
  letsencrypt:
    acme:
      email: [email protected]
      storage: /letsencrypt/acme.json
      httpChallenge:
        entryPoint: web

providers:
  docker:
    exposedByDefault: false
  file:
    directory: /etc/traefik/dynamic/

api:
  dashboard: true
  insecure: false

log:
  level: INFO

accessLog:
  filePath: /var/log/traefik/access.log
動的設定(ファイルプロバイダー)
# /etc/traefik/dynamic/services.yml
http:
  routers:
    app:
      rule: "Host(`app.example.com`)"
      entryPoints:
        - websecure
      service: app
      tls:
        certResolver: letsencrypt
      middlewares:
        - security-headers
        - rate-limit

    api:
      rule: "Host(`app.example.com`) && PathPrefix(`/api`)"
      entryPoints:
        - websecure
      service: api
      tls:
        certResolver: letsencrypt

  services:
    app:
      loadBalancer:
        servers:
          - url: "http://127.0.0.1:3000"
        healthCheck:
          path: /health
          interval: 10s
          timeout: 3s

    api:
      loadBalancer:
        servers:
          - url: "http://127.0.0.1:8080"
        healthCheck:
          path: /api/health
          interval: 10s
          timeout: 3s

  middlewares:
    security-headers:
      headers:
        stsSeconds: 63072000
        stsIncludeSubdomains: true
        frameDeny: true
        contentTypeNosniff: true
        browserXssFilter: true
        referrerPolicy: strict-origin-when-cross-origin

    rate-limit:
      rateLimit:
        average: 100
        burst: 50
        period: 1m
Docker ラベルを使用した Traefik
# docker-compose.yml
version: "3.8"

services:
  traefik:
    image: traefik:v3.0
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./traefik.yml:/etc/traefik/traefik.yml:ro
      - letsencrypt:/letsencrypt

  frontend:
    image: my-frontend:latest
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.frontend.rule=Host(`app.example.com`)"
      - "traefik.http.routers.frontend.tls.certresolver=letsencrypt"
      - "traefik.http.services.frontend.loadbalancer.server.port=3000"

  api:
    image: my-api:latest
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.api.rule=Host(`app.example.com`) && PathPrefix(`/api`)"
      - "traefik.http.routers.api.tls.certresolver=letsencrypt"
      - "traefik.http.services.api.loadbalancer.server.port=8080"
      - "traefik.http.routers.api.middlewares=api-ratelimit"
      - "traefik.http.middlewares.api-ratelimit.ratelimit.average=50"
      - "traefik.http.middlewares.api-ratelimit.ratelimit.burst=25"

volumes:
  letsencrypt:

nginx のテストと管理

# Test configuration syntax
sudo nginx -t

# Reload without downtime
sudo nginx -s reload

# View active connections
sudo nginx -s status

# Check which config file is active
nginx -V 2>&1 | grep -o '\-\-conf-path=[^ ]*'

# Monitor access logs
tail -f /var/log/nginx/access.log

# Monitor error logs
tail -f /var/log/nginx/error.log

IP 許可リストとジオブロッキング

# Allow only specific IPs (admin panel)
location /admin/ {
    allow 203.0.113.0/24;
    allow 198.51.100.5;
    deny all;
    proxy_pass http://127.0.0.1:3000;
}

# Block by country (requires GeoIP2 module)
# geoip2 /usr/share/GeoIP/GeoLite2-Country.mmdb {
#     auto_reload 60m;
#     $geoip2_data_country_iso_code country iso_code;
# }
# if ($geoip2_data_country_iso_code = "XX") {
#     return 403;
# }

トラブルシューティング

症状原因対処方法
502 Bad Gatewayバックエンドが稼働していないか、到達できないバックエンドがリッスンしていることを確認し、proxy_pass の URL を確認します
504 Gateway Timeoutバックエンドの応答が遅すぎるproxy_read_timeout を増やし、バックエンドのパフォーマンスを確認します
混在コンテンツの警告X-Forwarded-Proto が設定されていないproxy_set_header X-Forwarded-Proto $scheme を追加します
60 秒後に WebSocket が切断されるデフォルトのプロキシタイムアウトが期限切れになるWebSocket のロケーションに proxy_read_timeout 86400s を設定します
正規のユーザーがレート制限に達するゾーンのレート制限が厳しすぎるrate または burst の値を増やすか、エンドポイントごとに異なるゾーンを使用します
Let's Encrypt の更新に失敗するポート 80 がブロックされているか、サーバーブロックが正しくない.well-known/acme-challenge/ にアクセスできることを確認します
Traefik がすべてのルートで 404 を返すDocker ラベルが検出されていないDocker ソケットがマウントされていることを確認し、exposedByDefault の設定を確認します
TLS ハンドシェイクに失敗する証明書チェーンが不完全中間証明書を ssl_certificate に含めます

関連スキル

コメント