Skip to content

http.request placeholders not resolved in <find> of header directive #7109

@kiler129

Description

@kiler129

1. Environment

1a. Operating system and version

% docker version
Client:
 Version:           28.2.2
 API version:       1.50
 Go version:        go1.24.3
 Git commit:        e6534b4
 Built:             Fri May 30 12:07:35 2025
 OS/Arch:           darwin/arm64
 Context:           desktop-linux

Server: Docker Desktop 4.42.1 (196648)
 Engine:
  Version:          28.2.2
  API version:      1.50 (minimum version 1.24)
  Go version:       go1.24.3
  Git commit:       45873be
  Built:            Fri May 30 12:07:27 2025
  OS/Arch:          linux/arm64
  Experimental:     false
 containerd:
  Version:          1.7.27
  GitCommit:        05044ec0a9a75232cad458027ca83437aae3f4da
 runc:
  Version:          1.2.5
  GitCommit:        v1.2.5-0-g59923ef
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0

1b. Caddy version

v2.10.0 h1:fonubSaQKF1YANl8TXqGcn4IbIRUDdfAkpcsfI/vX5U=

2. Description

Attempting to use header directive in replace mode (<field> <find> <replace>), with placeholder used as <find>, results in no replacement being performed. Given the config below:

{
	debug
}

localhost {
	header X-Alt-Svc-Port "={http.request.local.port}="
	header X-Alt-Svc-NewPort "={$CADDY_EXT_HTTP3_PORT:-443}="

#	header Alt-Svc ":443" "TEST-1"
#	header Alt-Svc ":443" "TEST-2-{http.request.local.port}"
#	header Alt-Svc ":{http.request.local.port}" "TEST-3"
#	header Alt-Svc "TEST-4-{http.request.local.port}"

	respond "Hello, World!"
}

The results of curl -v -k https://localhost 2>&1 | grep alt-svc are as follows when subsequently uncommenting lines given:

# header Alt-Svc ":443" "TEST-1"
< alt-svc: h3="TEST-1"; ma=2592000
< x-alt-svc-newport: =8443=
< x-alt-svc-port: =443=
# header Alt-Svc ":443" "TEST-2-{http.request.local.port}"
< alt-svc: h3="TEST-2-443"; ma=2592000
< x-alt-svc-newport: =8443=
< x-alt-svc-port: =443=
# header Alt-Svc ":{http.request.local.port}" "TEST-3"
< alt-svc: h3=":443"; ma=2592000
< x-alt-svc-newport: =8443=
< x-alt-svc-port: =443=
#header Alt-Svc "TEST-4-{http.request.local.port}"
< alt-svc: TEST-4-443
< x-alt-svc-newport: =8443=
< x-alt-svc-port: =443=

It appears the value is available at this moment, as I can use it in <replace> as well

2b. Log output

