Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Generates and hardens production nginx configurations for reverse proxying with TLS termination, HTTP/2, response caching, rate limiting, gzip/brotli compression, and security headers. Use this skill when the user asks to "set up nginx as a reverse proxy", "add TLS/SSL to nginx", "configure nginx caching or rate limiting", "harden an nginx config", "proxy an app behind nginx", "fix nginx 502/504 errors", or write/review nginx.conf and server blocks.
| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-02 | ✗→✓ | ▲ Improved | 38% | 0% |
| case-01 | ✓→✓ | = Same ✓ | 43% | 0% |
| case-03 | ✓→✓ | = Same ✓ | 92% | 0% |
| case-04 | ✓→✓ | = Same ✓ | 171% | 0% |
| case-05 | ✓→✓ | = Same ✓ | 79% | 0% |
Authoritative guidance for writing secure, performant nginx reverse-proxy configurations. Produces server blocks that terminate TLS, speak HTTP/2, proxy to upstream applications, cache responses, throttle abusive clients, and compress payloads — without the footguns that cause 502s, header leaks, or weak ciphers.
Keywords: nginx, reverse proxy, TLS, SSL, HTTPS, HTTP/2, HTTP/3, certbot, Let's Encrypt, upstream, proxy_pass, gzip, brotli, rate limiting, limit_req, proxy_cache, security headers, HSTS, CSP, OCSP stapling, websocket, load balancing, 502 bad gateway, 504 gateway timeout.
This skill targets nginx 1.18+ (most directives work on 1.10+). HTTP/3 notes assume 1.25+.
nginx.conf for security and performance.Follow these steps in order. Each builds on the previous.
templates/reverse-proxy.conf.tmpl as the starting point. Place site configs in /etc/nginx/sites-available/<name>.conf and symlink into sites-enabled/ (Debian/Ubuntu), or /etc/nginx/conf.d/<name>.conf (RHEL).references/tls-hardening.md. Always serve a port 80 → 443 redirect. Add HSTS only once you are certain HTTPS is permanent.Host, X-Forwarded-For, X-Forwarded-Proto, and X-Real-IP. Set sane timeouts. For websockets, add the Upgrade/Connection map. See the "Proxy headers" checklist below.proxy_cache where appropriate.limit_req_zone / limit_conn_zone in http {} and apply them in location/server. Add the security-header bundle.scripts/nginx_check.sh <conf> to lint for the most common mistakes, then nginx -t and reload. Verify externally per references/troubleshooting.md.Put shared proxy settings in a snippet (e.g. /etc/nginx/snippets/proxy.conf) and include it:
nginxproxy_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 X-Forwarded-Host $host; proxy_set_header Connection ""; # enables upstream keepalive proxy_read_timeout 60s; proxy_connect_timeout 5s; proxy_send_timeout 60s; proxy_buffering on;
Rules:
$host (not $http_host) so a missing Host header degrades gracefully to server_name.$proxy_add_x_forwarded_for appends; never hard-set X-Forwarded-For $remote_addr behind another proxy.Connection "" only when paired with an upstream { keepalive N; } block and proxy_http_version 1.1.set_real_ip_from + real_ip_header.| Condition | Cache? | |-----------|--------| | Response is GET/HEAD, no Set-Cookie, no Authorization | Yes | | Upstream sends Cache-Control: private/no-store | No (respect it) | | Per-user/personalized HTML | No, or vary by a cache key that includes the session | | Static assets (js/css/img with hashed names) | Yes, long expires, immutable | | API JSON that changes every request | No |
When caching, always add a X-Cache-Status $upstream_cache_status header so you can confirm HIT/MISS/BYPASS. See references/caching-and-compression.md.
Define zones in http {}, apply in server/location:
nginx# http {} context limit_req_zone $binary_remote_addr zone=req_per_ip:10m rate=10r/s; limit_conn_zone $binary_remote_addr zone=conn_per_ip:10m; # location {} context limit_req zone=req_per_ip burst=20 nodelay; limit_conn conn_per_ip 20; limit_req_status 429;
burst absorbs short spikes; nodelay serves the burst immediately instead of queuing.$binary_remote_addr is compact (16 bytes/IPv6); a 10m zone holds ~160k unique IPs.rate=5r/m) to slow credential stuffing.real_ip), not the CDN edge IP.Apply at server scope (see references/tls-hardening.md for full rationale and CSP guidance):
nginxadd_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always; add_header X-Content-Type-Options "nosniff" always; add_header X-Frame-Options "SAMEORIGIN" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; add_header Content-Security-Policy "default-src 'self'" always; server_tokens off;
The always flag is mandatory — without it headers are dropped on 4xx/5xx responses.
server_tokens off; to hide the nginx version.proxy.conf, ssl.conf, and security-headers.conf so every site stays consistent.ssl_protocols TLSv1.2 TLSv1.3; and a modern cipher list; disable TLS 1.0/1.1.nginx -t && nginx -s reload. Never reload a config that fails the test.server_name _; block that returns 444 to drop requests for unknown hosts.Connection "" + keepalive mismatch → upstream connections not reused, or 502 storms. Pair proxy_http_version 1.1, Connection "", and upstream { keepalive }.add_header without always → security headers silently disappear on errors.add_header inheritance trap → defining ANY add_header in a location discards ALL add_header from the parent server. Re-include the full bundle or use a snippet in every location that needs it.proxy_pass trailing-slash semantics → proxy_pass http://up/; (with slash) strips the matched location prefix; without the slash it passes the full URI. Mixing these breaks routing.preload before submission → can lock users out of HTTP for max-age. Start with a small max-age, drop preload until you have submitted to hstspreload.org.502 Bad Gateway → upstream down, wrong port, or SELinux blocking the connection (setsebool -P httpd_can_network_connect 1). See references/troubleshooting.md.504 Gateway Timeout → slow upstream; raise proxy_read_timeout and fix the app, don't just mask it.413 Request Entity Too Large → raise client_max_body_size (default 1m) for uploads.proxy_cache_bypass/proxy_no_cache for cookies/Authorization.worker_connections and worker_rlimit_nofile raised together.templates/reverse-proxy.conf.tmpl — complete, fill-in server config with TLS, caching, rate limiting, gzip, websockets, and security headers.references/tls-hardening.md — cipher suites, protocols, OCSP stapling, HSTS, CSP, Let's Encrypt/certbot, real-IP behind CDN.references/caching-and-compression.md — proxy_cache zones, cache keys, bypass rules, gzip/brotli tuning, static asset strategy.references/troubleshooting.md — 502/504/413/444 diagnosis, header debugging, validation commands.scripts/nginx_check.sh — stdlib (bash/grep) linter for the most common nginx config mistakes.examples/node-app-proxy.md — worked example: proxying a Node app on :3000 to https://app.example.com.Other measured skills in the registry, with their headline benchmark lift.