nginx
Configure Nginx web serving, reverse proxies, TLS termination, rate limits, and upstream load balancing; diagnose request failures.
SKILL.mdskills/nginx/SKILL.md
GitHubNginx High-Performance Reverse Proxy AI Skill Guide (Claude)
Overview & Engine Architecture
Nginx terminates HTTP/TLS, serves static assets, and reverse-proxies to upstream apps using an event-driven worker model. Master parses config and binds ports; workers handle connections with keepalive pools to backends. In Docker/Kubernetes, upstream hostnames resolve at config parse time unless you use resolver + variable proxy_pass—the most common production footgun.
┌─────────────────────────────────────────────────────────────┐
│ Nginx request path │
│ │
│ Client → server { listen ssl http2 } │
│ ├── limit_req / WAF-adjacent headers │
│ ├── location → proxy_pass upstream (keepalive) │
│ └── access_log / error_log │
│ │
│ Upstream │
│ ├── Static upstream { server ip:port } │
│ └── Dynamic DNS: resolver + set $backend + proxy_pass │
└─────────────────────────────────────────────────────────────┘
When to use / when not to
Use when
- Authoring reverse proxy, TLS termination, WebSocket upgrades, rate limits, gzip/brotli.
- Debugging 502/504/413, cert chain issues, duplicate upstream names, large upload buffering.
- Container ingress pairing with
@docker/@kubernetesServices.
Do not use when
- Full API gateway auth/OAuth/rate-limit SaaS features → dedicated gateway or service mesh.
- Kubernetes Ingress controller choice/architecture → ingress controller docs +
@kubernetes; Nginx skill covers config semantics.
Operational Capabilities & Agent Directives
nginx -tbefore every reload; hot reload (nginx -s reload) for zero-downtime on config changes.- Forward headers:
Host,X-Real-IP,X-Forwarded-For,X-Forwarded-Proto; trust only known hop IPs for client IP decisions. - WebSockets:
proxy_http_version 1.1,Upgrade,Connection "upgrade". - Docker/K8s upstreams:
resolver 127.0.0.11 valid=10s ipv6=off;(Docker DNS) or cluster DNS IP; use variable form:set $upstream http://api:8080; proxy_pass $upstream; - TLS: full chain (
fullchain.pem), modern protocols (TLS 1.2+), HSTS only when HTTPS is stable on all hosts. - Streaming/long requests: raise
proxy_read_timeout; setproxy_buffering offfor SSE;proxy_max_temp_file_size 0for large downloads. - Rate limits:
limit_req_zone+ burst; return 429, don’t pass abuse to app tier.
Production Configuration: TLS + dynamic upstream (Docker)
limit_req_zone $binary_remote_addr zone=api_rl:10m rate=20r/s;
upstream backend_api {
least_conn;
server 127.0.0.1:8001 max_fails=3 fail_timeout=10s;
keepalive 32;
}
server {
listen 443 ssl http2;
server_name api.example.com;
ssl_certificate /etc/letsencrypt/live/api.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
add_header Strict-Transport-Security "max-age=63072000" always;
location / {
limit_req zone=api_rl burst=10 nodelay;
proxy_pass http://backend_api;
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;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 120s;
}
}
# Docker service name pattern (api container may boot after nginx)
server {
listen 80;
server_name internal-api.local;
resolver 127.0.0.11 valid=10s ipv6=off;
set $docker_upstream http://api:8080;
location / {
proxy_pass $docker_upstream;
proxy_http_version 1.1;
proxy_set_header Host $host;
}
}
Validation:
nginx -t
docker run --rm -v "$PWD/nginx.conf:/etc/nginx/nginx.conf:ro" nginx:alpine nginx -t
nginx -s reload
Technical Troubleshooting Matrix
| Signature | Root cause | Fix |
|---|---|---|
[emerg] host not found in upstream at container start |
Static resolve before peer exists | resolver + variable proxy_pass |
502 Bad Gateway |
Upstream down / wrong port / SELinux | Curl upstream; httpd_can_network_connect on RHEL |
504 Gateway Timeout |
proxy_read_timeout exceeded |
Increase timeout; disable buffering for streams |
413 Request Entity Too Large |
Default 1m body limit | client_max_body_size in server/http |
| SSL untrusted | Leaf without intermediate | Use fullchain.pem; test with openssl s_client |
| Duplicate upstream name | Two files define same upstream foo |
Unique names; grep conf.d |
| Download stops ~1GB | Temp file buffering limit | proxy_max_temp_file_size 0; streaming off |
Best Practices
- Separate
sites-available/sites-enabled(Debian) orconf.d/*.conf(RHEL/Alpine)—one concern per file. - Log format includes
$request_timeand$upstream_response_timefor latency splits. - CI: containerized
nginx -ton every PR touching config (@github-actions). - Pair with
@prometheusnginx-prometheus-exporter for connection/upstream metrics. - Never expose stub_status publicly without ACL.
Limitations
- Nginx does not replace WAF, OAuth, or mTLS service mesh policies at scale.
- Windows nginx paths differ (
C:\nginx\); reload semantics same, tooling differs. - OpenResty/Lua modules are out of scope unless explicitly requested.
- Stop and ask if cert issuance method (ACME DNS vs HTTP-01) and public DNS are unknown.
Related Skills
@docker— compose networking and health-gated startup@kubernetes— Ingress controllers using nginx data plane@lets-encrypt— certificate lifecycle@prometheus/@grafana— edge latency and error dashboards@github-actions— config lint in CI
Agent Operational Directive
MANDATORY: Run
nginx -tbefore reload. In container orchestration, use dynamic upstream resolution (resolver+ variableproxy_pass) so Nginx does not fail startup when backends are still booting. Redirect HTTP to HTTPS in production. Do not disable TLS verification upstream without documented trust boundaries.
Source anchors (research)
- Nginx proxy module
- Docker embedded DNS 127.0.0.11
- Community: duplicate upstream across conf.d; SELinux 502; proxy temp file 1GB limit