@marcel91200 / caddy Mirror · read-only / commits / 2ae0f7af
Fork 0

Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS

Mirrored from https://github.com/Slazee/caddy.git

Synced Nov 11, 17:15 UTC

reverseproxy: Set `Host` to `{upstream_hostport}` automatically if TLS (#7454)

Francis Lavoie committed Feb 9, 2026 at 15:06 UTC 2ae0f7af69521581e4b7fc4fc1913204d0659ecb
3 files changed +91 -5
modules/caddyhttp/reverseproxy/httptransport.go
+23
@@ -40,6 +40,7 @@ import (
40 "github.com/caddyserver/caddy/v2"
41 "github.com/caddyserver/caddy/v2/caddyconfig"
42 "github.com/caddyserver/caddy/v2/modules/caddyhttp"
43 + "github.com/caddyserver/caddy/v2/modules/caddyhttp/headers"
44 "github.com/caddyserver/caddy/v2/modules/caddytls"
45 "github.com/caddyserver/caddy/v2/modules/internal/network"
46 )
@@ -514,6 +515,28 @@ func (h *HTTPTransport) NewTransport(caddyCtx caddy.Context) (*http.Transport, e
515 return rt, nil
516 }
517
518 +// RequestHeaderOps implements TransportHeaderOpsProvider. It returns header
519 +// operations for requests when the transport's configuration indicates they
520 +// should be applied. In particular, when TLS is enabled for this transport,
521 +// return an operation to set the Host header to the upstream host:port
522 +// placeholder so HTTPS upstreams get the proper Host by default.
523 +//
524 +// Note: this is a provision-time hook; the Handler will call this during
525 +// its Provision and cache the resulting HeaderOps. The HeaderOps are
526 +// applied per-request (so placeholders are expanded at request time).
527 +func (h *HTTPTransport) RequestHeaderOps() *headers.HeaderOps {
528 + // If TLS is not configured for this transport, don't inject Host
529 + // defaults. TLS being non-nil indicates HTTPS to the upstream.
530 + if h.TLS == nil {
531 + return nil
532 + }
533 + return &headers.HeaderOps{
534 + Set: http.Header{
535 + "Host": []string{"{http.reverse_proxy.upstream.hostport}"},
536 + },
537 + }
538 +}
539 +
540 // RoundTrip implements http.RoundTripper.
541 func (h *HTTPTransport) RoundTrip(req *http.Request) (*http.Response, error) {
542 h.SetScheme(req)
modules/caddyhttp/reverseproxy/httptransport_test.go
+21
@@ -94,3 +94,24 @@ func TestHTTPTransportUnmarshalCaddyFileWithCaPools(t *testing.T) {
94 })
95 }
96 }
97 +
98 +func TestHTTPTransport_RequestHeaderOps_TLS(t *testing.T) {
99 + var ht HTTPTransport
100 + // When TLS is nil, expect no header ops
101 + if ops := ht.RequestHeaderOps(); ops != nil {
102 + t.Fatalf("expected nil HeaderOps when TLS is nil, got: %#v", ops)
103 + }
104 +
105 + // When TLS is configured, expect a HeaderOps that sets Host
106 + ht.TLS = &TLSConfig{}
107 + ops := ht.RequestHeaderOps()
108 + if ops == nil {
109 + t.Fatal("expected non-nil HeaderOps when TLS is set")
110 + }
111 + if ops.Set == nil {
112 + t.Fatalf("expected ops.Set to be non-nil, got nil")
113 + }
114 + if got := ops.Set.Get("Host"); got != "{http.reverse_proxy.upstream.hostport}" {
115 + t.Fatalf("unexpected Host value; want placeholder, got: %s", got)
116 + }
117 +}
modules/caddyhttp/reverseproxy/reverseproxy.go
+47 -5
@@ -192,6 +192,13 @@ type Handler struct {
192 CB CircuitBreaker `json:"-"`
193 DynamicUpstreams UpstreamSource `json:"-"`
194
195 + // transportHeaderOps is a set of header operations provided
196 + // by the transport at provision time, if the transport
197 + // implements TransportHeaderOpsProvider. These ops are
198 + // applied before any user-configured header ops so the
199 + // user can override transport defaults.
200 + transportHeaderOps *headers.HeaderOps
201 +
202 // Holds the parsed CIDR ranges from TrustedProxies
203 trustedProxies []netip.Prefix
204
@@ -322,6 +329,18 @@ func (h *Handler) Provision(ctx caddy.Context) error {
329 h.Transport = t
330 }
331
332 + // If the transport can provide header ops, cache them now so we don't
333 + // have to compute them per-request. Provision the HeaderOps if present
334 + // so any runtime artifacts (like precompiled regex) are prepared.
335 + if tph, ok := h.Transport.(RequestHeaderOpsTransport); ok {
336 + h.transportHeaderOps = tph.RequestHeaderOps()
337 + if h.transportHeaderOps != nil {
338 + if err := h.transportHeaderOps.Provision(ctx); err != nil {
339 + return fmt.Errorf("provisioning transport header ops: %v", err)
340 + }
341 + }
342 + }
343 +
344 // set up load balancing
345 if h.LoadBalancing == nil {
346 h.LoadBalancing = new(LoadBalancing)
@@ -575,14 +594,26 @@ func (h *Handler) proxyLoopIteration(r *http.Request, origReq *http.Request, w h
594 repl.Set("http.reverse_proxy.upstream.fails", upstream.Host.Fails())
595
596 // mutate request headers according to this upstream;
578 - // because we're in a retry loop, we have to copy
579 - // headers (and the r.Host value) from the original
580 - // so that each retry is identical to the first
581 - if h.Headers != nil && h.Headers.Request != nil {
597 + // because we're in a retry loop, we have to copy headers
598 + // (and the r.Host value) from the original so that each
599 + // retry is identical to the first. If either transport or
600 + // user ops exist, apply them in order (transport first,
601 + // then user, so user's config wins).
602 + var userOps *headers.HeaderOps
603 + if h.Headers != nil {
604 + userOps = h.Headers.Request
605 + }
606 + transportOps := h.transportHeaderOps
607 + if transportOps != nil || userOps != nil {
608 r.Header = make(http.Header)
609 copyHeader(r.Header, reqHeader)
610 r.Host = reqHost
585 - h.Headers.Request.ApplyToRequest(r)
611 + if transportOps != nil {
612 + transportOps.ApplyToRequest(r)
613 + }
614 + if userOps != nil {
615 + userOps.ApplyToRequest(r)
616 + }
617 }
618
619 // proxy the request to that upstream
@@ -1542,6 +1573,17 @@ type BufferedTransport interface {
1573 DefaultBufferSizes() (int64, int64)
1574 }
1575
1576 +// RequestHeaderOpsTransport may be implemented by a transport to provide
1577 +// header operations to apply to requests immediately before the RoundTrip.
1578 +// For example, overriding the default Host when TLS is enabled.
1579 +type RequestHeaderOpsTransport interface {
1580 + // RequestHeaderOps allows a transport to provide header operations
1581 + // to apply to the request. The transport is asked at provision time
1582 + // to return a HeaderOps (or nil) that will be applied before
1583 + // user-configured header ops.
1584 + RequestHeaderOps() *headers.HeaderOps
1585 +}
1586 +
1587 // roundtripSucceededError is an error type that is returned if the
1588 // roundtrip succeeded, but an error occurred after-the-fact.
1589 type roundtripSucceededError struct{ error }