The most detailed free FDE + DevOps library: 140+ lessons, 70+ labs and 80 long-form articles, in English and Turkish. Start learning →

DevOps Foundations · Module 2: Networking, DNS, HTTP and TLS

TLS Chains, Hostnames and Expiry

TLS failures all look identical in browsers and completely different on the command line. This lesson gives you the three checks so certificate incidents end with the right renewal, reorder or redeploy.

11 min reading

Objectives

  • Verify a certificate chain, hostname and expiry from the command line
  • Explain each common TLS error as a specific failed check
  • Separate an expired certificate from a wrong-hostname from an untrusted chain
  • Run TLS checks against fixture certificates without touching production

Why this matters

Every team has a certificate story: the wildcard renewed everywhere except the one service nobody owned, expiring on a holiday. The outage is never mysterious in retrospect; the chain was verifiable for months. TLS incidents are scheduled incidents that nobody scheduled. A quarterly verification habit and the ability to read the three errors turn them from pages into tickets.

Concepts

A TLS connection passes three independent checks, and each produces its own error. The chain check asks whether an unbroken signature path runs from the server certificate to a root your system trusts: server cert to intermediate to root, each signed by the next. Servers must send their intermediates; when they do not, some clients succeed (they cached the intermediate) and others fail with unable to get local issuer certificate, which is why TLS works in your browser and breaks in the pipeline. The hostname check asks whether the certificate names the host you connected to, via SAN entries, not the outdated CN field: a valid cert for api.example.com fails for internal-api.example.com with a hostname mismatch no renewal can fix. The expiry check asks whether now falls between notBefore and notAfter on every certificate in the path, including the intermediates teams forget exist.

openssl s_client performs all three against any reachable endpoint without changing anything: connect, print the presented chain with -showcerts, and report the verify result code. A return code 0 with Verify return code: 0 is health. Code 20 is an untrusted issuer (missing intermediate or private CA unknown to the trust store). Code 21 is an unverifiable first certificate. Hostname mismatches surface in clients and libraries rather than s_client itself, which is why curl --resolve or a small verify script completes the picture. For private test CAs, point the tool at the CA file explicitly (-CAfile) instead of disabling verification: --insecure in a test becomes --insecure in production through copy and habit.

Expiry deserves its own monitoring because it is the only TLS failure with a known date. Check end dates the way you check disk space: as a number with a threshold, alerted at 30 and 7 days, owned by a named human. Short-lived automated certificates changed the failure mode from yearly surprise to daily automation that either renews silently or breaks loudly; both demand the same check, because automation fails exactly when attention moves elsewhere.

Worked example

Staging clients reject the new internal API with a TLS error. Which check?

$ echo | openssl s_client -connect internal-api:8443 -servername internal-api 2>/dev/null | grep -E "Verify return|subject=|issuer=" Verify return code: 62 (Hostname mismatch) subject=CN = api.example.com issuer=C = US, O = Example, CN = Example Staging CA

Expected reading: code 62 names the hostname check, and the subject proves it: the deployed certificate names api.example.com while clients connect to internal-api. No renewal of this certificate helps; the fix is a certificate whose SAN list includes the real name (or clients using the real name, if that is the intended one). The issuer line adds the second finding for the follow-up: a staging CA, so even the corrected certificate needs the CA in client trust stores. Lab L06 replays all three failure classes against a local test CA and fixture certificates, so the errors become familiar before they page you.

The common wrong move

Disabling verification to unblock a deploy. Every --insecure, verify=False and NODE_TLS_REJECT_UNAUTHORIZED=0 ever committed as temporary has a production descendant, and each one converts every future network attacker into an invisible proxy. Verification failures are the system working; silencing them is the incident. Test environments get test CAs with explicit trust, never disabled checks.

Lab and next step

Lab L06 builds a local test CA, issues fixture certificates with deliberate flaws (wrong name, expired, missing intermediate) and requires the correct diagnosis of each from the command line; no public domain or real DNS account involved. From here the path continues to M03, where connections become programs: shell automation that must fail safely when the network does.

Quick check

An optional 4-question self-check. Answers never leave your device, are not stored, and never count toward any assessment.

Lesson feedback

No published feedback yet.

Log in and complete the lesson to leave feedback.

Exercise

With openssl, print the chain and expiry dates of any public site you use, note the days remaining and the issuer, and state which of the three checks would catch a mismatch if the site moved hosts tomorrow.

Pass criteria

Chain, issuer and expiry dates shown with days remaining computed; the mismatch-catching check named correctly (hostname, not chain or expiry).

Sources

Log in to track progressFree account: stores only your lesson progress and quiz results.