louislam/uptime-kuma · error · Error

Expected TLS alert '${expectedTlsAlert}' but connection succ

Error message

Expected TLS alert '${expectedTlsAlert}' but connection succeeded. The server accepted the connection without requiring a client certificate.

What it means

Thrown by checkTlsAlert (tcp.js:288-337), a monitor mode that VERIFIES mTLS by asserting a server REJECTS an unauthenticated client with a specific RFC 8446 alert (e.g. 'certificate_required'). This branch fires when attemptTlsConnection resolved with success:true — the server accepted the connection instead of demanding a client certificate — so the security property the user is testing for is absent.

Source

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

        }

        const result = await this.attemptTlsConnection(monitor, options, startTime, timeout);

        heartbeat.ping = result.responseTime;

        // Handle TLS info for certificate expiry monitoring
        if (result.tlsInfo && monitor.isEnabledExpiryNotification()) {
            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

View on GitHub (pinned to 6b5ea01557)

Solutions

  1. Confirm the target server actually requires client certificates (e.g. nginx `ssl_verify_client on;`, Apache `SSLVerifyClient require`).
  2. Verify the monitor is pointed at the port/listener that enforces mTLS, not a parallel open endpoint.
  3. Re-deploy or roll back the server config that disabled client-cert verification.
  4. If the server intentionally no longer requires client certs, remove or update the monitor's expected_tls_alert setting.

Example fix

// server side (nginx) — restore mTLS enforcement
// ssl_client_certificate /etc/ssl/ca.pem;
// ssl_verify_client on;
Defensive patterns

Strategy: validation

Validate before calling

// Confirm the server actually requires client certs before relying on the alert check
const { execSync } = require('child_process');
function serverRequiresClientCert(host, port) {
    try {
        // Connect WITHOUT a client cert; openssl returns non-zero with 'alert' if required
        execSync(`echo | openssl s_client -connect ${host}:${port} -servername ${host} 2>&1`, { stdio: 'pipe', timeout: 8000 });
        return /alert.*certificate_required|alert.* handshake_failure/i.test(out);
    } catch (e) {
        return /alert/i.test(String(e.stdout || '') + e.message);
    }
}

Type guard

function isExpectedAlertConfigured(v) {
    return typeof v === 'string' && v.length > 0 && /^[a-z_]+$/.test(v);
}

Try / catch

try {
    await monitor.checkTlsAlert(monitor, heartbeat, expectedTlsAlert);
} catch (e) {
    if (/connection succeeded/i.test(e.message)) {
        // server is NOT enforcing mTLS — escalate as a security finding, not a transient outage
        heartbeat.status = DOWN;
        heartbeat.msg = `mTLS NOT enforced: ${e.message}`;
    } else { throw e; }
}

Prevention

When it happens

Trigger: Monitor configured with an expected_tls_alert (e.g. certificate_required) against a server whose TLS endpoint does not require client certs; the mTLS policy was removed or never enabled server-side; testing against the wrong port (a plain-HTTPS listener rather than the mTLS one).

Common situations: Security/compliance check expecting a service to enforce client certificates, but a reverse proxy was reconfigured to terminate TLS itself and forward plain HTTP upstream; the expected alert was set against a dev environment that has mTLS disabled; nginx env var SSL_VERIFY_CLIENT reset to 'off' during a deploy.

Understand the failure class

Related errors


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