% docker run -e CADDY_EXT_HTTP3_PORT=8443 -p 80:80 -p 443:443 -it -v "$(pwd)/docker/web-server/:/etc/caddy/:ro" caddy:latest              
2025/07/02 08:35:09.396 INFO    maxprocs: Leaving GOMAXPROCS=10: CPU quota undefined
2025/07/02 08:35:09.396 INFO    GOMEMLIMIT is updated   {"package": "github.com/KimMachineGun/automemlimit/memlimit", "GOMEMLIMIT": 18848965017, "previous": 9223372036854775807}
2025/07/02 08:35:09.397 INFO    using config from file  {"file": "/etc/caddy/Caddyfile"}
2025/07/02 08:35:09.398 INFO    adapted config to JSON  {"adapter": "caddyfile"}
2025/07/02 08:35:09.398 WARN    Caddyfile input is not formatted; run 'caddy fmt --overwrite' to fix inconsistencies    {"adapter": "caddyfile", "file": "/etc/caddy/Caddyfile", "line": 9}
2025/07/02 08:35:09.400 INFO    admin   admin endpoint started  {"address": "localhost:2019", "enforce_origin": false, "origins": ["//127.0.0.1:2019", "//localhost:2019", "//[::1]:2019"]}
2025/07/02 08:35:09.400 INFO    http.auto_https server is listening only on the HTTPS port but has no TLS connection policies; adding one to enable TLS {"server_name": "srv0", "https_port": 443}
2025/07/02 08:35:09.400 INFO    http.auto_https enabling automatic HTTP->HTTPS redirects        {"server_name": "srv0"}
2025/07/02 08:35:09.402 INFO    tls.cache.maintenance   started background certificate maintenance      {"cache": "0x4000702500"}
2025/07/02 08:35:09.409 DEBUG   http.auto_https adjusted config {"tls": {"automation":{"policies":[{"subjects":["localhost"]},{}]}}, "http": {"servers":{"remaining_auto_https_redirects":{"listen":[":80"],"routes":[{},{}]},"srv0":{"listen":[":443"],"routes":[{"handle":[{"handler":"subroute","routes":[{"handle":[{"handler":"headers","response":{"set":{"X-Alt-Svc-Port":["={http.request.local.port}="]}}},{"handler":"headers","response":{"set":{"X-Alt-Svc-Newport":["=8443="]}}},{"handler":"headers","response":{"replace":{"Alt-Svc":[{"replace":"TEST-3","search_regexp":":{http.request.local.port}"}]}}},{"body":"Hello, World!","handler":"static_response"}]}]}],"terminal":true}],"tls_connection_policies":[{}],"automatic_https":{}}}}}
2025/07/02 08:35:09.409 DEBUG   http    starting server loop    {"address": "[::]:80", "tls": false, "http3": false}
2025/07/02 08:35:09.409 WARN    http    HTTP/2 skipped because it requires TLS  {"network": "tcp", "addr": ":80"}
2025/07/02 08:35:09.409 WARN    http    HTTP/3 skipped because it requires TLS  {"network": "tcp", "addr": ":80"}
2025/07/02 08:35:09.409 INFO    http.log        server running  {"name": "remaining_auto_https_redirects", "protocols": ["h1", "h2", "h3"]}
2025/07/02 08:35:09.409 DEBUG   http    starting server loop    {"address": "[::]:443", "tls": true, "http3": false}
2025/07/02 08:35:09.409 INFO    http    enabling HTTP/3 listener        {"addr": ":443"}
2025/07/02 08:35:09.409 INFO    failed to sufficiently increase receive buffer size (was: 208 kiB, wanted: 7168 kiB, got: 416 kiB). See https://github.com/quic-go/quic-go/wiki/UDP-Buffer-Sizes for details.
2025/07/02 08:35:09.410 INFO    http.log        server running  {"name": "srv0", "protocols": ["h1", "h2", "h3"]}
2025/07/02 08:35:09.410 INFO    http    enabling automatic TLS certificate management   {"domains": ["localhost"]}
2025/07/02 08:35:09.410 WARN    pki.ca.local    installing root certificate (you might be prompted for password)        {"path": "storage:pki/authorities/local/root.crt"}
2025/07/02 08:35:09.411 INFO    warning: "certutil" is not available, install "certutil" with "apt install libnss3-tools" or "yum install nss-tools" and try again
2025/07/02 08:35:09.411 INFO    define JAVA_HOME environment variable to use the Java trust
2025/07/02 08:35:09.412 INFO    tls     cleaning storage unit   {"storage": "FileStorage:/data/caddy"}
2025/07/02 08:35:09.412 INFO    tls.obtain      acquiring lock  {"identifier": "localhost"}
2025/07/02 08:35:09.413 INFO    tls.obtain      lock acquired   {"identifier": "localhost"}
2025/07/02 08:35:09.413 INFO    tls.obtain      obtaining certificate   {"identifier": "localhost"}
2025/07/02 08:35:09.413 DEBUG   events  event   {"name": "cert_obtaining", "id": "03f66c55-92fb-4cdf-9fbd-cdf5c95573c1", "origin": "tls", "data": {"identifier":"localhost"}}
2025/07/02 08:35:09.413 INFO    tls     finished cleaning storage units
2025/07/02 08:35:09.413 DEBUG   tls     created CSR     {"identifiers": ["localhost"], "san_dns_names": ["localhost"], "san_emails": [], "common_name": "", "extra_extensions": 0}
2025/07/02 08:35:09.413 DEBUG   tls.obtain      trying issuer 1/1       {"issuer": "local"}
2025/07/02 08:35:09.413 DEBUG   pki.ca.local    using intermediate signer       {"serial": "32491877551547616715895509392942500190", "not_before": "2025-07-02 08:35:09 +0000 UTC", "not_after": "2025-07-09 08:35:09 +0000 UTC"}
2025/07/02 08:35:09.416 INFO    tls.obtain      certificate obtained successfully       {"identifier": "localhost", "issuer": "local"}
2025/07/02 08:35:09.416 DEBUG   events  event   {"name": "cert_obtained", "id": "9ae8dcd9-b6f3-4f40-a0a6-d879d669c143", "origin": "tls", "data": {"certificate_path":"certificates/local/localhost/localhost.crt","csr_pem":"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURSBSRVFVRVNULS0tLS0KTUlIaE1JR0pBZ0VBTUFBd1dUQVRCZ2NxaGtqT1BRSUJCZ2dxaGtqT1BRTUJCd05DQUFRcUJHNnNIRFcvd284aAoyV2JiZS93OHgvT20vdUJXdHR2bTN2b1NZK3FubmhyeVdjWExZQ0NENW1CSHJWZkhPNExXVkprWTdiNUVnUEhUCkZVUmZHb2J1b0Njd0pRWUpLb1pJaHZjTkFRa09NUmd3RmpBVUJnTlZIUkVFRFRBTGdnbHNiMk5oYkdodmMzUXcKQ2dZSUtvWkl6ajBFQXdJRFJ3QXdSQUlnRzZmaEJTczc3YzFwNG1TY09yNGNnZVlCMGtyNkZ5SHBiamtWRzZRaAppcmtDSUZFcDEvVWNSdkJwTWhmQ3Noa3pxek80KzBjTHlMa0VFb1dsQmpxWEk1RTQKLS0tLS1FTkQgQ0VSVElGSUNBVEUgUkVRVUVTVC0tLS0tCg==","identifier":"localhost","issuer":"local","metadata_path":"certificates/local/localhost/localhost.json","private_key_path":"certificates/local/localhost/localhost.key","renewal":false,"storage_path":"certificates/local/localhost"}}
2025/07/02 08:35:09.416 INFO    tls.obtain      releasing lock  {"identifier": "localhost"}
2025/07/02 08:35:09.416 DEBUG   tls.cache       added certificate to cache      {"subjects": ["localhost"], "expiration": "2025/07/02 20:35:10.000", "managed": true, "issuer_key": "local", "hash": "6cd09a596957dd471ba6b95ca5671aa045ddc4a44a8246bffd3c0350b0a34c99", "cache_size": 1, "cache_capacity": 10000}
2025/07/02 08:35:09.416 DEBUG   events  event   {"name": "cached_managed_cert", "id": "9bd9d154-5bc9-46fb-980e-9b2c75f3f05c", "origin": "tls", "data": {"sans":["localhost"]}}
2025/07/02 08:35:09.431 INFO    certificate installed properly in linux trusts
2025/07/02 08:35:09.431 DEBUG   events  event   {"name": "started", "id": "db1d9cec-392b-4d90-9268-ab1e2c858b2e", "origin": "", "data": null}
2025/07/02 08:35:09.432 INFO    autosaved config (load with --resume flag)      {"file": "/config/caddy/autosave.json"}
2025/07/02 08:35:09.432 INFO    serving initial configuration
2025/07/02 08:35:21.273 DEBUG   events  event   {"name": "tls_get_certificate", "id": "5ddae3b5-895b-4768-8890-71be87861548", "origin": "tls", "data": {"client_hello":{"CipherSuites":[4867,4866,4865,52393,52392,52394,49200,49196,49192,49188,49172,49162,159,107,57,65413,196,136,129,157,61,53,192,132,49199,49195,49191,49187,49171,49161,158,103,51,190,69,156,60,47,186,65,49169,49159,5,4,49170,49160,22,10,255],"ServerName":"localhost","SupportedCurves":[29,23,24,25],"SupportedPoints":"AA==","SignatureSchemes":[2054,1537,1539,2053,1281,1283,2052,1025,1027,513,515],"SupportedProtos":["h2","http/1.1"],"SupportedVersions":[772,771,770,769],"RemoteAddr":{"IP":"185.85.0.29","Port":29296,"Zone":""},"LocalAddr":{"IP":"172.17.0.6","Port":443,"Zone":""}}}}
2025/07/02 08:35:21.273 DEBUG   tls.handshake   choosing certificate    {"identifier": "localhost", "num_choices": 1}
2025/07/02 08:35:21.273 DEBUG   tls.handshake   default certificate selection results   {"identifier": "localhost", "subjects": ["localhost"], "managed": true, "issuer_key": "local", "hash": "6cd09a596957dd471ba6b95ca5671aa045ddc4a44a8246bffd3c0350b0a34c99"}
2025/07/02 08:35:21.273 DEBUG   tls.handshake   matched certificate in cache    {"remote_ip": "185.85.0.29", "remote_port": "29296", "subjects": ["localhost"], "managed": true, "expiration": "2025/07/02 20:35:10.000", "hash": "6cd09a596957dd471ba6b95ca5671aa045ddc4a44a8246bffd3c0350b0a34c99"}
^C2025/07/02 08:35:25.376       INFO    shutting down   {"signal": "SIGINT"}
2025/07/02 08:35:25.376 WARN    exiting; byeee!! 👋     {"signal": "SIGINT"}
2025/07/02 08:35:25.376 DEBUG   events  event   {"name": "stopping", "id": "037ffe94-cf7d-4790-b0a5-bbd991568151", "origin": "", "data": null}
2025/07/02 08:35:25.376 INFO    http    servers shutting down with eternal grace period
2025/07/02 08:35:25.377 INFO    admin   stopped previous server {"address": "localhost:2019"}
2025/07/02 08:35:25.377 INFO    shutdown complete       {"signal": "SIGINT", "exit_code": 0}

2d. Workaround(s)

This was an attempt to replace port in Alt-Svc header instead of building the header from scratch, as discussed in #4996. Naturally crafting the header manually works as expected:

# header Alt-Svc "h3=\":{$CADDY_EXT_HTTP3_PORT:-443}\"; ma=2592000"
% curl -v -k https://localhost 2>&1 | grep alt-svc
< alt-svc: h3=":8443"; ma=2592000

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions