worker_processes 1; events { worker_connections 1024; } http { # Round-robin across the app nodes. Add a node here to scale out. # max_fails=1 marks a node down after a single failure so a dead node drains fast. upstream stirling_nodes { server stirling-1:8080 max_fails=1 fail_timeout=10s; server stirling-2:8080 max_fails=1 fail_timeout=10s; } # Large uploads (SYSTEM_MAXFILESIZE=100MB) plus headroom. client_max_body_size 200m; map $http_upgrade $connection_upgrade { default upgrade; '' close; } server { listen 8080; location / { proxy_pass http://stirling_nodes; # Graceful failover: retries the other node only on connection-level failures (unreachable/timeout), never on 5xx, so a POST a node already started is never re-sent. proxy_next_upstream error timeout; proxy_next_upstream_tries 2; proxy_connect_timeout 3s; 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; # WebSocket / SSE upgrade support (policy run streaming). proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection $connection_upgrade; # Stream server-sent events straight through, don't buffer. proxy_buffering off; proxy_read_timeout 3600s; # Surface which app node served the request, so the validate script can prove the LB is spreading load. add_header X-Served-By $upstream_addr always; } } }