{"record":{"id":"5cba7e96ad98019e","repo":"denoland/deno","slug":"err-tls-alpn-callback-with-protocols-5cba7e","errorCode":"ERR_TLS_ALPN_CALLBACK_WITH_PROTOCOLS","errorMessage":"The ALPNCallback and ALPNProtocols TLS options are mutually exclusive","messagePattern":"The ALPNCallback and ALPNProtocols TLS options are mutually exclusive","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"ext/node/polyfills/http2.ts","lineNumber":5085,"sourceCode":"\n  // Used only with allowHTTP1\n  const http1Options = options.http1Options ?? {};\n  options.Http1IncomingMessage ||= http1Options.IncomingMessage ||\n    http.IncomingMessage;\n  options.Http1ServerResponse ||= http1Options.ServerResponse ||\n    http.ServerResponse;\n\n  options.Http2ServerRequest ||= Http2ServerRequest;\n  options.Http2ServerResponse ||= Http2ServerResponse;\n  return options;\n}\n\nfunction initializeTLSOptions(options, servername) {\n  options = initializeOptions(options);\n\n  if (options.ALPNCallback) {\n    if (options.ALPNProtocols !== undefined) {\n      throw new ERR_TLS_ALPN_CALLBACK_WITH_PROTOCOLS();\n    }\n    // rustls does not expose a per-handshake ALPN selection callback, so\n    // we approximate it by pre-evaluating the user's ALPNCallback once\n    // and advertising the returned protocol. This only correctly handles\n    // callbacks that synchronously return a fixed protocol string; richer\n    // selection (rejecting handshakes via false/undefined, choosing per\n    // client-offered protocols) is not supported, so reject those shapes\n    // up-front rather than silently advertising an empty ALPN list.\n    const selected = options.ALPNCallback({ servername, protocols: [] });\n    if (typeof selected !== \"string\" || selected.length === 0) {\n      throw new ERR_INVALID_ARG_VALUE(\n        \"options.ALPNCallback\",\n        selected,\n        \"must synchronously return a non-empty protocol string; \" +\n          \"dynamic per-handshake ALPN selection is not supported\",\n      );\n    }\n    options.ALPNProtocols = [selected];","sourceCodeStart":5067,"sourceCodeEnd":5103,"githubUrl":"https://github.com/denoland/deno/blob/9ad36f7a2cce60488e6ec52283efb32efddaf93a/ext/node/polyfills/http2.ts#L5067-L5103","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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)."],"exampleFix":"// before\nconst server = http2.createSecureServer({\n  key, cert,\n  ALPNProtocols: ['h2', 'http/1.1'],\n  ALPNCallback: (protos) => protos.includes('h2') ? 'h2 : false,\n});\n\n// after\nconst server = http2.createSecureServer({\n  key, cert,\n  ALPNProtocols: ['h2', 'http/1.1'],\n  allowHTTP1: true,\n});","handlingStrategy":"validation","validationCode":"function assertExclusiveALPN(options) {\n  if (options?.ALPNCallback && options?.ALPNProtocols !== undefined) {\n    throw new Error('Use either ALPNCallback or ALPNProtocols, not both');\n  }\n}\nassertExclusiveALPN(tlsOptions);\nhttp2.createSecureServer(tlsOptions);","typeGuard":"function hasSingleALPNOption(o) {\n  return !(o && o.ALPNCallback && o.ALPNProtocols !== undefined);\n}","tryCatchPattern":null,"preventionTips":["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."],"tags":["http2","tls","alpn","node-compat","options-validation"],"backgroundTag":"tls-alpn-misconfiguration","analyzedSha":"9ad36f7a2cce60488e6ec52283efb32efddaf93a","analyzedAt":"2026-08-20T13:07:44.778Z","contentChangedAt":"2026-08-20T13:07:44.778Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}