ADR-012: Unified Networking and HTTP/2 Support - #20
Conversation
| - `http2Proxy.enabled: false` by default — users opt-in, no surprises | ||
| - `http2Proxy.enabled: true` by default — HTTP/2 out of the box, but certificate warning unless user provides `route.tls.externalCertificateSecretName` | ||
|
|
||
| If decided to default to `enabled: true`: provide clear documentation that users should supply their own certificate to avoid browser warnings. The performance benefit may justify the default, and the warning serves as a signal to configure properly. |
There was a problem hiding this comment.
If decided to default to
enabled: true: provide clear documentation that users should supply their own certificate to avoid browser warnings. The performance benefit may justify the default, and the warning serves as a signal to configure properly.
The JIRA requirement is clear and this was my understanding as well: Enable HTTP/2 by default and Existing deployments gain HTTP/2 on upgrade without manual configuration changes. But I am concerned here about the TLS requirement. Defaulting to enabled would require users to provide certs and proper TLS configuration; this implies that the "upgrade without manual configuration" isn't really do-able here. We need to raise this.
There was a problem hiding this comment.
yes, that's the point.
If we are not able to avoid manual configuration it seems to be nice but less important feature
Co-authored-by: Armel Soro <armel@rm3l.org>
| - **NGINX Ingress Controller**: HTTP/2 disabled by default, requires cluster-admin to enable via ConfigMap (cluster-wide) | ||
| - **HAProxy**: Depends on configuration | ||
|
|
||
| On both platforms, RHDH users on shared clusters cannot enable HTTP/2 without cluster-admin cooperation. |
There was a problem hiding this comment.
Actually, I think this requires further clarification. Users can force-use HTTP/2 even without cluster-admin cooperation. With curl, you can skip the protocol negotiation (ALPN) and talk directly via HTTP/2, and this works, even on OCP without the cluster-admin annotation:
❯ curl -k --http2-prior-knowledge -v -sS -o /dev/null https://console-openshift-console.apps.bcca4ad0ec252f076ae6.hypershift.aws-2.ci.openshift.org/
* Host console-openshift-console.apps.bcca4ad0ec252f076ae6.hypershift.aws-2.ci.openshift.org:443 was resolved.
* IPv6: (none)
* IPv4: 3.209.242.225, 52.200.71.79
* Trying 3.209.242.225:443...
* ALPN: curl offers h2
} [5 bytes data]
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
} [1635 bytes data]
* SSL Trust: peer verification disabled
{ [5 bytes data]
* TLSv1.3 (IN), TLS handshake, Server hello (2):
{ [1210 bytes data]
* TLSv1.3 (IN), TLS change cipher, Change cipher spec (1):
{ [1 bytes data]
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
{ [10 bytes data]
* TLSv1.3 (IN), TLS handshake, Certificate (11):
{ [1038 bytes data]
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
{ [264 bytes data]
* TLSv1.3 (IN), TLS handshake, Finished (20):
{ [36 bytes data]
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
} [1 bytes data]
* TLSv1.3 (OUT), TLS handshake, Finished (20):
} [36 bytes data]
* SSL connection using TLSv1.3 / TLS_AES_128_GCM_SHA256 / X25519MLKEM768 / RSASSA-PSS
* ALPN: server did not agree on a protocol. Uses default.
* Server certificate:
* subject: O=openshift; CN=openshift-ingress
* start date: Jul 15 07:42:27 2026 GMT
* expire date: Jul 15 07:42:27 2027 GMT
* issuer: OU=openshift; CN=root-ca
* Certificate level 0: Public key type RSA (2048/112 Bits/secBits), signed using sha256WithRSAEncryption
* SSL certificate verification failed, continuing anyway!
* Established connection to console-openshift-console.apps.bcca4ad0ec252f076ae6.hypershift.aws-2.ci.openshift.org (3.209.242.225 port 443) from 192.168.1.27 port 38278
* using HTTP/2
* [HTTP/2] [1] OPENED stream for https://console-openshift-console.apps.bcca4ad0ec252f076ae6.hypershift.aws-2.ci.openshift.org/
* [HTTP/2] [1] [:method: GET]
* [HTTP/2] [1] [:scheme: https]
* [HTTP/2] [1] [:authority: console-openshift-console.apps.bcca4ad0ec252f076ae6.hypershift.aws-2.ci.openshift.org]
* [HTTP/2] [1] [:path: /]
* [HTTP/2] [1] [user-agent: curl/8.18.0]
* [HTTP/2] [1] [accept: */*]
} [5 bytes data]
> GET / HTTP/2
> Host: console-openshift-console.apps.bcca4ad0ec252f076ae6.hypershift.aws-2.ci.openshift.org
> User-Agent: curl/8.18.0
> Accept: */*
>
* Request completely sent off
} [5 bytes data]
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
{ [313 bytes data]
* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4):
{ [313 bytes data]
< HTTP/2 200
< content-security-policy-report-only: base-uri 'self'; default-src 'self' console.redhat.com; img-src 'self' data:; font-src 'self' data:; script-src 'self' console.redhat.com 'unsafe-eval' 'nonce-3qZBYQoZ9PCRu1xyhfPKqbGkYIZZAJQL'; style-src 'self' 'unsafe-inline'; connect-src 'self' console.redhat.com; object-src 'self'; frame-src 'none'; frame-ancestors 'none'
< referrer-policy: strict-origin-when-cross-origin
< set-cookie: csrf-token=sV9OZyeKdRSNZ5jRhkqjiTLM2NuM6Nou3NwxQ0ygH2PNoaPDDaZsPglZP4Akg7HO; Path=/; Secure; SameSite=Strict
< x-content-type-options: nosniff
< x-dns-prefetch-control: off
< x-frame-options: DENY
< date: Wed, 15 Jul 2026 10:28:41 GMT
< content-type: text/html; charset=utf-8
< set-cookie: 1e2670d92730b515ce3a1bb65da45062=8fec6f603c4d482c32379d735fa77536; path=/; HttpOnly; Secure; SameSite=None
<
{ [4131 bytes data]
* Connection #0 to host console-openshift-console.apps.bcca4ad0ec252f076ae6.hypershift.aws-2.ci.openshift.org:443 left intact
The problem is that this doesn't work with browsers, which strictly require a successful ALPN negotiation during the TLS handshake to use HTTP/2 over HTTPS. So they are switching to HTTP/1.1.
I think it is worth mentioning ALPN here as this is central to the browser behaviour.
Claude review suggests that this distinction matters throughout the ADR:
- Platform constraints: "HTTP/2 requires cluster-admin to enable at IngressController level" should say "h2 (HTTP/2 over TLS via ALPN) requires cluster-admin."
- Ingress controllers: "HTTP/2 enabled by default with TLS" for Traefik/Caddy is imprecise. These support both h2 and h2c; the relevant point is that they negotiate h2 via ALPN automatically when TLS is configured.
- The problem statement: The ADR should explicitly state that the bottleneck is about h2 specifically, because browsers only use h2. This helps readers understand why TLS and ALPN are central to the solution, and why h2c (which works today without cluster-admin) doesn't solve the browser performance problem.
Making this distinction explicit would help reviewers evaluate alternatives more clearly. For example, it immediately clarifies why "just enable HTTP/2 on the router" isn't as simple as it sounds, and why the sidecar needs to handle TLS.
There was a problem hiding this comment.
"The problem is that this doesn't work with browsers" - exactly, so what do you propose? To add it as an alternative and reject because it does not work in browser?
| 5. **Default behavior considerations**: | ||
|
|
||
| Enabling `http2Proxy` by default is possible but has UX trade-offs: | ||
| - **Without user-provided certificate**: Uses OpenShift service serving certificates (internal CA) — HTTP/2 works but browsers show certificate warning |
There was a problem hiding this comment.
HTTP/2 works but browsers show certificate warning
Well, for clarity, HTTP/2 won't work until the user explicitly bypasses the certificate error in their browser, as ALPN negotiation happens during the TLS handshake itself.
As currently stated here, it sounds like a minor inconvenience, but in reality, without a user-provided certificate that matches the external RHDH hostname and is signed by a trusted CA (either internal or external to the company), the deployment is broken for browsers in any practical sense. I doubt any enterprise deployment would accept this IMO.
|
|
||
| Enabling `http2Proxy` by default is possible but has UX trade-offs: | ||
| - **Without user-provided certificate**: Uses OpenShift service serving certificates (internal CA) — HTTP/2 works but browsers show certificate warning | ||
| - **With user-provided certificate**: No warnings, full HTTP/2 benefits |
There was a problem hiding this comment.
should also clarify that the certificate should be trusted by browsers and match the RHDH hostname. Otherwise, they could provide a self-signed cert or one for the wrong hostname and would still get the same previous hard error in the browser, I guess.
|
|
||
| **Problem**: RHDH frontend startup time grows significantly with larger plugin counts (40+ plugins). The number of frontend assets (JavaScript bundles, CSS) increases with each plugin, and the Backstage New Frontend System (NFS) further increases asset count. With HTTP/1.1, browsers are limited to ~6 concurrent connections per domain, causing sequential loading and slower page render times. | ||
|
|
||
| HTTP/2 provides significant performance benefits through: |
There was a problem hiding this comment.
We should also clarify that we are talking about HTTP/2 on the public-facing connection segment (i.e, from client to proxy, as highlighted in the POC), not full HTTP/2 end-to-end requiring HTTP/2 also between the proxy/router and the application container (separate concern).
There was a problem hiding this comment.
I guess it is clear from the problem statement, the problem we are solving is "frontend startup time". Propose your addition if needed.
|
|
||
| **Note:** The `http2Proxy` sidecar provides HTTP/2 support without cluster-admin cooperation on both OpenShift and Kubernetes (where NGINX Ingress Controller also requires cluster-admin to enable HTTP/2). | ||
|
|
||
| 5. **Default behavior considerations**: |
There was a problem hiding this comment.
This could be labeled explicitly as an "Open Question"?
| kind: Route | ||
| spec: | ||
| tls: | ||
| termination: passthrough |
There was a problem hiding this comment.
There might be understated consequences to switching from edge to passthrough termination. Could be stated under the consequences section?
From Claude, this is a significant operational change:
- OpenShift no longer manages certificates for the route. With edge termination, the default wildcard cert or the route's configured certificate is managed by the router. With
passthrough, the sidecar owns TLS entirely. - HAProxy-level HTTP headers are lost. The router no longer injects
X-Forwarded-For,X-Forwarded-Proto,X-Forwarded-Port, etc. The sidecar must be configured to add these, or the Backstage application won't see the original client IP and protocol. - Monitoring and logging changes. HAProxy access logs and metrics no longer reflect individual HTTP requests for passthrough routes; they only see TCP connections.
There was a problem hiding this comment.
That's looks like truth :) so what must we do with this information?
|
|
||
| ### Alternative 1: Document cluster-admin HTTP/2 enablement | ||
| - **Approach**: Document how cluster-admins can enable HTTP/2 and require custom certificates per-route | ||
| - **Rejected because**: Users on shared clusters have no control; requires coordination with cluster-admin for each deployment; doesn't solve the core user autonomy problem |
There was a problem hiding this comment.
This is true, but might also be the cleanest solution when cluster-admin cooperation is available: no sidecar, no TLS shift, no extra container..
There was a problem hiding this comment.
It might but does not fit initial requirements (no admin privileges :) )
|
|
||
| If decided to default to `enabled: true`: provide clear documentation that users should supply their own certificate to avoid browser warnings. The performance benefit may justify the default, and the warning serves as a signal to configure properly. | ||
|
|
||
| ## Alternatives Considered |
There was a problem hiding this comment.
Looks there is another possible alternative: Gateway API (GA in OCP and available in vanilla K8s). It seems to rely on Envoy on OCP and h2 works automatically via ALPN without any cluster-admin annotation. However, I've quickly checked on a ROSA 4.21 cluster that while the CRDs are installed, no GatewayClass is deployed by default; so a cluster-admin would still need to set up the Gateway infrastructure first. On vanilla K8s, Gateway API is a standard with multiple implementations but also requires installation. Worth mentioning as a forward-looking alternative with these caveats..
|
|
||
| If decided to default to `enabled: true`: provide clear documentation that users should supply their own certificate to avoid browser warnings. The performance benefit may justify the default, and the warning serves as a signal to configure properly. | ||
|
|
||
| ## Alternatives Considered |
There was a problem hiding this comment.
I thought there was an earlier revision of the ADR mentioning "Node.js native HTTP/2", or did I hallucinate? :)
Might be worth keeping it as alternative with clear reasoning why it is rejected, no?
There was a problem hiding this comment.
Can mention but I do not think it is about proxy so I did not yhink about it
|
|
||
| ## Context | ||
|
|
||
| **Problem**: RHDH frontend startup time grows significantly with larger plugin counts (40+ plugins). The number of frontend assets (JavaScript bundles, CSS) increases with each plugin, and the Backstage New Frontend System (NFS) further increases asset count. With HTTP/1.1, browsers are limited to ~6 concurrent connections per domain, causing sequential loading and slower page render times. |
There was a problem hiding this comment.
With HTTP/1.1, browsers are limited to ~6 concurrent connections per domain, causing sequential loading and slower page render times.
I feel like this could be misleading as it reads like the limit is due to HTTP/1.1. The ~6 concurrent connection limit is a browser-imposed limit, not an HTTP/1.1 protocol constraint. Browsers apply this limit regardless of HTTP version, and the limit is set per domain. With HTTP/2, the connection limit is still there, except that browsers can typically send multiple requests to a same domain over one connection.
There was a problem hiding this comment.
What is misleading?
In a context of proposing HTTP/2 as a workaround we want to bypass the "limitation" of HTTP/1.1 and browser.
Explaining how exactly browser works is out of the scope of this ADR and it is clear IMO that we do not try to bypass browser limitation, we try to use potential of HTTP/2 protocol, no?
|
|
||
| On both platforms, RHDH users on shared clusters cannot enable HTTP/2 without cluster-admin cooperation. | ||
|
|
||
| ## Decision |
There was a problem hiding this comment.
@gazarenkov FYI (also shared during the standup call today), I raised the concerns here in the SOS call yesterday, and we agreed on the following next steps:
- explicitly document the requirements for users to enable HTTP/2; so this won't be enabled by default, but users will be aware of what to do to improve performance if needed.
- explicitly claim support for this setup, by essentially testing it on our supported platforms, as this was a concern raised by the customer in the support case
So I think we may no longer need this ADR for now, as we can "just" test and document the setup that was shared in the POC, so we can claim support for it, without having to update the install methods for now. I'll update the Epics under that Feature to reflect that.
There was a problem hiding this comment.
I think ADR did its work - making it clear that initial requirement about HTTP/2 by default does not seem to be realistic and so we can fallback to the simplest possible HTTP/2 solution to try to workaround the frontend performance problem.
This is kinda cool and new option of ADR cycle.
IMO what we need to do is document here that requirements changed and => new decision made (does not matter if it is related to new functionality or "just" testing and documentation, it is still a decision). Current decision should be moved to another Alternative with corresponding rejection reason.
Co-authored-by: Armel Soro <armel@rm3l.org>
Proposals for: