## Intro

Certificate expiry and misconfiguration are among the most common causes of avoidable outages. A certificate that expires at 3 a.m. can take down a production API, a checkout flow, or an entire customer portal. Monitoring TLS certificates and alerting on their health is not optional for teams that value uptime.

This guide is for developers, DevOps engineers, SREs, and technical founders who need a practical way to monitor certificates, set meaningful alerts, and respond to incidents without over-engineering. We'll cover how to inspect certificates with OpenSSL, what metrics to track, how to build a simple dashboard, and what to do when something goes wrong.

Every command in this article is safe to run against your own systems, and we'll emphasize read-only checks, minimal changes, and recovery paths. By the end, you'll have a clear checklist to implement or improve TLS monitoring in your environment.

## Version and Environment Inventory

Before troubleshooting or monitoring, you need to know what you're dealing with. This means capturing the current state of your certificates, the software versions, and the endpoints involved. A common mistake is to run a command that assumes a particular OpenSSL version or certificate format. Always check the basics first.

### Check OpenSSL Version

OpenSSL is the de facto tool for inspecting TLS certificates, and its behavior changes between versions. Run:

openssl version 
 Expected output looks like:

OpenSSL 3.0.2 15 Mar 2022 (Library: OpenSSL 3.0.2 15 Mar 2022) 
 If you're on a system with OpenSSL 1.1.1, some flags like -brief may not be available. Always verify the version before scripting.

### List Certificates on a Server

On a typical Linux server, certificates are often stored under /etc/ssl or /etc/pki . You can list them with:

find /etc/ssl -name "*.pem" -o -name "*.crt" 2>/dev/null 
 This helps you locate the certificate files you need to inspect. Note the paths; you'll use them in the next commands.

### Identify Certificate Subject and Expiry

To inspect a certificate file without modifying it, use:

openssl x509 -in /etc/ssl/certs/example.com.pem -noout -subject -issuer -dates -fingerprint -sha256 
 Sample output:

subject=CN = example.com
issuer=C = US, O = Let's Encrypt, CN = R3
notBefore=Jun 1 00:00:00 2024 GMT
notAfter=Aug 30 00:00:00 2024 GMT
SHA256 Fingerprint=3A:4B:5C:... (full hash) 
 Record these values. They become your baseline for comparison after any change.

### Test a Remote Endpoint

For a live server, use openssl s_client :

echo | openssl s_client -connect example.com:443 -servername example.com -showcerts 2>/dev/null | openssl x509 -noout -dates -subject 
 Expected output:

subject=CN = example.com
notBefore=Jun 1 00:00:00 2024 GMT
notAfter=Aug 30 00:00:00 2024 GMT 
 If the connection fails, check firewall rules or whether the service is listening on port 443.

## Safe Configuration Path

Monitoring should never risk breaking production. The safe path involves:

- Read-only observation : commands that do not change state.

- Non-destructive validation : testing a configuration without applying it.

- Scoped changes : applying only what is necessary, with rollback options.

- Verification after change : confirming the intended result with a check.

### Validate a Certificate Chain Offline

Before deploying a new certificate, validate the chain with:

openssl verify -CAfile /etc/ssl/certs/ca-certificates.crt -untrusted /etc/ssl/certs/intermediate.pem /etc/ssl/certs/example.com.pem 
 Expected output on success:

/etc/ssl/certs/example.com.pem: OK 
 If the chain is incomplete, you'll see an error like error 20 at 0 depth lookup: unable to get local issuer certificate . Fix the chain before applying.

### Test a Web Server Configuration Without Restarting

If using Nginx, check the configuration with:

nginx -t 
 Expected output:

nginx: configuration file /etc/nginx/nginx.conf test is successful 
 Only after this passes should you reload:

systemctl reload nginx 
 Then verify the certificate is served:

echo | openssl s_client -connect localhost:443 -servername example.com 2>/dev/null | openssl x509 -noout -dates 

### Automating Renewal Safely

 If you use Let's Encrypt with Certbot, perform a dry run first:

certbot renew --dry-run 
 This tests renewal without actually issuing certificates. Then schedule the real renewal with cron or systemd timer.

## Verification and Diagnostics

Once a certificate is in place or when you suspect an issue, run a systematic set of verifications.

### Check Expiry Date Remotely

echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -enddate 
 Expected output:

notAfter=Aug 30 00:00:00 2024 GMT 
 Calculate the remaining days with a simple script:

expiry_date=$(echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -enddate | cut -d= -f2)
expiry_epoch=$(date -d "$expiry_date" +%s)
current_epoch=$(date +%s)
days_left=$(( (expiry_epoch - current_epoch) / 86400 ))
echo "$days_left days left" 
 If the number is less than 30, you should be alerting.

