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, 14:45 UTC
reverseproxy: Fix `health_port` being ignored in health checks (#7533)
Akın Demirci committed
Mar 3, 2026 at 21:10 UTC
11b56c6cfc25f8c814fa66cb02060548d12c4040
2 files changed
+68
caddytest/integration/reverseproxy_test.go
+62
@@ -386,6 +386,68 @@ func TestReverseProxyHealthCheck(t *testing.T) {
386
tester.AssertGetResponse("http://localhost:9080/", 200, "Hello, World!")
387
}
388
389
+// TestReverseProxyHealthCheckPortUsed verifies that health_port is actually
390
+// used for active health checks and not the upstream's main port. This is a
391
+// regression test for https://github.com/caddyserver/caddy/issues/7524.
392
+func TestReverseProxyHealthCheckPortUsed(t *testing.T) {
393
+ // upstream server: serves proxied requests normally, but returns 503 for
394
+ // /health so that if health checks mistakenly hit this port the upstream
395
+ // gets marked unhealthy and the proxy returns 503.
396
+ upstreamSrv := &http.Server{
397
+ Handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
398
+ if req.URL.Path == "/health" {
399
+ w.WriteHeader(http.StatusServiceUnavailable)
400
+ return
401
+ }
402
+ _, _ = w.Write([]byte("Hello, World!"))
403
+ }),
404
+ }
405
+ ln0, err := net.Listen("tcp", "127.0.0.1:2022")
406
+ if err != nil {
407
+ t.Fatalf("failed to listen on 127.0.0.1:2022: %v", err)
408
+ }
409
+ go upstreamSrv.Serve(ln0)
410
+ t.Cleanup(func() { upstreamSrv.Close(); ln0.Close() })
411
+
412
+ // separate health check server on the configured health_port: returns 200
413
+ // so the upstream is marked healthy only if health checks go to this port.
414
+ healthSrv := &http.Server{
415
+ Handler: http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
416
+ _, _ = w.Write([]byte("ok"))
417
+ }),
418
+ }
419
+ ln1, err := net.Listen("tcp", "127.0.0.1:2023")
420
+ if err != nil {
421
+ t.Fatalf("failed to listen on 127.0.0.1:2023: %v", err)
422
+ }
423
+ go healthSrv.Serve(ln1)
424
+ t.Cleanup(func() { healthSrv.Close(); ln1.Close() })
425
+
426
+ tester := caddytest.NewTester(t)
427
+ tester.InitServer(`
428
+ {
429
+ skip_install_trust
430
+ admin localhost:2999
431
+ http_port 9080
432
+ https_port 9443
433
+ grace_period 1ns
434
+ }
435
+ http://localhost:9080 {
436
+ reverse_proxy {
437
+ to localhost:2022
438
+
439
+ health_uri /health
440
+ health_port 2023
441
+ health_interval 10ms
442
+ health_timeout 100ms
443
+ health_passes 1
444
+ health_fails 1
445
+ }
446
+ }
447
+ `, "caddyfile")
448
+ tester.AssertGetResponse("http://localhost:9080/", 200, "Hello, World!")
449
+}
450
+
451
func TestReverseProxyHealthCheckUnixSocket(t *testing.T) {
452
if runtime.GOOS == "windows" {
453
t.SkipNow()
modules/caddyhttp/reverseproxy/healthchecks.go
+6
@@ -359,6 +359,12 @@ func (h *Handler) doActiveHealthCheckForAllHosts() {
359
dialInfoUpstream = &Upstream{
360
Dial: h.HealthChecks.Active.Upstream,
361
}
362
+ } else if upstream.activeHealthCheckPort != 0 {
363
+ // health_port overrides the port; addr has already been updated
364
+ // with the health port, so use its address for dialing
365
+ dialInfoUpstream = &Upstream{
366
+ Dial: addr.JoinHostPort(0),
367
+ }
368
}
369
dialInfo, _ := dialInfoUpstream.fillDialInfo(repl)
370