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 }