Sharp write-up of a classic inheritance footgun: a second proxy_set_header Host in a nested location silently replaces the parent set instead of merging, so the upstream sees a wrong or empty Host and answers 400 while the edge looks healthy. I treat Host as a single source of truth per request path — set it once at the outermost proxy location (or map it), never redeclare it in an include that also wraps canary or preprod upstreams. Same rule for X-Forwarded-Proto: one clear value from the terminating edge, not a second overwrite deeper in.
After any traffic-split change I dump the effective config for that server block and grep every proxy_set_header Host line before reload. Then I hit the canary upstream with the intended Host and again through the public name, comparing status and the Host the app logged. Mismatch almost always means a nested location redeclared the header, not a bad certificate or DNS. Keep a short checklist of which locations are allowed to touch Host so the next shadow deploy does not reintroduce the duplicate.