DEV Community

Deep Fix
Deep Fix

Posted on

How to Resolve SSL/TLS Certificate Handshake Failures – Quick DevOps Guide

Introduction

Handshake failures are one of the most common pain points when working with secure services. Whether you're a backend developer, a site reliability engineer, or a DevOps specialist, understanding why a TLS/SSL handshake breaks and how to fix it can save hours of debugging time.

In this article we’ll walk through the most frequent causes, provide step‑by‑step troubleshooting commands, and share ready‑to‑run snippets that you can drop into your CI/CD pipelines.


1. Typical Reasons for a Handshake Failure

Symptom Likely Cause
SSL handshake failed (curl) Expired/invalid server cert, missing intermediate CA
certificate verify failed (Python) Wrong trust store or hostname mismatch
TLSV1_ALERT_PROTOCOL_VERSION Client uses deprecated TLS version
ERR_CERT_COMMON_NAME_INVALID (browser) Hostname does not match the CN/SAN

1.1 Expired or Self‑Signed Certificates

Most clouds rotate certificates automatically, but on‑prem services often rely on manually‑managed certs. An expired cert will cause every client that validates the chain to abort the handshake.

1.2 Missing Intermediate Certificates

If the server presents only the leaf cert, clients that don’t have the intermediate cached will fail the verification step.

1.3 Protocol Mismatch

Older libraries may still default to TLS 1.0/1.1, while modern servers require TLS 1.2+.


2. Quick Verification Commands

# Show the full certificate chain as seen by OpenSSL
openssl s_client -connect api.example.com:443 -servername api.example.com -showcerts

# Curl with verbose output – useful for HTTP APIs
curl -v https://api.example.com

# Python requests – force verification to see the exact error
python - <<'PY'
import requests, urllib3
urllib3.disable_warnings()
try:
    r = requests.get('https://api.example.com', timeout=5)
    print('Status:', r.status_code)
except requests.exceptions.SSLError as e:
    print('SSL error:', e)
PY
Enter fullscreen mode Exit fullscreen mode

These commands reveal whether the server is sending the full chain, which TLS versions are negotiated, and the exact verification error.


3. Step‑by‑Step Troubleshooting

Step 1 – Check the Certificate Dates

openssl x509 -noout -dates -in /path/to/leaf.crt
Enter fullscreen mode Exit fullscreen mode

If the notAfter date is in the past, renew the cert.

Step 2 – Validate the Chain Locally

openssl verify -CAfile /etc/ssl/certs/ca-bundle.crt leaf.crt
Enter fullscreen mode Exit fullscreen mode

A error 20 at 0 depth lookup:unable to get local issuer certificate means the intermediate is missing.

Step 3 – Add Missing Intermediates

Create a bundle:

cat leaf.crt intermediate.crt > fullchain.pem
Enter fullscreen mode Exit fullscreen mode

Configure your server (NGINX, Apache, HAProxy) to use fullchain.pem instead of just leaf.crt.

Step 4 – Enforce Modern TLS Versions

For NGINX:

ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers on;
Enter fullscreen mode Exit fullscreen mode

For Java (system property):

-Djdk.tls.client.protocols=TLSv1.2,TLSv1.3
Enter fullscreen mode Exit fullscreen mode

Step 5 – Update the Trust Store

On Linux you can refresh the CA bundle:

sudo update-ca-certificates
Enter fullscreen mode Exit fullscreen mode

On Windows, import the missing root/intermediate via the MMC snap‑in.


4. Automating the Fix

If you often run into missing intermediates, the following Bash helper bundles the leaf with the correct chain and reloads the service:

#!/usr/bin/env bash
set -euo pipefail
LEAF=$1
INTERMEDIATE_URL="https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem"
INTERMEDIATE=$(mktemp)
curl -sSL "$INTERMEDIATE_URL" -o "$INTERMEDIATE"
cat "$LEAF" "$INTERMEDIATE" > /etc/nginx/ssl/fullchain.pem
systemctl reload nginx
rm -f "$INTERMEDIATE"
Enter fullscreen mode Exit fullscreen mode

You can download the pre‑configured script here: Download the pre‑configured script here.


5. When All Else Fails – Use a Diagnostic Container

Running the same checks inside a minimal container guarantees a clean environment:

FROM alpine:latest
RUN apk add --no-cache openssl curl python3 py3-pip && pip install requests
COPY test.sh /test.sh
CMD ["/bin/sh","/test.sh"]
Enter fullscreen mode Exit fullscreen mode

Build and run:

docker build -t tls‑debug .
 docker run --rm tls‑debug
Enter fullscreen mode Exit fullscreen mode

The output mirrors the host commands but eliminates local CA cache issues.


Conclusion

SSL/TLS handshake failures are rarely mystical – they are usually the result of an expired cert, a missing intermediate, or a protocol mismatch. By systematically verifying the chain, updating trust stores, and enforcing modern TLS versions you can resolve the majority of issues in minutes.

Ready to automate the remediation? Get the complete patch tool: Get the complete patch tool.

For a deeper dive into certificate lifecycle management, check out the full repository fix: Access the full repository fix.

Top comments (0)