fix: preserve original Host header in Caddy reverse proxy #452

Merged
ldraney merged 1 commit from fix/caddy-host-header into main 2026-06-17 06:56:58 +00:00
Owner

Summary

  • Adds header_up Host {http.request.host} to the Caddy reverse proxy template so backends receive the original client hostname instead of the Tailscale proxy target
  • Without this, apps like landscaping-assistant.app redirect users to the internal Tailscale URL because the backend sees the wrong Host header

Changes

  • salt/states/caddy/Caddyfile.j2: Added header_up Host {http.request.host} inside the reverse_proxy block

Test Plan

  • After merge, run salt state.apply caddy on edge proxy (178.156.129.142)
  • Verify landscaping-assistant.app no longer redirects to Tailscale URL
  • Verify palinks.app continues to work (already working before this change)

Review Checklist

  • Passed automated review-fix loop
  • No secrets committed
  • No unnecessary file changes
  • Commit messages are descriptive
  • Feature flag needed? No — bug fix in Salt template
  • ldraney/pal-e-platform #451 — Caddy reverse proxy sends upstream hostname instead of original Host header
  • project-dns-custom-domain-waves — Wave 2 Caddy configuration

Closes #451

## Summary - Adds `header_up Host {http.request.host}` to the Caddy reverse proxy template so backends receive the original client hostname instead of the Tailscale proxy target - Without this, apps like `landscaping-assistant.app` redirect users to the internal Tailscale URL because the backend sees the wrong Host header ## Changes - `salt/states/caddy/Caddyfile.j2`: Added `header_up Host {http.request.host}` inside the `reverse_proxy` block ## Test Plan - [ ] After merge, run `salt state.apply caddy` on edge proxy (178.156.129.142) - [ ] Verify `landscaping-assistant.app` no longer redirects to Tailscale URL - [ ] Verify `palinks.app` continues to work (already working before this change) ## Review Checklist - [ ] Passed automated review-fix loop - [ ] No secrets committed - [ ] No unnecessary file changes - [ ] Commit messages are descriptive - [ ] Feature flag needed? No — bug fix in Salt template ## Related Notes - `ldraney/pal-e-platform #451` — Caddy reverse proxy sends upstream hostname instead of original Host header - `project-dns-custom-domain-waves` — Wave 2 Caddy configuration Closes #451
fix: preserve original Host header in Caddy reverse proxy
All checks were successful
ci/woodpecker/push/terraform Pipeline was successful
ci/woodpecker/pr/terraform Pipeline was successful
ci/woodpecker/pull_request_closed/terraform Pipeline was successful
5629df7226
Adds header_up Host {http.request.host} so the backend sees the
client's original hostname instead of the Tailscale proxy target.
Without this, apps redirect to the internal Tailscale URL.

Closes #451

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Author
Owner

PR #452 Review

DOMAIN REVIEW

Tech stack: Salt/Jinja2 template for Caddy reverse proxy configuration (infrastructure/IaC domain).

Change: Single line addition -- header_up Host {http.request.host} -- inside the reverse_proxy block in salt/states/caddy/Caddyfile.j2.

Correctness analysis:

  1. Caddy syntax: header_up is a valid sub-directive of reverse_proxy in Caddy v2. Placement alongside transport http inside the reverse_proxy block is correct.

  2. Placeholder: {http.request.host} is the correct Caddy placeholder for the original client Host header. This ensures the backend receives landscaping-assistant.app instead of landscaping-assistant.tail5b443a.ts.net.

  3. Root cause match: The bug described in #451 -- backends seeing the Tailscale hostname and redirecting users to internal URLs -- is precisely caused by Caddy forwarding the upstream target hostname. This header_up Host directive is the canonical fix.

  4. Template scope: Because this is inside the Jinja2 for loop over pillar['caddy']['sites'], the fix applies to ALL sites (currently palinks.app and landscaping-assistant.app). This is correct -- all reverse-proxied domains should preserve the original Host header.

  5. TLS interaction: The transport http { tls_server_name } block handles TLS SNI for the upstream connection separately from the Host header. These are independent -- tls_server_name tells Caddy what SNI to present to the upstream TLS handshake, while header_up Host controls the HTTP-layer Host header. No conflict.

  6. Idempotency: Salt will render and deploy the updated Caddyfile on next state.apply caddy. Caddy reload is graceful. No state drift risk.

BLOCKERS

