1. The problem I’m having:
Caddy is not pushing correct headers using placeholders when reverse proxying to jetty websocket server. Specifically header_up Sec-WebSocket-Key
header. When I’m hardcoding the value, it does work.
I want to implement jwt validation and only then I want to proxy the same request to my jetty server which is listening for active websockets on port 8090
. Also websocket is listening on the path "/ws"
Caddy is build using xcaddy
on the module GitHub - ggicci/caddy-jwt: 🆔 Caddy Module JWT Authentication
2. Error messages and/or full log output:
Notice the differences in config file
- Hardcoded
Sec-WebSocket-Key
websocket headers - they work
{
order jwtauth before reverse_proxy
debug
}
localhost:8080 {
route /ws {
jwtauth {
sign_key YW1pZ28=
sign_alg HS256
user_claims name
}
reverse_proxy localhost:8090 {
header_up Host {http.request.host} # Explicit Host value
header_up X-Real-IP {http.request.remote.host} # Explicit IP value (localhost IPv6)
header_up X-Forwarded-For {http.request.remote.host} # Explicit IP value (localhost IPv6)
header_up X-Forwarded-Port {http.request.port} # Explicit port value
header_up X-Forwarded-Proto {http.request.scheme} # Explicit protocol
header_up Sec-WebSocket-Version 13
header_up Sec-WebSocket-Key n5Y5CpwHDlJv1O6JPW55FFMDNCy7GHI0wehpvAVLB4
# header_up Sec-WebSocket-Key {>Sec-WebSocket-Key}
header_up Upgrade websocket # Explicit Upgrade value
header_up Connection Upgrade # Explicit Connection value
}
}
}
After using caddy.exe run
I send this request via curl
curl -vvvkl "https://localhost:8080/ws" -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiaG9sYSJ9.28_fvE4ygYXjzL9CbxXgBR2ZdCWmnOXCO8tvYAI4eq0"
o/p
* Host localhost:8080 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
* Trying [::1]:8080...
* Connected to localhost (::1) port 8080
* schannel: disabled automatic use of client certificate
* ALPN: curl offers http/1.1
* ALPN: server accepted http/1.1
* using HTTP/1.x
> GET /ws HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/8.9.1
> Accept: */*
> Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiaG9sYSJ9.28_fvE4ygYXjzL9CbxXgBR2ZdCWmnOXCO8tvYAI4eq0
>
* Request completely sent off
< HTTP/1.1 101 Switching Protocols
< Alt-Svc: h3=":8080"; ma=2592000
< Connection: Upgrade
< Date: Wed, 02 Apr 2025 17:58:22 GMT
< Sec-WebSocket-Accept: WShj5nKJRfyS7XVd4/uOdjqrCNE=
< Server: Caddy
< Server: Jetty(12.0.16)
< Upgrade: websocket
<
Caddy Logs
2025/04/02 17:55:14.013 ←[34mINFO←[0m using adjacent Caddyfile
2025/04/02 17:55:14.014 ←[33mWARN←[0m caddyfile Unnecessary header_up X-Forwarded-For: the reverse proxy's default behavior is to pass headers to the upstream
2025/04/02 17:55:14.014 ←[33mWARN←[0m caddyfile Unnecessary header_up X-Forwarded-Proto: the reverse proxy's default behavior is to pass headers to the upstream
2025/04/02 17:55:14.015 ←[34mINFO←[0m adapted config to JSON {"adapter": "caddyfile"}
2025/04/02 17:55:14.016 ←[33mWARN←[0m Caddyfile input is not formatted; run 'caddy fmt --overwrite' to fix inconsistencies {"adapter": "caddyfile", "file": "Caddyfile", "line": 2}
2025/04/02 17:55:14.026 ←[34mINFO←[0m admin admin endpoint started {"address": "localhost:2019", "enforce_origin": false, "origins": ["//[::1]:2019", "//127.0.0.1:2019", "//localhost:2019"]}
2025/04/02 17:55:14.026 ←[34mINFO←[0m tls.cache.maintenance started background certificate maintenance {"cache": "0xc00064f800"}
2025/04/02 17:55:14.026 ←[34mINFO←[0m http.auto_https enabling automatic HTTP->HTTPS redirects {"server_name": "srv0"}
2025/04/02 17:55:14.027 ←[35mDEBUG←[0m http.auto_https adjusted config {"tls": {"automation":{"policies":[{"subjects":["localhost"]},{}]}}, "http": {"servers":{"remaining_auto_https_redirects":{"listen":[":80"],"routes":[{},{}]},"srv0":{"listen":[":8080"],"routes":[{"handle":[{"handler":"subroute","routes":[{"handle":[{"handler":"subroute","routes":[{"handle":[{"handler":"authentication","providers":{"jwt":{"audience_whitelist":null,"from_cookies":null,"from_header":null,"from_query":null,"issuer_whitelist":null,"jwk_url":"","meta_claims":null,"sign_alg":"HS256","sign_key":"YW1pZ28=","skip_verification":false,"user_claims":["name"]}}},{"handler":"reverse_proxy","headers":{"request":{"set":{"Connection":["Upgrade"],"Host":["{http.request.host}"],"Sec-Websocket-Key":["n5Y5CpwHDlJv1O6JPW55FFMDNCy7GHI0wehpvAVLB4"],"Sec-Websocket-Version":["13"],"Upgrade":["websocket"],"X-Forwarded-For":["{http.request.remote.host}"],"X-Forwarded-Port":["{http.request.port}"],"X-Forwarded-Proto":["{http.request.scheme}"],"X-Real-Ip":["{http.request.remote.host}"]}}},"upstreams":[{"dial":"localhost:8090"}]}]}]}],"match":[{"path":["/ws"]}]}]}],"terminal":true}],"tls_connection_policies":[{}],"automatic_https":{}}}}}
2025/04/02 17:55:14.030 ←[34mINFO←[0m tls storage cleaning happened too recently; skipping for now {"storage": "FileStorage:C:\\Users\\username\\AppData\\Roaming\\Caddy", "instance": "49e59ef7-a3f2-4fd3-a466-4f510b2ddf53", "try_again": "2025/04/03 17:55:14.030", "try_again_in": 86400}
2025/04/02 17:55:14.030 ←[34mINFO←[0m tls finished cleaning storage units
2025/04/02 17:55:14.042 ←[34mINFO←[0m pki.ca.local root certificate is already trusted by system {"path": "storage:pki/authorities/local/root.crt"}
2025/04/02 17:55:14.043 ←[35mDEBUG←[0m http starting server loop {"address": "[::]:8080", "tls": true, "http3": false}
2025/04/02 17:55:14.043 ←[34mINFO←[0m http enabling HTTP/3 listener {"addr": ":8080"}
2025/04/02 17:55:14.043 ←[34mINFO←[0m http.log server running {"name": "srv0", "protocols": ["h1", "h2", "h3"]}
2025/04/02 17:55:14.043 ←[35mDEBUG←[0m http starting server loop {"address": "[::]:80", "tls": false, "http3": false}
2025/04/02 17:55:14.043 ←[33mWARN←[0m http HTTP/2 skipped because it requires TLS {"network": "tcp", "addr": ":80"}
2025/04/02 17:55:14.043 ←[33mWARN←[0m http HTTP/3 skipped because it requires TLS {"network": "tcp", "addr": ":80"}
2025/04/02 17:55:14.043 ←[34mINFO←[0m http.log server running {"name": "remaining_auto_https_redirects", "protocols": ["h1", "h2", "h3"]}
2025/04/02 17:55:14.043 ←[34mINFO←[0m http enabling automatic TLS certificate management {"domains": ["localhost"]}
2025/04/02 17:55:14.044 ←[35mDEBUG←[0m tls.cache added certificate to cache {"subjects": ["localhost"], "expiration": "2025/04/02 23:25:40.000", "managed": true, "issuer_key": "local", "hash": "edc650750ad0172995bdc5bd6741ebb0360839308c679698931e56f91d0283bb", "cache_size": 1, "cache_capacity": 10000}
2025/04/02 17:55:14.044 ←[35mDEBUG←[0m events event {"name": "cached_managed_cert", "id": "58dbc699-b587-4eae-9411-97f592dd4a45", "origin": "tls", "data": {"sans":["localhost"]}}
2025/04/02 17:55:14.045 ←[34mINFO←[0m autosaved config (load with --resume flag) {"file": "C:\\Users\\username\\AppData\\Roaming\\Caddy\\autosave.json"}
2025/04/02 17:55:14.045 ←[34mINFO←[0m serving initial configuration
2025/04/02 17:58:22.365 ←[35mDEBUG←[0m events event {"name": "tls_get_certificate", "id": "e36d88a9-f766-4486-b6d9-323de84360f5", "origin": "tls", "data": {"client_hello":{"CipherSuites":[49196,49195,49200,49199,159,158,49188,49187,49192,49191,49162,49161,49172,49171,157,156,61,60,53,47,10],"ServerName":"localhost","SupportedCurves":[29,23,24],"SupportedPoints":"AA==","SignatureSchemes":[2052,2053,2054,1025,1281,513,1027,1283,515,514,1537,1539],"SupportedProtos":["http/1.1"],"SupportedVersions":[771,770,769],"RemoteAddr":{"IP":"::1","Port":58398,"Zone":""},"LocalAddr":{"IP":"::1","Port":8080,"Zone":""}}}}
2025/04/02 17:58:22.365 ←[35mDEBUG←[0m tls.handshake choosing certificate {"identifier": "localhost", "num_choices": 1}
2025/04/02 17:58:22.365 ←[35mDEBUG←[0m tls.handshake default certificate selection results {"identifier": "localhost", "subjects": ["localhost"], "managed": true, "issuer_key": "local", "hash": "edc650750ad0172995bdc5bd6741ebb0360839308c679698931e56f91d0283bb"}
2025/04/02 17:58:22.365 ←[35mDEBUG←[0m tls.handshake matched certificate in cache {"remote_ip": "::1", "remote_port": "58398", "subjects": ["localhost"], "managed": true, "expiration": "2025/04/02 23:25:40.000", "hash": "edc650750ad0172995bdc5bd6741ebb0360839308c679698931e56f91d0283bb"}
2025/04/02 17:58:22.370 ←[34mINFO←[0m http.authentication.providers.jwt user authenticated {"token_string": "eyJhbGciOiJIUzI1…mnOXCO8tvYAI4eq0", "user_claim": "name", "id": "hola"}
2025/04/02 17:58:22.370 ←[35mDEBUG←[0m http.handlers.reverse_proxy selected upstream {"dial": "localhost:8090", "total_upstreams": 1}
2025/04/02 17:58:22.388 ←[35mDEBUG←[0m http.handlers.reverse_proxy upstream roundtrip {"upstream": "localhost:8090", "duration": 0.0171977, "request": {"remote_ip": "::1", "remote_port": "58398", "client_ip": "::1", "proto": "HTTP/1.1", "method": "GET", "host": "localhost", "uri": "/ws", "headers": {"X-Forwarded-Port": ["8080"], "Sec-Websocket-Version": ["13"], "User-Agent": ["curl/8.9.1"], "Accept": ["*/*"], "Authorization": ["REDACTED"], "X-Forwarded-Host": ["localhost:8080"], "Upgrade": ["websocket"], "X-Real-Ip": ["::1"], "Connection": ["Upgrade"], "X-Forwarded-For": ["::1"], "X-Forwarded-Proto": ["https"], "Sec-Websocket-Key": ["n5Y5CpwHDlJv1O6JPW55FFMDNCy7GHI0wehpvAVLB4"]}, "tls": {"resumed": false, "version": 771, "cipher_suite": 49195, "proto": "http/1.1", "server_name": "localhost"}}, "headers": {"Server": ["Jetty(12.0.16)"], "Date": ["Wed, 02 Apr 2025 17:58:22 GMT"], "Upgrade": ["websocket"], "Sec-Websocket-Accept": ["WShj5nKJRfyS7XVd4/uOdjqrCNE="], "Connection": ["Upgrade"]}, "status": 101}
2025/04/02 17:58:22.389 ←[35mDEBUG←[0m http.handlers.reverse_proxy upgrading connection {"upstream": "localhost:8090", "duration": 0.0171977, "request": {"remote_ip": "::1", "remote_port": "58398", "client_ip": "::1", "proto": "HTTP/1.1", "method": "GET", "host": "localhost", "uri": "/ws", "headers": {"X-Forwarded-Port": ["8080"], "Sec-Websocket-Version": ["13"], "User-Agent": ["curl/8.9.1"], "Accept": ["*/*"], "Authorization": ["REDACTED"], "X-Forwarded-Host": ["localhost:8080"], "Upgrade": ["websocket"], "X-Real-Ip": ["::1"], "Connection": ["Upgrade"], "X-Forwarded-For": ["::1"], "X-Forwarded-Proto": ["https"], "Sec-Websocket-Key": ["n5Y5CpwHDlJv1O6JPW55FFMDNCy7GHI0wehpvAVLB4"]}, "tls": {"resumed": false, "version": 771, "cipher_suite": 49195, "proto": "http/1.1", "server_name": "localhost"}}, "http_version": 1}
As you can see from Caddy log [The last line], this is the name:value for the header "Sec-Websocket-Key": ["n5Y5CpwHDlJv1O6JPW55FFMDNCy7GHI0wehpvAVLB4"]}
Additionally from my jetty logs, I see this header
23:28:22.381 [qtp1147580192-56] DEBUG o.eclipse.jetty.http.HttpParser - HEADER:FIELD --> IN_VALUE
23:28:22.381 [qtp1147580192-56] DEBUG o.eclipse.jetty.http.HttpParser - HEADER:IN_VALUE --> FIELD(Connection: Upgrade: Upgrade)
23:28:22.381 [qtp1147580192-56] DEBUG o.eclipse.jetty.http.HttpParser - parsedHeader(Connection: Upgrade) header=Connection, headerString=[Connection], valueString=[Upgrade]
23:28:22.381 [qtp1147580192-56] DEBUG o.eclipse.jetty.http.HttpParser - HEADER:FIELD --> VALUE
23:28:22.381 [qtp1147580192-56] DEBUG o.eclipse.jetty.http.HttpParser - HEADER:VALUE --> IN_VALUE
23:28:22.381 [qtp1147580192-56] DEBUG o.eclipse.jetty.http.HttpParser - HEADER:IN_VALUE --> FIELD(Sec-WebSocket-Key: n5Y5CpwHDlJv1O6JPW55FFMDNCy7GHI0wehpvAVLB4)
23:28:22.381 [qtp1147580192-56] DEBUG o.eclipse.jetty.http.HttpParser - parsedHeader(null) header=Sec-WebSocket-Key, headerString=[Sec-WebSocket-Key], valueString=[n5Y5CpwHDlJv1O6JPW55FFMDNCy7GHI0wehpvAVLB4]
So that means Caddy can forward the hardcoded header which is expected.
- Placeholders websocket headers - the placeholders are being passed as is in the reverse_proxy section. Please note the 3rd last header
{
order jwtauth before reverse_proxy
debug
}
localhost:8080 {
route /ws {
jwtauth {
sign_key YW1pZ28=
sign_alg HS256
user_claims name
}
reverse_proxy localhost:8090 {
header_up Host {http.request.host} # Explicit Host value
header_up X-Real-IP {http.request.remote.host} # Explicit IP value (localhost IPv6)
header_up X-Forwarded-For {http.request.remote.host} # Explicit IP value (localhost IPv6)
header_up X-Forwarded-Port {http.request.port} # Explicit port value
header_up X-Forwarded-Proto {http.request.scheme} # Explicit protocol
header_up Sec-Websocket-Version 13
# header_up Sec-WebSocket-Key n5Y5CpwHDlJv1O6JPW55FFMDNCy7GHI0wehpvAVLB4
header_up Sec-WebSocket-Key {>Sec-WebSocket-Key}
header_up Upgrade websocket # Explicit Upgrade value
header_up Connection Upgrade # Explicit Connection value
}
}
}
o/p from curl
* Host localhost:8080 was resolved.
* IPv6: ::1
* IPv4: 127.0.0.1
* Trying [::1]:8080...
* Connected to localhost (::1) port 8080
* schannel: disabled automatic use of client certificate
* ALPN: curl offers http/1.1
* ALPN: server accepted http/1.1
* using HTTP/1.x
> GET /ws HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/8.9.1
> Accept: */*
> Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiaG9sYSJ9.28_fvE4ygYXjzL9CbxXgBR2ZdCWmnOXCO8tvYAI4eq0
>
* Request completely sent off
< HTTP/1.1 101 Switching Protocols
< Alt-Svc: h3=":8080"; ma=2592000
< Connection: Upgrade
< Date: Wed, 02 Apr 2025 18:05:40 GMT
< Sec-WebSocket-Accept: 8uqguKSXBj9pNIoSSdV7K8mDnXw=
< Server: Caddy
< Server: Jetty(12.0.16)
< Upgrade: websocket
<
Caddy Logs
2025/04/02 18:04:33.856 ←[34mINFO←[0m using adjacent Caddyfile
2025/04/02 18:04:33.857 ←[33mWARN←[0m caddyfile Unnecessary header_up X-Forwarded-For: the reverse proxy's default behavior is to pass headers to the upstream
2025/04/02 18:04:33.857 ←[33mWARN←[0m caddyfile Unnecessary header_up X-Forwarded-Proto: the reverse proxy's default behavior is to pass headers to the upstream
2025/04/02 18:04:33.859 ←[34mINFO←[0m adapted config to JSON {"adapter": "caddyfile"}
2025/04/02 18:04:33.859 ←[33mWARN←[0m Caddyfile input is not formatted; run 'caddy fmt --overwrite' to fix inconsistencies {"adapter": "caddyfile", "file": "Caddyfile", "line": 2}
2025/04/02 18:04:33.868 ←[34mINFO←[0m admin admin endpoint started {"address": "localhost:2019", "enforce_origin": false, "origins": ["//localhost:2019", "//[::1]:2019", "//127.0.0.1:2019"]}
2025/04/02 18:04:33.868 ←[34mINFO←[0m tls.cache.maintenance started background certificate maintenance {"cache": "0xc00044eb80"}
2025/04/02 18:04:33.868 ←[34mINFO←[0m http.auto_https enabling automatic HTTP->HTTPS redirects {"server_name": "srv0"}
2025/04/02 18:04:33.869 ←[35mDEBUG←[0m http.auto_https adjusted config {"tls": {"automation":{"policies":[{"subjects":["localhost"]},{}]}}, "http": {"servers":{"remaining_auto_https_redirects":{"listen":[":80"],"routes":[{},{}]},"srv0":{"listen":[":8080"],"routes":[{"handle":[{"handler":"subroute","routes":[{"handle":[{"handler":"subroute","routes":[{"handle":[{"handler":"authentication","providers":{"jwt":{"audience_whitelist":null,"from_cookies":null,"from_header":null,"from_query":null,"issuer_whitelist":null,"jwk_url":"","meta_claims":null,"sign_alg":"HS256","sign_key":"YW1pZ28=","skip_verification":false,"user_claims":["name"]}}},{"handler":"reverse_proxy","headers":{"request":{"set":{"Connection":["Upgrade"],"Host":["{http.request.host}"],"Sec-Websocket-Key":["{\u003eSec-WebSocket-Key}"],"Sec-Websocket-Version":["13"],"Upgrade":["websocket"],"X-Forwarded-For":["{http.request.remote.host}"],"X-Forwarded-Port":["{http.request.port}"],"X-Forwarded-Proto":["{http.request.scheme}"],"X-Real-Ip":["{http.request.remote.host}"]}}},"upstreams":[{"dial":"localhost:8090"}]}]}]}],"match":[{"path":["/ws"]}]}]}],"terminal":true}],"tls_connection_policies":[{}],"automatic_https":{}}}}}
2025/04/02 18:04:33.884 ←[34mINFO←[0m pki.ca.local root certificate is already trusted by system {"path": "storage:pki/authorities/local/root.crt"}
2025/04/02 18:04:33.884 ←[35mDEBUG←[0m http starting server loop {"address": "[::]:8080", "tls": true, "http3": false}
2025/04/02 18:04:33.885 ←[34mINFO←[0m http enabling HTTP/3 listener {"addr": ":8080"}
2025/04/02 18:04:33.885 ←[34mINFO←[0m http.log server running {"name": "srv0", "protocols": ["h1", "h2", "h3"]}
2025/04/02 18:04:33.885 ←[35mDEBUG←[0m http starting server loop {"address": "[::]:80", "tls": false, "http3": false}
2025/04/02 18:04:33.885 ←[33mWARN←[0m http HTTP/2 skipped because it requires TLS {"network": "tcp", "addr": ":80"}
2025/04/02 18:04:33.885 ←[33mWARN←[0m http HTTP/3 skipped because it requires TLS {"network": "tcp", "addr": ":80"}
2025/04/02 18:04:33.885 ←[34mINFO←[0m http.log server running {"name": "remaining_auto_https_redirects", "protocols": ["h1", "h2", "h3"]}
2025/04/02 18:04:33.885 ←[34mINFO←[0m http enabling automatic TLS certificate management {"domains": ["localhost"]}
2025/04/02 18:04:33.886 ←[35mDEBUG←[0m tls.cache added certificate to cache {"subjects": ["localhost"], "expiration": "2025/04/02 23:25:40.000", "managed": true, "issuer_key": "local", "hash": "edc650750ad0172995bdc5bd6741ebb0360839308c679698931e56f91d0283bb", "cache_size": 1, "cache_capacity": 10000}
2025/04/02 18:04:33.886 ←[35mDEBUG←[0m events event {"name": "cached_managed_cert", "id": "a8e44720-ff0e-4687-87a5-bb03ea5c53fc", "origin": "tls", "data": {"sans":["localhost"]}}
2025/04/02 18:04:33.887 ←[34mINFO←[0m autosaved config (load with --resume flag) {"file": "C:\\Users\\username\\AppData\\Roaming\\Caddy\\autosave.json"}
2025/04/02 18:04:33.887 ←[34mINFO←[0m serving initial configuration
2025/04/02 18:04:33.888 ←[34mINFO←[0m tls storage cleaning happened too recently; skipping for now {"storage": "FileStorage:C:\\Users\\username\\AppData\\Roaming\\Caddy", "instance": "49e59ef7-a3f2-4fd3-a466-4f510b2ddf53", "try_again": "2025/04/03 18:04:33.888", "try_again_in": 86400}
2025/04/02 18:04:33.888 ←[34mINFO←[0m tls finished cleaning storage units
2025/04/02 18:05:40.358 ←[35mDEBUG←[0m events event {"name": "tls_get_certificate", "id": "83c27146-a56d-4594-8c1e-7c2d6d7e247f", "origin": "tls", "data": {"client_hello":{"CipherSuites":[49196,49195,49200,49199,159,158,49188,49187,49192,49191,49162,49161,49172,49171,157,156,61,60,53,47,10],"ServerName":"localhost","SupportedCurves":[29,23,24],"SupportedPoints":"AA==","SignatureSchemes":[2052,2053,2054,1025,1281,513,1027,1283,515,514,1537,1539],"SupportedProtos":["http/1.1"],"SupportedVersions":[771,770,769],"RemoteAddr":{"IP":"::1","Port":58411,"Zone":""},"LocalAddr":{"IP":"::1","Port":8080,"Zone":""}}}}
2025/04/02 18:05:40.358 ←[35mDEBUG←[0m tls.handshake choosing certificate {"identifier": "localhost", "num_choices": 1}
2025/04/02 18:05:40.359 ←[35mDEBUG←[0m tls.handshake default certificate selection results {"identifier": "localhost", "subjects": ["localhost"], "managed": true, "issuer_key": "local", "hash": "edc650750ad0172995bdc5bd6741ebb0360839308c679698931e56f91d0283bb"}
2025/04/02 18:05:40.359 ←[35mDEBUG←[0m tls.handshake matched certificate in cache {"remote_ip": "::1", "remote_port": "58411", "subjects": ["localhost"], "managed": true, "expiration": "2025/04/02 23:25:40.000", "hash": "edc650750ad0172995bdc5bd6741ebb0360839308c679698931e56f91d0283bb"}
2025/04/02 18:05:40.365 ←[34mINFO←[0m http.authentication.providers.jwt user authenticated {"token_string": "eyJhbGciOiJIUzI1…mnOXCO8tvYAI4eq0", "user_claim": "name", "id": "hola"}
2025/04/02 18:05:40.365 ←[35mDEBUG←[0m http.handlers.reverse_proxy selected upstream {"dial": "localhost:8090", "total_upstreams": 1}
2025/04/02 18:05:40.378 ←[35mDEBUG←[0m http.handlers.reverse_proxy upstream roundtrip {"upstream": "localhost:8090", "duration": 0.0125187, "request": {"remote_ip": "::1", "remote_port": "58411", "client_ip": "::1", "proto": "HTTP/1.1", "method": "GET", "host": "localhost", "uri": "/ws", "headers": {"X-Forwarded-Host": ["localhost:8080"], "Authorization": ["REDACTED"], "X-Forwarded-For": ["::1"], "Sec-Websocket-Key": ["{>Sec-WebSocket-Key}"], "Sec-Websocket-Version": ["13"], "Upgrade": ["websocket"], "X-Real-Ip": ["::1"], "Connection": ["Upgrade"], "X-Forwarded-Proto": ["https"], "User-Agent": ["curl/8.9.1"], "Accept": ["*/*"], "X-Forwarded-Port": ["8080"]}, "tls": {"resumed": false, "version": 771, "cipher_suite": 49195, "proto": "http/1.1", "server_name": "localhost"}}, "headers": {"Date": ["Wed, 02 Apr 2025 18:05:40 GMT"], "Upgrade": ["websocket"], "Sec-Websocket-Accept": ["8uqguKSXBj9pNIoSSdV7K8mDnXw="], "Connection": ["Upgrade"], "Server": ["Jetty(12.0.16)"]}, "status": 101}
2025/04/02 18:05:40.378 ←[35mDEBUG←[0m http.handlers.reverse_proxy upgrading connection {"upstream": "localhost:8090", "duration": 0.0125187, "request": {"remote_ip": "::1", "remote_port": "58411", "client_ip": "::1", "proto": "HTTP/1.1", "method": "GET", "host": "localhost", "uri": "/ws", "headers": {"X-Forwarded-Host": ["localhost:8080"], "Authorization": ["REDACTED"], "X-Forwarded-For": ["::1"], "Sec-Websocket-Key": ["{>Sec-WebSocket-Key}"], "Sec-Websocket-Version": ["13"], "Upgrade": ["websocket"], "X-Real-Ip": ["::1"], "Connection": ["Upgrade"], "X-Forwarded-Proto": ["https"], "User-Agent": ["curl/8.9.1"], "Accept": ["*/*"], "X-Forwarded-Port": ["8080"]}, "tls": {"resumed": false, "version": 771, "cipher_suite": 49195, "proto": "http/1.1", "server_name": "localhost"}}, "http_version": 1}
This header is passed as is "Sec-Websocket-Key": ["{>Sec-WebSocket-Key}"]
[Im unsure if this behaviour is expected or not]
Same in jetty logs as well
23:35:40.373 [qtp1147580192-41] DEBUG o.eclipse.jetty.http.HttpParser - HEADER:IN_VALUE --> FIELD(Sec-WebSocket-Key: {>Sec-WebSocket-Key})
23:35:40.373 [qtp1147580192-41] DEBUG o.eclipse.jetty.http.HttpParser - parsedHeader(null) header=Sec-WebSocket-Key, headerString=[Sec-WebSocket-Key], valueString=[{>Sec-WebSocket-Key}]
23:35:40.373 [qtp1147580192-41] DEBUG o.eclipse.jetty.http.HttpParser - HEADER:FIELD --> VALUE
3. Caddy version:
caddy.exe --version
v2.9.1 h1:OEYiZ7DbCzAWVb6TNEkjRcSCRGHVoZsJinoDR/n9oaY=
4. How I installed and ran Caddy:
a. System environment:
Build with xcaddy and running on Windows 10. Running from the same directory using cmd in blocking mode and not as a service. Caddy is build using xcaddy
on the module GitHub - ggicci/caddy-jwt: 🆔 Caddy Module JWT Authentication
5. Links to relevant resources:
This header Sec-WebSocket-Key
seem to be important because it might have been mandated by jetty
- jetty.project/jetty-websocket/websocket-server/src/main/java/org/eclipse/jetty/websocket/server/HandshakeRFC6455.java at ac24196b0d341534793308d585161381d5bca4ac · jetty/jetty.project · GitHub
- Issue #3608 - give 400 error response for missing Sec-WebSocket-Key by lachlan-roberts · Pull Request #3610 · jetty/jetty.project · GitHub
Goal
Please help so that I dont have to hardcode the value to caddy. Please also let me know if this can be generated by caddy itself ?