diff --git a/modules/caddyhttp/reverseproxy/caddyfile.go b/modules/caddyhttp/reverseproxy/caddyfile.go index 264671703ff..aea27228df9 100644 --- a/modules/caddyhttp/reverseproxy/caddyfile.go +++ b/modules/caddyhttp/reverseproxy/caddyfile.go @@ -602,9 +602,15 @@ func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error { if strings.EqualFold(args[0], "host") && (args[1] == "{hostport}" || args[1] == "{http.request.hostport}") { log.Printf("[WARNING] Unnecessary header_up ('Host' field): the reverse proxy's default behavior is to pass headers to the upstream") } + if strings.EqualFold(args[0], "x-forwarded-for") && (args[1] == "{remote}" || args[1] == "{http.request.remote}" || args[1] == "{remote_host}" || args[1] == "{http.request.remote.host}") { + log.Printf("[WARNING] Unnecessary header_up ('X-Forwarded-For' field): the reverse proxy's default behavior is to pass headers to the upstream") + } if strings.EqualFold(args[0], "x-forwarded-proto") && (args[1] == "{scheme}" || args[1] == "{http.request.scheme}") { log.Printf("[WARNING] Unnecessary header_up ('X-Forwarded-Proto' field): the reverse proxy's default behavior is to pass headers to the upstream") } + if strings.EqualFold(args[0], "x-forwarded-host") && (args[1] == "{host}" || args[1] == "{http.request.host}" || args[1] == "{hostport}" || args[1] == "{http.request.hostport}") { + log.Printf("[WARNING] Unnecessary header_up ('X-Forwarded-Host' field): the reverse proxy's default behavior is to pass headers to the upstream") + } err = headers.CaddyfileHeaderOp(h.Headers.Request, args[0], args[1], "") case 3: err = headers.CaddyfileHeaderOp(h.Headers.Request, args[0], args[1], args[2]) diff --git a/modules/caddyhttp/reverseproxy/reverseproxy.go b/modules/caddyhttp/reverseproxy/reverseproxy.go index c87b77bfa95..b9a584347db 100644 --- a/modules/caddyhttp/reverseproxy/reverseproxy.go +++ b/modules/caddyhttp/reverseproxy/reverseproxy.go @@ -102,7 +102,8 @@ type Handler struct { // By default, all headers are passed-thru without changes, // with the exceptions of special hop-by-hop headers. // - // X-Forwarded-For and X-Forwarded-Proto are also set implicitly. + // X-Forwarded-For, X-Forwarded-Proto and X-Forwarded-Host + // are also set implicitly. Headers *headers.Handler `json:"headers,omitempty"` // If true, the entire request body will be read and buffered @@ -595,6 +596,7 @@ func (h Handler) addForwardedHeaders(req *http.Request) error { // potentially trusting a header that came from the client req.Header.Del("X-Forwarded-For") req.Header.Del("X-Forwarded-Proto") + req.Header.Del("X-Forwarded-Host") return nil } @@ -624,12 +626,13 @@ func (h Handler) addForwardedHeaders(req *http.Request) error { req.Header.Set("X-Forwarded-For", clientIP) } - // Set X-Forwarded-Proto; many backend apps expect this too + // Set X-Forwarded-Proto; many backend apps expect this, + // so that they can properly craft URLs with the right + // scheme to match the original request proto := "https" if req.TLS == nil { proto = "http" } - prior, ok = req.Header["X-Forwarded-Proto"] omit = ok && prior == nil if trusted && len(prior) > 0 { @@ -639,6 +642,23 @@ func (h Handler) addForwardedHeaders(req *http.Request) error { req.Header.Set("X-Forwarded-Proto", proto) } + // Set X-Forwarded-Host; often this is redundant because + // we pass through the request Host as-is, but in situations + // where we proxy over HTTPS, the user may need to override + // Host themselves, so it's helpful to send the original too. + host, _, err := net.SplitHostPort(req.Host) + if err != nil { + host = req.Host // OK; there probably was no port + } + prior, ok = req.Header["X-Forwarded-Host"] + omit = ok && prior == nil + if trusted && len(prior) > 0 { + host = prior[0] + } + if !omit { + req.Header.Set("X-Forwarded-Host", host) + } + return nil }