# Case Study: A Complete Production Front End — Nginx

Source: https://www.skillbyai.com/en/nginx/p-case

> Combine everything into one production-ready configuration.

## One configuration, many responsibilities

A product has a React single-page app, a REST API on three instances, a WebSocket service for live updates and an admin panel. The NGINX design: a **catch-all default server** returning 444; an **HTTP server** that answers ACME challenges and redirects everything else to `https://www.example.com`; an **HTTPS server** with TLS 1.2/1.3, HTTP/2 and HTTP/3, HSTS and a shared **security-headers** snippet; **`/assets/`** served from disk with gzip_static and year-long immutable caching; **`/`** with `try_files ... /index.html` and `no-cache`; **`/api/`** proxied to a `least_conn` upstream with keepalive, forwarded headers, a 1-second connect timeout, a retry on connection errors only, per-IP **rate limiting** with 429 responses and a **1-second micro-cache** for public product listings; **`/ws/`** with WebSocket upgrade headers and a one-hour read timeout; **`/admin/`** restricted to the office VPN range plus SSO through `auth_request`; **JSON access logs** with request IDs forwarded to the API; and **`stub_status`** on localhost for metrics. Changes go through Git, CI runs `nginx -t` in a container, and deployment performs a graceful reload.

## The API part of the configuration

Rate limiting, micro-caching, keepalive and forwarded headers together.

```nginx
upstream api_backend {
    least_conn;
    server 10.0.1.11:8080 max_fails=3 fail_timeout=15s;
    server 10.0.1.12:8080 max_fails=3 fail_timeout=15s;
    server 10.0.1.13:8080 max_fails=3 fail_timeout=15s;
    keepalive 64;
}

limit_req_zone $binary_remote_addr zone=api:20m rate=20r/s;
proxy_cache_path /var/cache/nginx/api keys_zone=api_cache:50m max_size=1g inactive=10m;

server {
    # ... TLS, HTTP/2, HTTP/3, security headers as shown earlier ...
    location /api/ {
        limit_req zone=api burst=40 nodelay;
        limit_req_status 429;

        proxy_pass http://api_backend;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Request-ID $request_id;
        proxy_connect_timeout 1s;
        proxy_read_timeout 15s;
        proxy_next_upstream error timeout;

        proxy_cache api_cache;
        proxy_cache_valid 200 1s;
        proxy_cache_use_stale error timeout updating http_502 http_503;
        proxy_cache_lock on;
        proxy_no_cache $http_authorization;      # never cache authenticated calls
        proxy_cache_bypass $http_authorization;
    }
}
```

## Keep configuration in Git and test it in CI

Run `docker run --rm -v $PWD/nginx:/etc/nginx:ro nginx:stable nginx -t` in CI so syntax errors never reach production, and review configuration changes like code.

**Quiz:** In the case study, why are requests with an Authorization header excluded from the micro-cache?

- [x] Authenticated responses may be user-specific, so caching them could leak data between users
- [ ] Authorization headers are too large to cache
- [ ] NGINX cannot read headers
- [ ] Caching would disable rate limiting

*Answer:* Authenticated responses may be user-specific, so caching them could leak data between users. User-specific responses must not be stored in a shared cache keyed only by URL.