None.

  • No new functionality requiring tests (infrastructure template fix, tested via manual validation per test plan).
  • No user input handling (Jinja2 values come from Salt pillar, not user input).
  • No secrets or credentials in the diff.
  • No DRY violations.

NITS

None. The change is minimal, correctly scoped, and syntactically clean.

SOP COMPLIANCE

  • PR body has Summary, Changes, Test Plan, Related -- all present and well-structured
  • No secrets committed
  • No unnecessary file changes (1 file, 1 line -- surgically scoped)
  • Commit message is descriptive (fix: preserve original Host header in Caddy reverse proxy)
  • Closes #451 present -- issue will auto-close on merge
  • Review checklist included in PR body

PROCESS OBSERVATIONS

  • Change failure risk: Low. Single directive addition to a well-understood Caddy pattern. The header_up Host directive is documented Caddy behavior and widely used in reverse proxy configurations.
  • Deployment: Requires salt state.apply caddy on edge-proxy post-merge. Test plan correctly identifies manual validation steps for both affected domains.
  • Wave context: This is a Wave 2 (Caddy configuration) fix per project-dns-custom-domain-waves. Clean isolation from Wave 1 (DNS) and Wave 3 (app config).

VERDICT: APPROVED

## PR #452 Review ### DOMAIN REVIEW **Tech stack:** Salt/Jinja2 template for Caddy reverse proxy configuration (infrastructure/IaC domain). **Change:** Single line addition -- `header_up Host {http.request.host}` -- inside the `reverse_proxy` block in `salt/states/caddy/Caddyfile.j2`. **Correctness analysis:** 1. **Caddy syntax:** `header_up` is a valid sub-directive of `reverse_proxy` in Caddy v2. Placement alongside `transport http` inside the `reverse_proxy` block is correct. 2. **Placeholder:** `{http.request.host}` is the correct Caddy placeholder for the original client Host header. This ensures the backend receives `landscaping-assistant.app` instead of `landscaping-assistant.tail5b443a.ts.net`. 3. **Root cause match:** The bug described in #451 -- backends seeing the Tailscale hostname and redirecting users to internal URLs -- is precisely caused by Caddy forwarding the upstream target hostname. This `header_up Host` directive is the canonical fix. 4. **Template scope:** Because this is inside the Jinja2 `for` loop over `pillar['caddy']['sites']`, the fix applies to ALL sites (currently `palinks.app` and `landscaping-assistant.app`). This is correct -- all reverse-proxied domains should preserve the original Host header. 5. **TLS interaction:** The `transport http { tls_server_name }` block handles TLS SNI for the upstream connection separately from the Host header. These are independent -- `tls_server_name` tells Caddy what SNI to present to the upstream TLS handshake, while `header_up Host` controls the HTTP-layer Host header. No conflict. 6. **Idempotency:** Salt will render and deploy the updated Caddyfile on next `state.apply caddy`. Caddy reload is graceful. No state drift risk. ### BLOCKERS None. - No new functionality requiring tests (infrastructure template fix, tested via manual validation per test plan). - No user input handling (Jinja2 values come from Salt pillar, not user input). - No secrets or credentials in the diff. - No DRY violations. ### NITS None. The change is minimal, correctly scoped, and syntactically clean. ### SOP COMPLIANCE - [x] PR body has Summary, Changes, Test Plan, Related -- all present and well-structured - [x] No secrets committed - [x] No unnecessary file changes (1 file, 1 line -- surgically scoped) - [x] Commit message is descriptive (`fix: preserve original Host header in Caddy reverse proxy`) - [x] `Closes #451` present -- issue will auto-close on merge - [x] Review checklist included in PR body ### PROCESS OBSERVATIONS - **Change failure risk:** Low. Single directive addition to a well-understood Caddy pattern. The `header_up Host` directive is documented Caddy behavior and widely used in reverse proxy configurations. - **Deployment:** Requires `salt state.apply caddy` on edge-proxy post-merge. Test plan correctly identifies manual validation steps for both affected domains. - **Wave context:** This is a Wave 2 (Caddy configuration) fix per `project-dns-custom-domain-waves`. Clean isolation from Wave 1 (DNS) and Wave 3 (app config). ### VERDICT: APPROVED
ldraney deleted branch fix/caddy-host-header 2026-06-17 06:56:58 +00:00
Sign in to join this conversation.
No description provided.