### Verify Hostname Match

A certificate is valid only if its subject or SAN matches the requested hostname. Check with:

echo | openssl s_client -connect example.com:443 -servername example.com 2>/dev/null | openssl x509 -noout -text | grep -A1 "Subject Alternative Name" 
 Expected output:

X509v3 Subject Alternative Name:
 DNS:example.com, DNS:www.example.com 
 If the hostname you're connecting to is not listed, clients will get a certificate error.

### Check Certificate Chain Presented by Server

Ensure the server sends the full chain (leaf + intermediate):

echo | openssl s_client -connect example.com:443 -showcerts 2>/dev/null | grep "s:" 
 You should see at least two certificates: the server cert and the intermediate. Missing intermediates cause trust errors on some clients.

### Test TLS Protocol and Cipher

Confirm the server supports TLS 1.2 or 1.3 and strong ciphers:

echo | openssl s_client -connect example.com:443 -tls1_2 2>/dev/null | grep "Protocol\|Cipher" 
 For TLS 1.3:

echo | openssl s_client -connect example.com:443 -tls1_3 2>/dev/null | grep "Protocol\|Cipher" 
 If TLS 1.2 fails but 1.3 works, you may have a compatibility issue for older clients.

## Failure Modes and Recovery

Certificates fail in predictable ways. Understanding these modes helps you respond quickly.

### Expired Certificate

Symptoms : Browsers show "Your connection is not private", APIs return SSL errors, monitoring alerts fire.

Diagnosis : Check the expiry date as shown above. If notAfter is in the past, the certificate is expired.

Recovery :

- Obtain a new certificate from your CA.

- Replace the certificate file on the server.

- Reload the web server:

systemctl reload nginx 

- Verify with openssl s_client that the new expiry date is in the future.

 Prevention : Set alerts at 30, 14, and 7 days before expiry. Automate renewal where possible.

### Hostname Mismatch

Symptoms : Certificate valid but browser complains about name mismatch.

Diagnosis : Compare the requested hostname with the certificate SAN list.

Recovery :

- If using a wildcard certificate, ensure the wildcard covers the subdomain (e.g., *.example.com covers api.example.com ).

- If the certificate is for the wrong domain, issue a new one with correct SANs.

- Update the server configuration to use the correct certificate.

### Incomplete Chain

Symptoms : Some clients (especially mobile) fail to verify, while desktop browsers may work because they cache intermediates.

Diagnosis : Use openssl s_client -showcerts and check that the intermediate is present. Alternatively, use an online SSL checker like SSL Labs.

Recovery :

- Concatenate leaf and intermediate certificates in the correct order (leaf first, then intermediate).

- For Nginx, set ssl_certificate to the full chain file.

- Reload and verify.

### Revoked Certificate

Symptoms : Certificate is within validity but rejected by clients.

Diagnosis : Check revocation status with OCSP:

openssl ocsp -issuer intermediate.pem -cert example.com.pem -url http://ocsp.example.com -text 
 Look for Cert Status: revoked or good .

Recovery : If revoked, immediately replace the certificate. Investigate why it was revoked (compromised key, mis-issuance).

### Private Key Mismatch

Symptoms : Web server fails to start or TLS handshake fails.

Diagnosis : Compare the modulus of the certificate and private key:

openssl x509 -noout -modulus -in example.com.pem | openssl md5
openssl rsa -noout -modulus -in example.com.key | openssl md5 
 If the MD5 hashes differ, they do not match.

Recovery : Obtain the correct key or generate a new key and certificate pair.

## Operations Checklist

Here is a concrete checklist for ongoing TLS certificate operations. Assign a single owner for each item and review quarterly or more frequently.

