HTTPS & HTTP/2
Enable TLS and negotiate HTTP/2 automatically, without changing your application code
Your route handlers, middleware, and Context never know or care which transport a request
arrived over. Enabling HTTPS — and getting HTTP/2 for clients that support it — is entirely a
serve() option; nothing about how you write handlers changes.
Why there's no "protocol" option
HTTP/2 is negotiated during the TLS handshake via ALPN (Application-Layer Protocol Negotiation) —
the client and server agree on h2 or http/1.1 before any HTTP data is exchanged. Because of
that, NextRush never exposes a protocol: 'http2' option to choose from. You configure TLS; the
runtime negotiates the rest. A client that doesn't support HTTP/2 falls back to HTTPS/1.1
transparently, with identical framework behavior either way.
Enable it
import { createApp } from '@nextrush/core';
import { serve } from '@nextrush/adapter-node';
import { readFileSync } from 'node:fs';
const app = createApp();
app.use(async (ctx) => ctx.json({ hello: 'world' }));
await serve(app, {
port: 443,
tls: {
cert: readFileSync('certificate.pem'),
key: readFileSync('private-key.pem'),
},
});import { createApp } from '@nextrush/core';
import { serve } from '@nextrush/adapter-bun';
const app = createApp();
app.use(async (ctx) => ctx.json({ hello: 'world' }));
await serve(app, {
port: 443,
tls: {
cert: Bun.file('./certificate.pem'),
key: Bun.file('./private-key.pem'),
},
});Bun does not negotiate HTTP/2 via this option
Verified against Bun.serve() directly: its native tls option serves HTTPS/1.1 only — it
does not negotiate h2 via ALPN. getRuntimeCapabilities().http2 correctly reports false
on Bun. If you need HTTP/2 specifically on Bun today, it isn't available through this
adapter yet; TLS/HTTPS works exactly as shown above.
import { createApp } from '@nextrush/core';
import { serve } from '@nextrush/adapter-deno';
const app = createApp();
app.use(async (ctx) => ctx.json({ hello: 'world' }));
await serve(app, {
port: 443,
tls: {
cert: await Deno.readTextFile('./certificate.pem'),
key: await Deno.readTextFile('./private-key.pem'),
},
});Deno.serve() negotiates HTTP/2 via ALPN automatically once tls is present — no extra
configuration needed.
The tls shape ({ cert, key, ca? }) is identical across Node and Bun; Deno also accepts it as
of this shape's introduction, deprecating its previous flat cert/key options (see the
Deno adapter reference for the migration note if
you're on the old fields).
Query the capability instead of branching on the runtime
If your code needs to know whether the current runtime actually negotiates HTTP/2, query
RuntimeCapabilities — never branch on which runtime you're on:
import { getRuntimeCapabilities } from '@nextrush/runtime';
const { secureServing, http2 } = getRuntimeCapabilities();
if (http2) {
// safe to assume h2 negotiates when tls is configured
}This is the same capability-negotiation model every other runtime-varying behavior in NextRush uses — see Runtime Compatibility for the underlying model.
Generate a local development certificate
For local testing, a self-signed certificate is enough:
openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem \
-days 365 -nodes -subj "/CN=localhost"Browsers and most HTTP clients will warn about the untrusted self-signed cert — that's expected
locally. For a real HTTP/2 client to verify the negotiation, you can also check with curl:
curl -k --http2 -v https://localhost:443/ 2>&1 | grep "using HTTP"Verify HTTP/2 is actually negotiating
Because negotiation happens transparently, a shipped tls config can go unverified — nothing
fails if h2 silently isn't being selected. A quick way to check from Node itself:
import http2 from 'node:http2';
const client = http2.connect('https://localhost:443', { rejectUnauthorized: false });
client.on('connect', () => {
console.log('Negotiated:', client.alpnProtocol); // 'h2' or 'http/1.1'
client.close();
});Production deployment
Terminating TLS inside your own process (as shown above) is one option. Many production deployments instead terminate TLS at a reverse proxy or load balancer (nginx, Caddy, a cloud load balancer) and run NextRush behind it in plain HTTP — both are valid; this guide covers configuring TLS inside the adapter when you do want to terminate it yourself. For platform-specific deployment concerns (Docker, Vercel, Lambda, Cloudflare), see production/deployment.
Edge runtimes
On Cloudflare Workers, Vercel Edge, and other edge platforms, TLS termination and protocol
negotiation are handled entirely by the hosting platform before your code ever runs — there is
no tls option on @nextrush/adapter-edge, and none is needed.