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

  1. Choose one mechanism: delete ALPNProtocols when you provide ALPNCallback, or vice versa
  2. After merging config objects, assert mutual exclusivity before constructing the server
  3. 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 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

Related errors


AI-assisted analysis of denoland/deno@89f33cbef2 (2026-08-16). Data as JSON: /api/errors/79407ee5e43f9301. Report an issue: GitHub.