<div class="my-stack-md overflow-x-auto">
<table class="min-w-[42rem] border-collapse text-left">
<thead><tr><th scope="col" class="border border-outline-variant bg-surface-container-low px-4 py-3 text-left font-label-md font-semibold text-on-surface">#</th><th scope="col" class="border border-outline-variant bg-surface-container-low px-4 py-3 text-left font-label-md font-semibold text-on-surface">Task</th><th scope="col" class="border border-outline-variant bg-surface-container-low px-4 py-3 text-left font-label-md font-semibold text-on-surface">Command / Tool</th><th scope="col" class="border border-outline-variant bg-surface-container-low px-4 py-3 text-left font-label-md font-semibold text-on-surface">Frequency</th><th scope="col" class="border border-outline-variant bg-surface-container-low px-4 py-3 text-left font-label-md font-semibold text-on-surface">Owner</th><th scope="col" class="border border-outline-variant bg-surface-container-low px-4 py-3 text-left font-label-md font-semibold text-on-surface">Expected Result</th></tr></thead>
<tbody></tbody>
</table>
</div>
<div style="overflow-x:auto;margin:1.5rem 0;"><table style="width:100%;border-collapse:collapse;font-size:0.95rem;line-height:1.5;"><tbody><tr><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">1</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Check expiry of all public certs</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">echo | openssl s_client -connect :443 2&gt;/dev/null | openssl x509 -noout -enddate</td></tr><tr><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Daily (automated)</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Priya Shah, DevOps Engineer</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">&gt; 30 days remaining</td></tr><tr><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">2</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Verify SAN matches hostnames</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">openssl x509 -in cert.pem -noout -text | grep -A1 SAN</td></tr><tr><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">On deployment</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Marcus Lee, Backend Lead</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">All hostnames listed</td></tr><tr><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">3</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Test certificate chain completeness</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">openssl s_client -showcerts</td></tr><tr><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Weekly</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Priya Shah</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">At least leaf + intermediate</td></tr><tr><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">4</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Check OCSP revocation status</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">openssl ocsp -issuer intermediate.pem -cert leaf.pem -url http://ocsp.example.com</td></tr><tr><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Monthly</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Priya Shah</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Status: good</td></tr><tr><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">5</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Validate private key matches cert</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">openssl x509 -noout -modulus -in cert.pem | openssl md5 and openssl rsa -noout -modulus -in key.pem | openssl md5</td></tr><tr><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">On key rotation</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Marcus Lee</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">MD5 hashes match</td></tr><tr><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">6</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Test renewal process (dry run)</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">certbot renew --dry-run</td></tr><tr><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Monthly</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Priya Shah</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">No errors</td></tr><tr><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">7</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Monitor TLS protocol version</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">openssl s_client -tls1_2 -connect host:443 and -tls1_3</td></tr><tr><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Quarterly</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Security Team</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">TLS 1.2 and 1.3 enabled</td></tr><tr><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">8</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Review monitoring alerts configuration</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Check alert rules in Prometheus/Alertmanager</td></tr><tr><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Quarterly</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Priya Shah</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Alerts triggered on test</td></tr><tr><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">9</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Update inventory of certificates and owners</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Maintain a spreadsheet or CMDB</td></tr><tr><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Monthly</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Marcus Lee</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">All certs documented</td></tr><tr><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">10</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Run incident response drill for certificate expiry</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Simulate an expired certificate in staging</td></tr><tr><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Annually</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Priya Shah</td><td style="border:1px solid rgba(15,23,42,.14);padding:.75rem;vertical-align:top;">Recovery in under 30 min</td></tr></tbody></table></div>

### Common Pitfalls and How to Avoid Them

- Not setting alerts : Teams often rely on manual checks or memory. Expiry at odd hours goes unnoticed. How to avoid : Implement automated checks with Prometheus Blackbox Exporter or a simple cron job that pushes to a notification channel.

- Testing only in production : Making changes directly on production without testing in staging leads to downtime. How to avoid : Mirror your TLS configuration in a staging environment and test there first.

- Hardcoding certificate paths in scripts : Paths change between servers and environments. How to avoid : Use variables or configuration management (Ansible, Puppet) to manage paths.

- Ignoring intermediate certificates : New developers may upload only the leaf certificate, breaking chain validation. How to avoid : Include the full chain file provided by your CA and verify with openssl verify .

- Not monitoring revocation : A certificate can be valid but revoked. How to avoid : Periodically check OCSP status and configure OCSP stapling on your server.

- Assuming all clients support the latest TLS : Disabling TLS 1.0/1.1 might break legacy clients. How to avoid : Analyze client logs before disabling older protocols, and offer a gradual deprecation plan.

- Not testing renewal process : Automated renewal may fail silently due to changed DNS or firewall rules. How to avoid : Run certbot renew --dry-run monthly and monitor the actual renewal logs.

- Storing private keys in version control : Keys in git repositories are a security risk. How to avoid : Use secret management tools like HashiCorp Vault or environment variables.

## Conclusion

TLS certificate monitoring and alerting is a critical operational practice. By following the steps in this guide, you can:

- Identify the current state of certificates and endpoints.

- Safely inspect and validate certificates without causing disruption.

- Diagnose common failure modes and recover quickly.

- Establish a routine checklist with clear ownership and frequency.

Start with one low-risk verification: run the expiry check on your most important endpoint, set up a cron job to alert you at 30 days, and document the recovery steps. Expand from there. The goal is to make certificate failures visible before they become incidents.

Remember, a reliable TLS monitoring workflow protects your users, your reputation, and your sleep.