HTTP/2 support is often treated as a magic switch, but the reality is that Kubernetes Ingress controllers don’t automatically enable it; you have to explicitly configure your Ingress resources to signal their readiness.
Let’s see what this looks like in practice. Imagine we have a simple Nginx Ingress controller and a backend service my-app running on port 80.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-app-ingress
annotations:
nginx.ingress.kubernetes.io/backend-protocol: "HTTP" # This is the default, but good to be explicit
spec:
ingressClassName: nginx
rules:
- host: myapp.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: my-app
port:
number: 80
This Ingress resource, as is, will only serve HTTP/1.1. To enable HTTP/2, we need to signal to the Ingress controller that the backend service itself is capable of speaking HTTP/2.
The core problem is that Kubernetes Ingress, by design, is an abstraction over a reverse proxy. It doesn’t dictate the protocol between the Ingress controller and the backend pods. The Ingress controller needs a hint.
Here are the common ways to achieve HTTP/2, from most common to less so:
-
Backend Protocol Annotation (Nginx Ingress Controller): This is the most direct and common method for Nginx Ingress. You tell the controller what protocol to use when talking to your backend.
-
Diagnosis: Check the
nginx.ingress.kubernetes.io/backend-protocolannotation on your Ingress resource. If it’sHTTPor absent, it defaults to HTTP/1.1. -
Fix: Change the annotation to
HTTPSorGRPC(if your backend supports gRPC, which implies HTTP/2). If you useHTTPShere, you must also configure TLS on your backend service. The Ingress controller will then speak TLS to your backend, and most TLS implementations will negotiate HTTP/2 if both sides support it.apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: my-app-ingress annotations: nginx.ingress.kubernetes.io/backend-protocol: "HTTPS" # Crucial change spec: ingressClassName: nginx tls: # TLS is now required for the backend connection - hosts: - myapp.example.com secretName: my-app-tls-secret # Secret containing your backend TLS cert/key rules: - host: myapp.example.com http: paths: - path: / pathType: Prefix backend: service: name: my-app port: number: 443 # Your backend must be listening on 443 for TLS -
Why it works: By setting
backend-protocoltoHTTPS, you instruct the Nginx Ingress controller to establish a TLS connection to the backend service on port443. During the TLS handshake, HTTP/2 is negotiated if both the Ingress controller and the backend pods support it. If you’re not actually terminating TLS at the backend, this annotation is a misnomer, but it’s the mechanism Nginx uses to trigger the HTTP/2 negotiation.
-
-
Backend Service TLS Configuration: This is intrinsically linked to the above. If your backend service is configured to accept TLS connections (and it should be if you’re using
HTTPSforbackend-protocol), then the negotiation for HTTP/2 happens as part of the TLS handshake.-
Diagnosis: Ensure your backend
Servicein Kubernetes is pointing to pods that are listening on a TLS port (typically 443) and are configured to serve TLS. Check yourDeploymentorStatefulSetfor the container’sportsand ensurecontainerPortis set to443and that the application within the pod is actually configured to use TLS. -
Fix: Modify your
Serviceto expose port443and ensure yourDeployment/StatefulSethas pods listening on443with TLS enabled.# Example Service for backend TLS apiVersion: v1 kind: Service metadata: name: my-app spec: selector: app: my-app ports: - protocol: TCP port: 443 # Service port targetPort: 443 # Container port -
Why it works: Modern TLS libraries (like OpenSSL, which Nginx uses) support Application-Layer Protocol Negotiation (ALPN) during the handshake. ALPN allows the client (Ingress controller) and server (backend pod) to negotiate which application-layer protocols they will use. If both support HTTP/2 and TLS, it will be preferred over HTTP/1.1.
-
-
Ingress Controller Specific Configuration (Less Common for HTTP/2 Backend): Some Ingress controllers might have global or specific configurations that force HTTP/2 for all backends or specific backends, independent of the
backend-protocolannotation. This is less common for the backend connection but more common for the client-to-ingress connection.- Diagnosis: Consult the specific documentation for your Ingress controller (e.g., Traefik, HAProxy Ingress, Istio Gateway). Look for global configuration options or custom resource definitions (CRDs) that might enable HTTP/2 for egress traffic from the controller.
- Fix: Apply the controller-specific configuration. For example, with Traefik, you might configure
serversTransportin the static configuration. - Why it works: The Ingress controller itself is configured to offer or prefer HTTP/2 when initiating connections to its upstream services.
-
HTTP/2 Proxying in the Application: If you absolutely cannot configure your backend to speak TLS or HTTP/2 directly, you could potentially run an HTTP/2-aware proxy within your Kubernetes pods, in front of your application.
- Diagnosis: Your application pods are only listening on HTTP/1.1, and the Ingress controller is configured to talk HTTP/1.1 to them.
- Fix: Deploy a sidecar proxy (like Envoy or a minimalist Go HTTP/2 proxy) in your pods. Configure the Ingress controller to talk to the sidecar (e.g., via
backend-protocol: HTTPon a local port), and configure the sidecar to talk HTTP/2 to your actual application. This is complex and generally discouraged. - Why it works: The sidecar acts as a protocol translator, accepting HTTP/1.1 from the Ingress controller and then establishing an HTTP/2 connection to the application, or vice-versa.
-
Client-to-Ingress HTTP/2: It’s important to distinguish between client-to-Ingress HTTP/2 and Ingress-to-backend HTTP/2. The former is often enabled by default on the Ingress controller’s public-facing listener if the controller supports it and the client offers it. The latter, which we’ve been discussing, is about the connection from the Ingress controller to your backend services.
- Diagnosis: Use
curl --http2(orcurl -v --http2) against your Ingress’s public endpoint. If it successfully negotiates HTTP/2 and shows performance benefits, your client-to-Ingress is working. The problem is usually the backend connection. - Fix: Ensure your Ingress controller is configured to listen on its public interface with HTTP/2 support enabled (often default for modern controllers like Nginx Ingress). For Nginx Ingress, this is usually controlled by the
http2directive in its configuration, which is often enabled by default. - Why it works: The Ingress controller’s frontend listener advertises HTTP/2 support via ALPN, and clients (browsers,
curl) can then establish an HTTP/2 connection.
- Diagnosis: Use
The most common pitfall is forgetting that nginx.ingress.kubernetes.io/backend-protocol: "HTTPS" requires your backend service and pods to actually be configured for TLS on the port the Ingress controller is trying to reach. If your backend is only listening on HTTP/1.1, setting backend-protocol to HTTPS will cause connection failures, not HTTP/2.
After successfully configuring HTTP/2 to your backend, the next thing you’ll likely encounter is managing the lifecycle of TLS certificates for your backend services, especially if you’re using HTTPS as the backend-protocol.