louislam/uptime-kuma · error · Error

Expected TLS alert '${expectedTlsAlert}' but got unexpected

Error message

Expected TLS alert '${expectedTlsAlert}' but got unexpected error: ${result.errorMessage}

What it means

Final else branch of checkTlsAlert (tcp.js:332-335). attemptTlsConnection returned success:false but parseTlsAlertNumber could not extract a numeric alert (alertNumber === null), so the failure is not a recognizable TLS alert — it is a transport/parse-level error surfaced verbatim in result.errorMessage.

Source

Thrown at server/monitor-types/tcp.js:333

            await monitor.handleTlsInfo(result.tlsInfo);
        }

        // Check if we got the expected alert
        // Note: Error messages below could be translated, but alert names (e.g., certificate_required)
        // are from RFC 8446 spec and should remain in English for consistency with the spec.
        if (result.alertName === expectedTlsAlert) {
            heartbeat.status = UP;
            heartbeat.msg = `TLS alert received as expected: ${result.alertName} (${result.alertNumber})`;
        } else if (result.success) {
            throw new Error(
                `Expected TLS alert '${expectedTlsAlert}' but connection succeeded. The server accepted the connection without requiring a client certificate.`
            );
        } else if (result.alertNumber !== null) {
            throw new Error(
                `Expected TLS alert '${expectedTlsAlert}' but received '${result.alertName}' (${result.alertNumber})`
            );
        } else {
            throw new Error(
                `Expected TLS alert '${expectedTlsAlert}' but got unexpected error: ${result.errorMessage}`
            );
        }
    }

    /**
     * Attempt TLS connection and capture result/alert
     * @param {object} monitor Monitor object
     * @param {object} options TLS connection options
     * @param {number} startTime Connection start timestamp
     * @param {number} timeout Connection timeout in ms
     * @returns {Promise<object>} Connection result with success, responseTime, tlsInfo, alertNumber, alertName, errorMessage
     */
    attemptTlsConnection(monitor, options, startTime, timeout) {
        return new Promise((resolve, reject) => {
            const socket = tls.connect(options);

            const timeoutId = setTimeout(() => {

View on GitHub (pinned to 6b5ea01557)

Solutions

  1. Inspect result.errorMessage in the heartbeat for the true cause (e.g. ECONNREFUSED, getaddrinfo ENOTFOUND).
  2. Confirm host/port reachability with `nc -vz host port` or `openssl s_client`.
  3. If the error looks like a TLS alert but no number was parsed, check whether a newer Node version changed the message shape and report/patch parseTlsAlertNumber.
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-flight DNS + TCP so transport errors surface as transport, not as alert-check failures
const dns = require('dns').promises;
const net = require('net');
async function endpointReady(host, port) {
    await dns.lookup(host); // throws on DNS failure
    return new Promise((res) => {
        const s = net.createConnection({ host, port });
        s.setTimeout(5000);
        s.on('connect', () => { s.destroy(); res(true); });
        s.on('error', () => res(false));
        s.on('timeout', () => { s.destroy(); res(false); });
    });
}

Type guard

function isAttemptResult(v) {
    return v != null && typeof v === 'object'
        && typeof v.success === 'boolean'
        && (v.alertNumber === null || typeof v.alertNumber === 'number')
        && (typeof v.errorMessage === 'string' || v.errorMessage == null);
}

Try / catch

try {
    await monitor.checkTlsAlert(monitor, heartbeat, expectedTlsAlert);
} catch (e) {
    if (/got unexpected error/.test(e.message)) {
        const inner = e.message.split('error:')[1]?.trim();
        heartbeat.msg = `Transport-level TLS failure (no alert): ${inner}`;
    }
}

Prevention

When it happens

Trigger: Connection reset before any TLS byte (firewall RST), DNS failure surfaced through tls.connect's error event, ECONNREFUSED, or an error string format Node changed so the regex in parseTlsAlertNumber no longer extracts the number.

Common situations: Host down or port closed; intermediate proxy does not speak TLS on that port; Node.js version upgrade altered the error message format, breaking the alert-number parser.

Understand the failure class

Related errors


AI-assisted analysis of louislam/uptime-kuma@6b5ea01557 (2026-08-12). Data as JSON: /api/errors/aa8c885accfaec5a. Report an issue: GitHub.