DEV Community

Cover image for Why Most SSL Certificate Monitors Are Blind (And How to Fix It)
Serg Petrov
Serg Petrov

Posted on

Why Most SSL Certificate Monitors Are Blind (And How to Fix It)

Google "golang check ssl certificate" and every example looks like this:

conn, _ := tls.Dial("tcp", "example.com:443",
    &tls.Config{InsecureSkipVerify: true})
cert := conn.ConnectionState().PeerCertificates[0]
fmt.Println(cert.NotAfter) // 2026-03-16
Enter fullscreen mode Exit fullscreen mode

53 days left. Great. Ship it.

But your monitor just became blind to 3 out of 4 certificate problems.

The InsecureSkipVerify trap

InsecureSkipVerify: true skips all certificate validation - expiry, trust chain, hostname match. The connection is still encrypted, but Go won't check if the cert is actually valid.

Why do examples use it? Without it, Go refuses to connect if anything is wrong. You get an error instead of certificate details. For a quick script, that's inconvenient.

For monitoring, it's dangerous:

Problem What users see What your monitor shows
Self-signed cert Browser warning "53 days left"
Wrong domain Browser warning "53 days left"
Untrusted CA Browser warning "53 days left"
Expired cert Browser warning "Expired"

Three problems go undetected. Your monitor says everything is fine while users can't access your site.

The fix: two-step check

First, try a normal connection. If it succeeds, the certificate is valid. If it fails, make a second connection with InsecureSkipVerify to get the details anyway.

func checkCert(host string) CertStatus {
    status := CertStatus{Host: host}
    dialer := &net.Dialer{Timeout: 10 * time.Second}

    // Step 1: Try normal connection (validates certificate)
    conn, err := tls.DialWithDialer(dialer, "tcp", host+":443", nil)

    if err == nil {
        // Certificate is valid - get details
        cert := conn.ConnectionState().PeerCertificates[0]
        status.IsValid = true
        status.DaysLeft = int(time.Until(cert.NotAfter).Hours() / 24)
        conn.Close()
        return status
    }

    // Step 2: Validation failed - get details anyway
    status.ValidationError = err.Error()

    conn, err = tls.DialWithDialer(dialer, "tcp", host+":443",
        &tls.Config{InsecureSkipVerify: true})
    if err != nil {
        status.Error = err.Error()
        return status
    }
    defer conn.Close()

    cert := conn.ConnectionState().PeerCertificates[0]
    status.DaysLeft = int(time.Until(cert.NotAfter).Hours() / 24)
    return status
}
Enter fullscreen mode Exit fullscreen mode

Now you see both: validity status AND expiry dates. A self-signed cert with 2 years left is still a problem your users will hit.

Complete monitor

package main

import (
    "crypto/tls"
    "fmt"
    "net"
    "time"
)

type CertStatus struct {
    Host            string
    ExpiresAt       time.Time
    DaysLeft        int
    Issuer          string
    IsValid         bool
    ValidationError string
    Error           string
}

func checkCert(host string) CertStatus {
    status := CertStatus{Host: host}
    dialer := &net.Dialer{Timeout: 10 * time.Second}

    // Step 1: Try normal connection (validates certificate)
    conn, err := tls.DialWithDialer(dialer, "tcp", host+":443", nil)

    if err == nil {
        cert := conn.ConnectionState().PeerCertificates[0]
        status.ExpiresAt = cert.NotAfter
        status.DaysLeft = int(time.Until(cert.NotAfter).Hours() / 24)
        status.Issuer = cert.Issuer.CommonName
        status.IsValid = true
        conn.Close()
        return status
    }

    // Step 2: Validation failed - get details anyway
    status.ValidationError = err.Error()

    conn, err = tls.DialWithDialer(dialer, "tcp", host+":443",
        &tls.Config{InsecureSkipVerify: true})

    if err != nil {
        status.Error = err.Error()
        return status
    }
    defer conn.Close()

    cert := conn.ConnectionState().PeerCertificates[0]
    status.ExpiresAt = cert.NotAfter
    status.DaysLeft = int(time.Until(cert.NotAfter).Hours() / 24)
    status.Issuer = cert.Issuer.CommonName

    return status
}

func main() {
    domains := []string{
        "github.com",
        "google.com",
        "expired.badssl.com",
        "self-signed.badssl.com",
        "wrong.host.badssl.com",
    }

    for _, domain := range domains {
        status := checkCert(domain)

        if status.Error != "" {
            fmt.Printf("ERR  %s: %s\n", domain, status.Error)
            continue
        }

        if !status.IsValid {
            fmt.Printf("FAIL %s: %s\n", domain, status.ValidationError)
            fmt.Printf("     expires: %s\n", status.ExpiresAt.Format("2006-01-02"))
            continue
        }

        switch {
        case status.DaysLeft < 7:
            fmt.Printf("CRIT %s: %d days left\n", domain, status.DaysLeft)
        case status.DaysLeft < 30:
            fmt.Printf("WARN %s: %d days left\n", domain, status.DaysLeft)
        default:
            fmt.Printf("OK   %s: %d days left\n", domain, status.DaysLeft)
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Output:

OK   github.com: 73 days left
OK   google.com: 40 days left
FAIL expired.badssl.com: tls: failed to verify certificate: x509: certificate has expired or is not yet valid: "*.badssl.com" certificate is expired
     expires: 2015-04-12
FAIL self-signed.badssl.com: tls: failed to verify certificate: x509: certificate signed by unknown authority
     expires: 2028-01-20
FAIL wrong.host.badssl.com: tls: failed to verify certificate: x509: certificate is valid for *.badssl.com, badssl.com, not wrong.host.badssl.com
     expires: 2026-04-20
Enter fullscreen mode Exit fullscreen mode

"But I have auto-renewal"

Let's Encrypt auto-renewal fails silently. DNS changed, server migrated, disk full, rate limits hit, nginx didn't reload after renewal. You won't know until users see browser warnings.

Always monitor even with auto-renewal.

Non-obvious gotchas

Wildcard doesn't cover what you think

*.example.com does NOT cover example.com (root) or api.staging.example.com (two levels deep). Monitor them separately.

Different certs behind load balancer

Three servers, each with its own certbot. One renewed, two didn't. 33% of users see errors. You can't reproduce it - you keep hitting the working server.

Monitor from multiple locations or run checks repeatedly.


This is how we monitor certificates at upsonar.io - but the code above works if you prefer to run your own.


How do you monitor your certificates?

Top comments (0)