Repository: caddyserver/caddy (language: go)

Issue:
TLS health check on revers_proxy
Hi Team, 

I have caddy running with version: `v2.6.4 h1:2hwYqiRwk1tf3VruhMpLcYTg+11fCdr8S3jhNAdnPy8=` , and here is how the config looks, Caddyfile: 
```
reverse_proxy * https://<IP_address>:443 https://<IP_address>:443 {
  health_uri /
  health_port 8888
  health_timeout 10s
  health_status 200
  lb_policy uri_hash
  transport http {
    tls
    tls_insecure_skip_verify
    tls_except_ports 8888
  }
}
```

as you can see I'm using `reverse_proxy` module here to forward my incoming requests to the backend servers, the issue I'm facing is with the health checks to the backend, i have a health checker running on port `8888` and it needs to reached via http and not https, hence i used this config in `transport` block `tls_except_ports` hoping that connection would be non-tls, but still caddy tries to reach the health checker port via tls, here is a log snippet:

```
Jun 22 16:06:44 <backend> caddy[2247]: {"level":"info","ts":1687450004.2618585,"logger":"http.handlers.reverse_proxy.health_checker.active","msg":"HTTP request failed","host":"<IP_address>:8888","error":"Get \"https://<IP_address>:8888/\": context deadline exceeded (Client.Timeout exceeded while awaiting headers)"}
```

can you please help me here if i'm missing something?


Discussion / hints:
Thanks for your question, and we're glad that you're using Caddy! So far, this doesn't look like a bug in Caddy. It looks more like a question about how to use Caddy rather than a bug report or feature request.

Since this issue tracker is reserved for actionable development items, I'm going to close this, but we have a [community forum at caddy.community](https://caddy.community) where more people will be exposed to your question, including people who may be more expert or experienced than I am with the specific issue you're facing. I hope you'll ask your question there. The Help category in the forum even provides a template you can fill out.

If I'm wrong, and this does turn out to be a bug after further investigation, we can reopen this. Thanks for understanding!

Fix patch:
diff --git a/modules/caddyhttp/reverseproxy/healthchecks.go b/modules/caddyhttp/reverseproxy/healthchecks.go
index c969c8c1bc0..80b635adc0a 100644
--- a/modules/caddyhttp/reverseproxy/healthchecks.go
+++ b/modules/caddyhttp/reverseproxy/healthchecks.go
@@ -306,16 +306,35 @@ func (h *Handler) doActiveHealthCheckForAllHosts() {
 // the host's health status fails.
 func (h *Handler) doActiveHealthCheck(dialInfo DialInfo, hostAddr string, upstream *Upstream) error {
 	// create the URL for the request that acts as a health check
-	scheme := "http"
-	if ht, ok := h.Transport.(TLSTransport); ok && ht.TLSEnabled() {
-		// this is kind of a hacky way to know if we should use HTTPS, but whatever
-		scheme = "https"
-	}
 	u := &url.URL{
-		Scheme: scheme,
+		Scheme: "http",
 		Host:   hostAddr,
 	}
 
+	// split the host and port if possible, override the port if configured
+	host, port, err := net.SplitHostPort(hostAddr)
+	if err != nil {
+		host = hostAddr
+	}
+	if h.HealthChecks.Active.Port != 0 {
+		port := strconv.Itoa(h.HealthChecks.Active.Port)
+		u.Host = net.JoinHostPort(host, port)
+	}
+
+	// this is kind of a hacky way to know if we should use HTTPS, but whatever
+	if tt, ok := h.Transport.(TLSTransport); ok && tt.TLSEnabled() {
+		u.Scheme = "https"
+
+		// if the port is in the except list, flip back to HTTP
+		if ht, ok := h.Transport.(*HTTPTransport); ok {
+			for _, exceptPort := range ht.TLS.ExceptPorts {
+				if exceptPort == port {
+					u.Scheme = "http"
+				}
+			}
+		}
+	}
+
 	// if we have a provisioned uri, use that, otherwise use
 	// the deprecated Path option
 	if h.HealthChecks.Active.uri != nil {
@@ -325,16 +344,6 @@ func (h *Handler) doActiveHealthCheck(dialInfo DialInfo, hostAddr string, upstre
 		u.Path = h.HealthChecks.Active.Path
 	}
 
-	// adjust the port, if configured to be different
-	if h.HealthChecks.Active.Port != 0 {
-		portStr := strconv.Itoa(h.HealthChecks.Active.Port)
-		host, _, err := net.SplitHostPort(hostAddr)
-		if err != nil {
-			host = hostAddr
-		}
-		u.Host = net.JoinHostPort(host, portStr)
-	}
-
 	// attach dialing information to this request, as well as context values that
 	// may be expected by handlers of this request
 	ctx := h.ctx.Context


Test patch: