Repository: caddyserver/caddy (language: go)

Issue:
No Placeholders in Active Health Checking Headers
## Version
**Caddy**: 2.7.2

## Bug description
When defining a header in the active health checker, placeholders as header values do not get interpreted altogether.

## Expected behaviour
Placeholders set in active health checker headers should be interpreted normally as everywhere else in the Caddyfile.

## Steps of bug reproduction
1. Set a new web server for request catching. This can either be a Caddy server, or any new web server spun up with any language.
  Example Caddyfile snippet and [WingBlade](https://github.com/ltgcgo/wingblade) (JavaScript) web server snippet below.
```sh
http://127.0.0.1:8081 {
	log {
		level INFO
	}
	handle /api/example {
		respond "OK"
	}
}
```
```js
"use strict";

WingBlade.web.serve(async function (request) {
	console.info("New request received. Header info below.");
	request.headers.forEach((value, key) => {
		console.info(`${key}: ${value}`);
	});
	return new Response("OK", {
		status: 200
	});
});
```
2. Set a header in a new reverse proxied target with an active health checker. Example Caddyfile snippet:
```sh
...
health_uri "/api/example"
health_headers {
	X-Hostname "{system.hostname}"
}
...
```
3. Reload the Caddyfile.
4. Check the sent headers in the target. Prettified Caddy log entry and WingBlade service log outputs are below.
```json
{
	"level": "info",
	"ts": 1691110717.1085844,
	"logger": "http.log.access",
	"msg": "handled request",
	"request": {
		"remote_ip": "127.0.0.1",
		"remote_port": "41368",
		"client_ip": "127.0.0.1",
		"proto": "HTTP/1.1",
		"method": "GET",
		"host": "127.0.0.1:8081",
		"uri": "/api/example",
		"headers": {
			"User-Agent": ["Go-http-client/1.1"],
			"X-Hostname": ["{system.hostname}"],
			"Accept-Encoding": ["gzip"]
		}
	},
	"bytes_read": 0,
	"user_id": "",
	"duration": 0.003900064,
	"size": 2,
	"status": 200,
	"resp_headers": {
		"Server": ["Caddy"],
		"Content-Type": ["text/plain; charset=utf-8"]
	}
}
```
```
New request received. Header info below.
User-Agent: Go-http-client/1.1
X-Hostname: {system.hostname}
Accept-Encoding: gzip
```


Discussion / hints:
I guess we didn't enable placeholders there because most placeholders are only available in the context of an HTTP request, which active health checks aren't. But the system and env var placeholders are always available, so that'd make sense.

After 2.7.3 I can add support for this. But not before :sweat_smile: 

Fix patch:
diff --git a/modules/caddyhttp/reverseproxy/caddyfile.go b/modules/caddyhttp/reverseproxy/caddyfile.go
index 674776f87a8..3e73e10e70e 100644
--- a/modules/caddyhttp/reverseproxy/caddyfile.go
+++ b/modules/caddyhttp/reverseproxy/caddyfile.go
@@ -372,7 +372,7 @@ func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
 				if len(values) == 0 {
 					values = append(values, "")
 				}
-				healthHeaders[key] = values
+				healthHeaders[key] = append(healthHeaders[key], values...)
 			}
 			if h.HealthChecks == nil {
 				h.HealthChecks = new(HealthChecks)
diff --git a/modules/caddyhttp/reverseproxy/healthchecks.go b/modules/caddyhttp/reverseproxy/healthchecks.go
index 1b4f2d05534..ad21ccb5c14 100644
--- a/modules/caddyhttp/reverseproxy/healthchecks.go
+++ b/modules/caddyhttp/reverseproxy/healthchecks.go
@@ -358,11 +358,17 @@ func (h *Handler) doActiveHealthCheck(dialInfo DialInfo, hostAddr string, upstre
 	}
 	ctx = context.WithValue(ctx, caddyhttp.OriginalRequestCtxKey, *req)
 	req = req.WithContext(ctx)
-	for key, hdrs := range h.HealthChecks.Active.Headers {
+
+	// set headers, using a replacer with only globals (env vars, system info, etc.)
+	repl := caddy.NewReplacer()
+	for key, vals := range h.HealthChecks.Active.Headers {
+		key = repl.ReplaceAll(key, "")
 		if key == "Host" {
-			req.Host = h.HealthChecks.Active.Headers.Get(key)
-		} else {
-			req.Header[key] = hdrs
+			req.Host = repl.ReplaceAll(h.HealthChecks.Active.Headers.Get(key), "")
+			continue
+		}
+		for _, val := range vals {
+			req.Header.Add(key, repl.ReplaceKnown(val, ""))
 		}
 	}
 


Test patch:
diff --git a/caddytest/integration/caddyfile_adapt/reverse_proxy_health_headers.txt b/caddytest/integration/caddyfile_adapt/reverse_proxy_health_headers.txt
index 17adcaa6828..800c11f188c 100644
--- a/caddytest/integration/caddyfile_adapt/reverse_proxy_health_headers.txt
+++ b/caddytest/integration/caddyfile_adapt/reverse_proxy_health_headers.txt
@@ -6,6 +6,9 @@ reverse_proxy 127.0.0.1:65535 {
 		X-Header-Key 95ca39e3cbe7
 		X-Header-Keys VbG4NZwWnipo 335Q9/MhqcNU3s2TO
 		X-Empty-Value
+		Same-Key 1
+		Same-Key 2
+		X-System-Hostname {system.hostname}
 	}
 	health_uri /health
 }
@@ -29,6 +32,10 @@ reverse_proxy 127.0.0.1:65535 {
 												"Host": [
 													"example.com"
 												],
+												"Same-Key": [
+													"1",
+													"2"
+												],
 												"X-Empty-Value": [
 													""
 												],
@@ -38,6 +45,9 @@ reverse_proxy 127.0.0.1:65535 {
 												"X-Header-Keys": [
 													"VbG4NZwWnipo",
 													"335Q9/MhqcNU3s2TO"
+												],
+												"X-System-Hostname": [
+													"{system.hostname}"
 												]
 											},
 											"uri": "/health"