Enabling and Validating HTTP/2 in a Ballerina Service
Enable HTTP/2 in a Ballerina service by configuring TLS, setting the server protocol to http2, and verifying negotiation with curl and runtime logs. The guide covers prerequisites, step‑by‑step configuration, expected checks, and fallback handling.
17 Sept 2026, 06:23 UTC

Problem & Takeaway
Deploying a Ballerina service that can serve requests over HTTP/2 improves latency and throughput, but the upgrade must be explicit and verified. The key takeaway: enable HTTP/2 by configuring the server protocol list, providing a TLS certificate, and then confirm the negotiation with a modern client and runtime logs.
Desired Outcome
After following this guide the service will:
- Accept TLS connections and advertise HTTP/2 via ALPN.
- Automatically negotiate HTTP/2 when a client supports it.
- Gracefully fall back to HTTP/1.1 for clients that do not advertise HTTP/2.
- Log the negotiated protocol for audit and debugging.
Prerequisites
- Ballerina distribution 2201.5.0 or newer (HTTP/2 support introduced in 2201.5.0).
- Valid TLS certificate and private key (or a Java KeyStore containing them).
- Administrative privileges to edit
ballerina.tomlor the runtime configuration file. - Access to the Ballerina runtime logs (default location:
~/.ballerina/logs/ballerina.log). - Client capable of HTTP/2 (e.g.,
curl --http2, modern browsers).
Configuration Steps
- Prepare TLS material
Generate a self‑signed certificate for local testing:
openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 -nodes -subj "/CN=localhost"Convert to a Java KeyStore (JKS) if the runtime is configured to use JKS:
openssl pkcs12 -export -in cert.pem -inkey key.pem -out keystore.p12 -name ballerina -CAfile cert.pem -caname root keytool -importkeystore -deststorepass changeit -destkeypass changeit -destkeystore keystore.jks -srckeystore keystore.p12 -srcstoretype PKCS12 -srcstorepass changeit -alias ballerina - Update the service configuration
Open
ballerina.toml(or the specific server config file) and add the following section:[http] protocols = ["http2"] host = "0.0.0.0" port = 9443 [security] keyStore = "path/to/keystore.jks" keyStorePassword = "changeit" keyStoreAlias = "ballerina"Explanation of fields:
protocols– explicitly lists supported protocols; listing only"http2"forces the runtime to advertise HTTP/2 via ALPN.keyStore– path to the JKS containing the certificate/private key.keyStorePassword– password protecting the JKS.keyStoreAlias– alias of the key entry to use for TLS.
- Restart the Ballerina runtime
Run:
ballerina run yourService.balEnsure the process has read access to the keystore file.
- Verify HTTP/2 negotiation
From a terminal on the same host, execute:
curl -v --http2 https://localhost:9443/yourServicePathLook for the line:
* Trying 127.0.0.1:9443... * Connected to localhost (127.0.0.1) port 9443 (#0) * ALPN, offering http/1.1 * ALPN, offering h2 * SSL connection using TLSv1.3 / ECDHE_RSA_AES_128_GCM_SHA256 * server certificate: localhost * server certificate verification OK * TLSv1.3 (OUT), TLS handshake, Client Hello (1): * TLSv1.3 (IN), TLS handshake, Server Hello (2): * TLSv1.3 (IN), TLS change cipher spec, Change Cipher Spec (1): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (2): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (2): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (2): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (2): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (2): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (2): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (2): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (2): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (2): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (2): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (2): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (2): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (2): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (2): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (2): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (2): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (2): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (2): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (2): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (2): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (2): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (2): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (2): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (2): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (2): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (2): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (2): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (2): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (2): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (2): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (2): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (2): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (2): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (2): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (2): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (2): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (2): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (2): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (2): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (2): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (2): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (2): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (2): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (2): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (2): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (2): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (2): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (2): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (2): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (2): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (3): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (3): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (3): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (3): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (3): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (3): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (3): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (3): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (3): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (3): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (3): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (3): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (3): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (3): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (3): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (3): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (3): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (3): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (3): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (3): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (3): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (3): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (3): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (3): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (3): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (3): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (3): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (3): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (3): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (3): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (3): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (3): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (3): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (3): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (3): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (3): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (3): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (3): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (3): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (3): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (3): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (3): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (3): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (3): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (3): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (3): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (3): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (3): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (3): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (3): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (3): * TLSv1.3 (IN), TLS handshake, Encrypted Handshake Message (3): * * HTTP 2.0In the output, the line
* HTTP 2.0confirms the runtime negotiated HTTP/2. - Check runtime logs
Open the log file and search for a line similar to:
2026-10-10 22:23:47.235 INFO Ballerina HTTP Server: Negotiated HTTP/2 for connection from 127.0.0.1:56789Presence of this entry indicates the server selected HTTP/2 for that session.
- Test fallback behavior
Run the same curl command without the
--http2flag:curl -v https://localhost:9443/yourServicePathThe response should show
* HTTP 1.1, confirming graceful fallback. The log will contain:Negotiated HTTP/1.1 for connection from 127.0.0.1:56790
Expected Checks
- Client output contains
HTTP 2.0when--http2is used. - Runtime log shows
Negotiated HTTP/2for the connection. - When the client omits the ALPN hint, the server logs
Negotiated HTTP/1.1and the response header reflects HTTP/1.1.
Recovery & Troubleshooting
- No HTTP/2 negotiation – Verify the
protocolsarray contains"http2"and that the TLS keystore is correctly referenced. - Runtime fails to start – Check file permissions on the keystore; ensure the password matches the
keyStorePasswordentry. - ALPN not forwarded by a proxy – If a TLS‑terminating load balancer or reverse proxy sits in front of the Ballerina service, it must forward the ALPN extension to the backend. Configure the proxy accordingly or place Ballerina behind the proxy.
- Older client shows HTTP/1.1 only – This is expected; the service will not downgrade to a lower protocol than the client advertises.
Limitations
- HTTP/2 requires TLS; without a valid certificate the server will fall back to HTTP/1.1.
- Only clients that advertise HTTP/2 via ALPN can trigger the upgrade.
- Proxies that terminate TLS must preserve ALPN; otherwise the backend will be unaware of the client’s capability.
Practical Checklist
| Step | Verification |
|---|---|
| TLS material present and referenced | File exists, correct permissions, keyStorePassword matches |
| Server protocols include http2 | Check ballerina.toml for protocols = ["http2"] |
| Runtime started without errors | Check startup logs for TLS handshake success |
| Client test with --http2 shows HTTP 2.0 | curl output contains HTTP 2.0 |
| Runtime log shows Negotiated HTTP/2 | Search log file for Negotiated HTTP/2 |
| Fallback test shows HTTP 1.1 | curl output contains HTTP 1.1 and log contains Negotiated HTTP/1.1 |
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.