NGINX Reverse-Proxy vhost Design Prompt
Generate a clean, production-ready reverse-proxy server block for your backend app — correct headers, timeouts, keepalive, and WebSocket support — instead of copy-pasting a Stack Overflow snippet that leaks the client IP.
- Target user
- Engineers standing up a new NGINX vhost in front of an app server or container
- Difficulty
- Intermediate
- Tools
- Claude, ChatGPT, Cursor
The prompt
You are a senior infrastructure engineer who writes NGINX reverse-proxy configs that survive production. You set forwarding headers correctly, never break WebSockets, and you comment every non-obvious directive.
I will provide:
- The public hostname(s) this vhost serves: [DESCRIBE HOSTNAMES]
- The backend address (host:port, unix socket, or container): [DESCRIBE BACKEND]
- Whether the app uses WebSockets / SSE / long polling: [YES/NO + DETAILS]
- TLS setup (terminating here, behind another LB, or plain HTTP for now): [DESCRIBE TLS]
- Any static assets NGINX should serve directly: [DESCRIBE STATIC PATHS]
Build the config:
1. **Server block skeleton** — `listen`, `server_name`, and a separate `upstream {}` block (named, not an inline `proxy_pass` to a literal host) so the backend is reusable and tunable.
2. **Upstream tuning** — add `keepalive` to the upstream and set `proxy_http_version 1.1` plus `proxy_set_header Connection ""` so connections are reused, not torn down per request.
3. **Forwarding headers** — `Host`, `X-Real-IP`, `X-Forwarded-For`, `X-Forwarded-Proto`. Explain why each matters and what breaks (redirect loops, wrong client IP in logs) if it is missing.
4. **WebSocket / upgrade** — if needed, the `Upgrade`/`Connection` map and `proxy_set_header` lines, in their own `location` if the app isolates WS to a path.
5. **Static + proxy split** — `try_files` or a `location` for static assets so NGINX serves them without round-tripping the app; proxy everything else.
6. **Timeouts and buffers** — sensible `proxy_connect_timeout`, `proxy_read_timeout`, and a note on `proxy_buffering` for streaming responses.
Output: (a) the complete, commented `upstream {}` + `server {}` config as one block, (b) a short table of every header you set and why, (c) the `nginx -t` command and a `curl -H "Host: ..."` smoke test. Treat the config as a reviewable artifact: validate with `nginx -t` and reload — never paste directives straight into a live prod file.
Why this prompt works
Most broken reverse proxies fail in the same three places: missing forwarding headers (so the backend sees NGINX’s IP, not the client’s), no keepalive on the upstream (so every request opens a fresh TCP connection), and a WebSocket upgrade that silently downgrades to HTTP. The prompt makes each of these an explicit, numbered requirement so none gets skipped.
Forcing a named upstream {} block instead of an inline proxy_pass http://host:port produces a config you can later add load balancing, health checks, and keepalive to without restructuring. It is the difference between a throwaway snippet and a maintainable building block.
The header-explanation table and the curl -H "Host: ..." smoke test keep you in control: you understand every directive before it ships, and you prove the vhost routes correctly behind nginx -t rather than discovering a redirect loop in production.