# Nginx Config — API Gateway (HTTP/2 + Gzip + Error Masking) Production config for `api.` on nginx 1.28.3. ## Server block (SSL + HTTP/2) ```nginx server { listen 443 ssl; listen [::]:443 ssl; http2 on; server_name api.[REDACTED-NAME].loc.cc; server_tokens off; ssl_certificate /etc/letsencrypt/live/[REDACTED-NAME].loc.cc/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/[REDACTED-NAME].loc.cc/privkey.pem; include /etc/letsencrypt/options-ssl-nginx.conf; ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; client_max_body_size 25m; gzip on; gzip_min_length 256; gzip_types application/json text/event-stream; gzip_comp_level 1; gzip_vary on; location / { proxy_pass http://[REDACTED-NAME]; 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 X-Request-ID $request_id; proxy_set_header Connection ""; proxy_connect_timeout 10s; proxy_read_timeout 600s; proxy_send_timeout 600s; proxy_request_buffering off; proxy_buffering off; proxy_cache off; proxy_socket_keepalive on; proxy_intercept_errors on; } # Error masking: ALL upstream/nginx errors → neutral JSON error_page 400 408 411 414 431 = @invalid_request; error_page 405 = @method_not_allowed; error_page 413 = @request_too_large; error_page 500 502 503 504 = @service_unavailable; location @invalid_request { internal; default_type application/json; return 400 '{"error":{"message":"Invalid request","type":"gateway_error","code":"invalid_request"}}'; } location @method_not_allowed { internal; default_type application/json; return 405 '{"error":{"message":"Method not allowed","type":"gateway_error","code":"method_not_allowed"}}'; } location @request_too_large { internal; default_type application/json; return 413 '{"error":{"message":"Request body is too large","type":"gateway_error","code":"request_too_large"}}'; } location @service_unavailable { internal; default_type application/json; return 502 '{"error":{"message":"Service temporarily unavailable","type":"gateway_error","code":"service_unavailable"}}'; } } ``` ## Upstream block ```nginx upstream [REDACTED-NAME] { zone [REDACTED-NAME] 64k; server 127.0.0.1:18181; keepalive 64; } ``` ## HTTP → HTTPS redirect ```nginx server { listen 80; listen [::]:80; server_name [REDACTED-NAME].loc.cc www.[REDACTED-NAME].loc.cc api.[REDACTED-NAME].loc.cc; return 301 https://$host$request_uri; } ``` ## Echo/legacy paths (block on old domain) ```nginx server { listen 443 ssl; server_name [REDACTED-NAME].loc.cc www.[REDACTED-NAME].loc.cc; # ... SSL ... location ^~ /echo/ { default_type application/json; return 404 '{"error":{"message":"Not found","type":"gateway_error","code":"not_found"}}'; } location / { proxy_pass http://127.0.0.1:18080; # ... proxy ... } } ``` ## Key decisions - `gzip_comp_level 1` — minimal CPU cost, sufficient compression for JSON - `proxy_buffering off` — required for SSE streaming - `proxy_socket_keepalive on` — reduce TIME_WAIT on localhost - `http2 on;` (not `listen ... http2`) — nginx 1.28 deprecation - `proxy_intercept_errors on` — enables error_page for upstream errors - `proxy_set_header Connection ""` — prevents hop-by-hop forwarding