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
A TLS server can select the ALPN protocol either statically, by listing options.ALPNProtocols, or dynamically, by consulting options.ALPNCallback per connection. Supplying both is ambiguous, so the Server constructor throws ERR_TLS_ALPN_CALLBACK_WITH_PROTOCOLS (ext/node/polyfills/_tls_wrap.js:1385) as soon as it sees a truthy ALPNProtocols together with a truthy ALPNCallback.
Source
Thrown at ext/node/polyfills/_tls_wrap.js:1385
return new Server(options, listener);
}
if (typeof options === "function") {
listener = options;
options = kEmptyObject;
} else if (options == null || typeof options === "object") {
options ??= kEmptyObject;
} else {
throw new ERR_INVALID_ARG_TYPE("options", "object", options);
}
this._contexts = [];
this.requestCert = options.requestCert === true;
this.rejectUnauthorized = options.rejectUnauthorized !== false;
if (options.ALPNProtocols) {
if (options.ALPNCallback) {
throw new ERR_TLS_ALPN_CALLBACK_WITH_PROTOCOLS();
}
convertALPNProtocols(options.ALPNProtocols, this);
}
if (options.sessionTimeout != null) {
validateInt32(
options.sessionTimeout,
"options.sessionTimeout",
0,
);
}
if (options.ticketKeys != null) {
if (!isArrayBufferView(options.ticketKeys)) {
throw new ERR_INVALID_ARG_TYPE(
"options.ticketKeys",
["Buffer", "TypedArray", "DataView"],
options.ticketKeys,View on GitHub (pinned to 89f33cbef2)
Solutions
- Choose one mechanism: delete ALPNProtocols when you provide ALPNCallback, or vice versa
- After merging config objects, assert mutual exclusivity before constructing the server
- Use ALPNProtocols for a fixed protocol list (common case) and reserve ALPNCallback for per-connection logic such as negotiated-protocol audit logging
Example fix
// before
const server = tls.createServer({
...baseOpts, // contains ALPNProtocols
ALPNCallback: pickProto, // also set -> throw
});
// after
const { ALPNProtocols, ...rest } = baseOpts;
const server = tls.createServer({ ...rest, ALPNCallback: pickProto }); Defensive patterns
Strategy: validation
Validate before calling
if (opts.ALPNProtocols && opts.ALPNCallback) delete opts.ALPNProtocols; // choose callback
Type guard
function hasAlpnConflict(o) { return Boolean(o?.ALPNProtocols && o?.ALPNCallback); } Try / catch
try { tls.createServer(opts); } catch (e) { if (e.code === 'ERR_TLS_ALPN_CALLBACK_WITH_PROTOCOLS') { const { ALPNCallback, ...rest } = opts; return tls.createServer(rest); } throw e; } Prevention
- When merging layered TLS configs, delete the key you are overriding instead of overwriting
- Assert ALPN exclusivity in config-loading tests
When it happens
Trigger: tls.createServer({ ALPNProtocols: ['h2', 'http/1.1'], ALPNCallback: (versions) => ... }); merging a base config that already sets ALPNProtocols with a layer that adds ALPNCallback; spreading defaults plus overrides without deleting either key.
Common situations: HTTP/2 + HTTP/1.1 negotiation setups where one layer adds the protocol list and another adds dynamic selection; config spread/merge utilities (defaults deep) that accumulate both keys; upgrading a library version where the callback moved into user config while the list stayed in shared defaults.
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/
- Unexpected 'name' field in options, bench name is already pr
- Unexpected second argument to Deno.bench()
- Unexpected third argument to Deno.bench()
- ${prefix}Linter plugin name must only contain lowercase lett
AI-assisted analysis of denoland/deno@89f33cbef2 (2026-08-16).
Data as JSON: /api/errors/79407ee5e43f9301.
Report an issue: GitHub.