Describe the issue
MockServer hangs for ~30 seconds and then sends a graceful HTTP/2 GOAWAY (NO_ERROR, last_stream=1) without sending any response, when an HTTPS request is made through the proxy port (1080) over HTTP/2. The same request works correctly when forced to HTTP/1.1.
This affects both:
- requests matching a configured expectation (mocked response), and
- requests with no matching expectation (forward / catch-all behaviour).
The hang happens after MockServer accepts the CONNECT, completes TLS, negotiates h2 via ALPN, and receives the HTTP/2 GET frame. It then never writes a response. After the timeout it sends GOAWAY error=0 and closes the stream, causing curl to error with (16) Error in the HTTP2 framing layer.
What you are trying to do
Use MockServer as the HTTPS forward proxy for a Playwright-based e2e test suite. The browser (headless Chromium) connects to MockServer via proxy: { server: 'http://mockserver:1080' } and Chromium negotiates h2 with the proxy. Some requests should be served by MockServer expectations (e.g. payment-gateway stubs), others should be forwarded upstream. Both paths fail over HTTP/2.
MockServer version
mockserver/mockserver:5.15.0 upgraded to v6 (same image repo, latest v6 tag). Same behaviour on both — v5 fails on upstream HTTP/2, v6 fails on the proxy-facing HTTP/2 response path.
To Reproduce
- Run MockServer via Docker (minimal repro, no docker-compose needed):
# CA + key for TLS MITM (any CA works; using mkcert here for simplicity)
mkdir -p ./ca
mkcert -install
cp "$(mkcert -CAROOT)/rootCA.pem" ./ca/cert.pem
cp "$(mkcert -CAROOT)/rootCA-key.pem" ./ca/key.pem
# Expectations: one mock for a known host
mkdir -p ./expectations
cat > ./expectations/typekit.json <<'EOF'
[
{
"id": "typekit-stub-css",
"priority": 10,
"httpRequest": {
"method": "GET",
"path": "/mzn8ecb.css",
"headers": { "Host": ["use\\.typekit\\.net"] }
},
"httpResponse": {
"statusCode": 200,
"headers": { "Content-Type": ["text/css;charset=utf-8"] },
"body": "/* mocked */"
}
}
]
EOF
docker run --rm --name mockserver \
-p 1080:1080 \
-v "$PWD/ca:/config/ca:ro" \
-v "$PWD/expectations:/config/expectations:ro" \
-e MOCKSERVER_SERVER_PORT="1080,443" \
-e MOCKSERVER_LOG_LEVEL="INFO" \
-e MOCKSERVER_CERTIFICATE_AUTHORITY_X509_CERTIFICATE="/config/ca/cert.pem" \
-e MOCKSERVER_CERTIFICATE_AUTHORITY_PRIVATE_KEY="/config/ca/key.pem" \
-e MOCKSERVER_INITIALIZATION_JSON_PATH="/config/expectations/*.json" \
mockserver/mockserver:6.0.0 # or 5.15.0
- Reproduce — HTTPS over HTTP/2 (hangs):
curl -kx http://localhost:1080 -v 'https://use.typekit.net/mzn8ecb.css' \
-o /dev/null --max-time 35 2>&1 | tail -20
Output (abridged):
* using HTTP/2
* [HTTP/2] [1] OPENED stream for https://use.typekit.net/mzn8ecb.css
* [HTTP/2] [1] [:method: GET]
* [HTTP/2] [1] [:authority: use.typekit.net]
* [HTTP/2] [1] [:path: /mzn8ecb.css]
> GET /mzn8ecb.css HTTP/2
* Request completely sent off
* received GOAWAY, error=0, last_stream=1 <-- ~29s later
* TLSv1.2 (IN), TLS alert, close notify (256):
curl: (16) Error in the HTTP2 framing layer
- Compare — same request forced to HTTP/1.1 (works):
curl -kx http://localhost:1080 --http1.1 -v 'https://use.typekit.net/mzn8ecb.css' \
-o /dev/null --max-time 35 2>&1 | tail -20
3. Output:
< HTTP/1.1 200 OK
< Content-Type: text/css;charset=utf-8
< content-length: 12
{ [12 bytes data]
/* mocked */
The only variable between the two runs is ALPN negotiating h2 vs http/1.1. The mocked expectation matches in both cases, only HTTP/2 hangs.
Expected behaviour
When the client negotiates h2 via ALPN on the proxy port, MockServer should:
- send the mocked httpResponse back as a valid
HTTP/2 response on the same stream, or
- if no expectation matches and forwarding is desired, complete the forward and relay the upstream response as
HTTP/2.
Either outcome is acceptable; silently hanging and emitting GOAWAY after ~30s is not.
MockServer Log
At MOCKSERVER_LOG_LEVEL=INFO (and at lower levels), no log line is emitted for the HTTP/2 request — neither a "received request" entry nor an "expectation matched" entry.
The mockserver/retrieve?type=LOGS&format=JSON endpoint also returns no record of the request. From the operator's perspective the request silently disappears inside MockServer. The HTTP/1.1 variant of the same request does produce normal INFO-level log entries showing the expectation matching.
(Container startup logs only contain SLF4J init noise:)
SLF4J(W): No SLF4J providers were found.
SLF4J(W): Defaulting to no-operation (NOP) logger implementation
SLF4J(W): See https://www.slf4j.org/codes.html#noProviders for further details.
Loading JavaScript to validate ECMA262 regular expression in JsonSchema ...
The absence of an SLF4J binding in the official image (v6.0.0) is likely a separate packaging issue but is worth flagging here as it makes the HTTP/2 hang impossible to diagnose from container logs alone.
Describe the issue
MockServer hangs for ~30 seconds and then sends a graceful
HTTP/2 GOAWAY (NO_ERROR, last_stream=1)without sending any response, when an HTTPS request is made through the proxy port (1080)over HTTP/2. The same request works correctly when forced to HTTP/1.1.This affects both:
The hang happens after MockServer accepts the
CONNECT, completesTLS, negotiates h2 via ALPN, and receives theHTTP/2 GETframe. It then never writes a response. After the timeout it sendsGOAWAY error=0and closes the stream, causing curl to error with(16) Error in the HTTP2 framing layer.What you are trying to do
Use MockServer as the
HTTPS forward proxyfor a Playwright-based e2e test suite. The browser (headless Chromium) connects to MockServer viaproxy: { server: 'http://mockserver:1080' }and Chromium negotiates h2 with the proxy. Some requests should be served by MockServer expectations (e.g. payment-gateway stubs), others should be forwarded upstream. Both paths fail over HTTP/2.MockServer version
mockserver/mockserver:5.15.0upgraded tov6(same image repo, latest v6 tag). Same behaviour on both — v5 fails on upstreamHTTP/2, v6 fails on theproxy-facing HTTP/2response path.To Reproduce
The only variable between the two runs is ALPN negotiating h2 vs http/1.1. The mocked expectation matches in both cases, only HTTP/2 hangs.
Expected behaviour
When the client negotiates h2 via ALPN on the proxy port, MockServer should:
HTTP/2response on the same stream, orHTTP/2.Either outcome is acceptable; silently hanging and emitting
GOAWAY after ~30sis not.MockServer Log
At
MOCKSERVER_LOG_LEVEL=INFO(and at lower levels), no log line is emitted for theHTTP/2request — neither a "received request" entry nor an "expectation matched" entry.The
mockserver/retrieve?type=LOGS&format=JSONendpoint also returns no record of the request. From the operator's perspective the request silently disappears inside MockServer. TheHTTP/1.1variant of the same request does produce normal INFO-level log entries showing the expectation matching.The absence of an SLF4J binding in the official image (v6.0.0) is likely a separate packaging issue but is worth flagging here as it makes the
HTTP/2hang impossible to diagnose from container logs alone.