denoland/deno · error · TypeError
ERR_TLS_ALPN_CALLBACK_WITH_PROTOCOLS
ERR_TLS_ALPN_CALLBACK_WITH_PROTOCOLS
Error message
The ALPNCallback and ALPNProtocols TLS options are mutually exclusive
What it means
TLS ALPN can be configured exactly one way: a static ALPNProtocols list, or an ALPNCallback invoked per handshake — never both. initializeTLSOptions() in the http2 polyfill enforces the same rule as Node: if options.ALPNCallback is truthy while options.ALPNProtocols is defined, it throws ERR_TLS_ALPN_CALLBACK_WITH_PROTOCOLS.
Source
Thrown at ext/node/polyfills/http2.ts:5085
// Used only with allowHTTP1
const http1Options = options.http1Options ?? {};
options.Http1IncomingMessage ||= http1Options.IncomingMessage ||
http.IncomingMessage;
options.Http1ServerResponse ||= http1Options.ServerResponse ||
http.ServerResponse;
options.Http2ServerRequest ||= Http2ServerRequest;
options.Http2ServerResponse ||= Http2ServerResponse;
return options;
}
function initializeTLSOptions(options, servername) {
options = initializeOptions(options);
if (options.ALPNCallback) {
if (options.ALPNProtocols !== undefined) {
throw new ERR_TLS_ALPN_CALLBACK_WITH_PROTOCOLS();
}
// rustls does not expose a per-handshake ALPN selection callback, so
// we approximate it by pre-evaluating the user's ALPNCallback once
// and advertising the returned protocol. This only correctly handles
// callbacks that synchronously return a fixed protocol string; richer
// selection (rejecting handshakes via false/undefined, choosing per
// client-offered protocols) is not supported, so reject those shapes
// up-front rather than silently advertising an empty ALPN list.
const selected = options.ALPNCallback({ servername, protocols: [] });
if (typeof selected !== "string" || selected.length === 0) {
throw new ERR_INVALID_ARG_VALUE(
"options.ALPNCallback",
selected,
"must synchronously return a non-empty protocol string; " +
"dynamic per-handshake ALPN selection is not supported",
);
}
options.ALPNProtocols = [selected];View on GitHub (pinned to 9ad36f7a2c)
Solutions
- Keep exactly one of the two options: delete ALPNProtocols if you need the callback, otherwise delete ALPNCallback.
- When merging config sources, explicitly remove the stale key (delete merged.ALPNProtocols or delete merged.ALPNCallback) before passing to createSecureServer.
- In Deno prefer ALPNProtocols — dynamic ALPNCallback behavior is additionally restricted there (see the ERR_INVALID_ARG_VALUE error on ALPNCallback).
Example fix
// before
const server = http2.createSecureServer({
key, cert,
ALPNProtocols: ['h2', 'http/1.1'],
ALPNCallback: (protos) => protos.includes('h2') ? 'h2 : false,
});
// after
const server = http2.createSecureServer({
key, cert,
ALPNProtocols: ['h2', 'http/1.1'],
allowHTTP1: true,
}); Defensive patterns
Strategy: validation
Validate before calling
function assertExclusiveALPN(options) {
if (options?.ALPNCallback && options?.ALPNProtocols !== undefined) {
throw new Error('Use either ALPNCallback or ALPNProtocols, not both');
}
}
assertExclusiveALPN(tlsOptions);
http2.createSecureServer(tlsOptions); Type guard
function hasSingleALPNOption(o) {
return !(o && o.ALPNCallback && o.ALPNProtocols !== undefined);
} Prevention
- Never merge two TLS config sources without checking for ALPNCallback/ALPNProtocols collisions.
- Remember the Deno http2 default: with neither option set it advertises ['h2'] (plus 'http/1.1' when allowHTTP1 is true) — most servers need neither key.
- Grep configs in CI for objects containing both keys.
When it happens
Trigger: http2.createSecureServer() or http2.connect() with an options object containing both keys, e.g. { key, cert, ALPNProtocols: ['h2', 'http/1.1'], ALPNCallback: (protos) => 'h2' }. Also happens when TLS options are spread from an existing https-server config that already set ALPNProtocols and code then adds ALPNCallback.
Common situations: Merging a shared TLS options object (which already has ALPNProtocols) with code that adds ALPNCallback; upgrading a server from a static list to a callback without deleting the old key; copy-pasted TLS snippets combining both.
Understand the failure class
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- Unsupported 'alpnProtocols' option provided. 'h2' and 'http/
- ERR_TLS_ALPN_CALLBACK_WITH_PROTOCOLS
- ERR_TLS_PROTOCOL_VERSION_CONFLICT
- ERR_TLS_INVALID_PROTOCOL_METHOD
- ERR_TLS_INVALID_PROTOCOL_VERSION
AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20).
Data as JSON: /api/errors/5cba7e96ad98019e.
Report an issue: GitHub.