{"record":{"id":"868f854f11ab9850","repo":"louislam/uptime-kuma","slug":"expected-tls-alert-expectedtlsalert-but-recei","errorCode":null,"errorMessage":"Expected TLS alert '${expectedTlsAlert}' but received '${result.alertName}' (${result.alertNumber})","messagePattern":"Expected TLS alert '(.+?)' but received '(.+?)' \\((.+?)\\)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"server/monitor-types/tcp.js","lineNumber":329,"sourceCode":"        heartbeat.ping = result.responseTime;\n\n        // Handle TLS info for certificate expiry monitoring\n        if (result.tlsInfo && monitor.isEnabledExpiryNotification()) {\n            await monitor.handleTlsInfo(result.tlsInfo);\n        }\n\n        // Check if we got the expected alert\n        // Note: Error messages below could be translated, but alert names (e.g., certificate_required)\n        // are from RFC 8446 spec and should remain in English for consistency with the spec.\n        if (result.alertName === expectedTlsAlert) {\n            heartbeat.status = UP;\n            heartbeat.msg = `TLS alert received as expected: ${result.alertName} (${result.alertNumber})`;\n        } else if (result.success) {\n            throw new Error(\n                `Expected TLS alert '${expectedTlsAlert}' but connection succeeded. The server accepted the connection without requiring a client certificate.`\n            );\n        } else if (result.alertNumber !== null) {\n            throw new Error(\n                `Expected TLS alert '${expectedTlsAlert}' but received '${result.alertName}' (${result.alertNumber})`\n            );\n        } else {\n            throw new Error(\n                `Expected TLS alert '${expectedTlsAlert}' but got unexpected error: ${result.errorMessage}`\n            );\n        }\n    }\n\n    /**\n     * Attempt TLS connection and capture result/alert\n     * @param {object} monitor Monitor object\n     * @param {object} options TLS connection options\n     * @param {number} startTime Connection start timestamp\n     * @param {number} timeout Connection timeout in ms\n     * @returns {Promise<object>} Connection result with success, responseTime, tlsInfo, alertNumber, alertName, errorMessage\n     */\n    attemptTlsConnection(monitor, options, startTime, timeout) {","sourceCodeStart":311,"sourceCodeEnd":347,"githubUrl":"https://github.com/louislam/uptime-kuma/blob/6b5ea0155793e666666745fb8d6fef1e829543a2/server/monitor-types/tcp.js#L311-L347","documentation":"Sibling branch in checkTlsAlert (tcp.js:328-331). attemptTlsConnection failed (success:false) and parseTlsAlertNumber extracted a numeric alert, but its name does not match the expected_tls_alert the user configured. So the server DID send a TLS alert — just not the one the monitor asserts should appear.","triggerScenarios":"Server returns 'handshake_failure' (40) when the user expected 'certificate_required' (116); client offered a cert the server rejected with 'bad_certificate' (10); protocol/cipher mismatch yields 'illegal_parameter' (47) instead of an auth alert.","commonSituations":"The expected_tls_alert was guessed rather than taken from an observed run; server-side TLS policy changed to reject for a different reason; client cert present but expired prompts 'certificate_expired' instead of 'certificate_required'.","solutions":["Compare result.alertName from the heartbeat with expectedTlsAlert — they must match exactly (RFC 8446 snake_case names).","Update the monitor's expected_tls_alert to the alert the server now legitimately returns, OR fix the server so it returns the expected one.","For 'bad_certificate' alerts, supply a valid client cert/key in the monitor (tls_cert/tls_key) so the test reflects the intended scenario.","Run `openssl s_client -connect host:port` locally to see the alert the server emits."],"exampleFix":"// before: monitor.expected_tls_alert = 'certificate_required' but server sends handshake_failure\n// after: align expectation with observed behaviour\nmonitor.expected_tls_alert = 'handshake_failure';","handlingStrategy":"validation","validationCode":"// Capture the real alert the server emits, then align configuration\nfunction captureServerAlert(host, port) {\n    const out = require('child_process').execSync(\n        `openssl s_client -connect ${host}:${port} -servername ${host} -tlsextdebug 2>&1 < /dev/null`,\n        { encoding: 'utf8', timeout: 8000 }\n    );\n    const m = out.match(/alert\\s+(\\d+)[^:]*:\\s*([A-Za-z_]+)/);\n    return m ? { number: +m[1], name: m[2] } : null;\n}","typeGuard":"function isAlertResult(v) {\n    return v != null && typeof v === 'object'\n        && typeof v.alertNumber === 'number'\n        && typeof v.alertName === 'string';\n}","tryCatchPattern":"try {\n    await monitor.checkTlsAlert(monitor, heartbeat, expectedTlsAlert);\n} catch (e) {\n    if (/but received/.test(e.message)) {\n        // extract the received alert and reconcile config or server\n        const received = e.message.match(/received '([a-z_]+)'\\s*\\((\\d+)\\)/);\n        log.warn(`Alert mismatch — expected ${expectedTlsAlert}, got ${received?.[1]} (${received?.[2]})`);\n    }\n    throw e;\n}","preventionTips":["Keep a mapping of RFC 8446 alert numbers to names next to your runbook.","Treat an unexpected alert as a signal to diff server TLS config, not just a monitor failure."],"tags":["tls","mtls","tcp-monitor","alert","certificate"],"backgroundTag":null,"analyzedSha":"6b5ea0155793e666666745fb8d6fef1e829543a2","analyzedAt":"2026-08-12T23:42:12.959Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}