Skip to main content
RunBook Academy

Secrets, PKI & CertificatesV · X.509 Certificates in DepthX509

Reading a certificate: the fields an operator actually uses

Intermediate⏱ ~22 min🧪 Lab requiredopenssl

What you'll learn

  • Decode a PEM certificate into its three top-level ASN.1 components and explain which part the signature covers
  • Print subject, issuer, serial and validity in one command and interpret every line of the result
  • Distinguish the certificate stored on disk from the certificate presented in a TLS handshake
  • Recognise when a PEM file holds more than one certificate and read the ones openssl x509 ignores

Prerequisites

None — start here.

Practice

Verified against OpenSSL 3.5.x teaching target; 3.0+ minimum · OpenSSH 10.x teaching target; 8.2+ minimum for certificate workflows · OpenBao 2.6.x · Smallstep step-ca 0.30.x · Certbot / Pebble Certbot current release; Pebble 2.10.x ACME test server · Kubernetes (cross-course target) 1.36.x · PostgreSQL 17.x · 2026-08-26

Not yet marked complete on this device.

An X.509 certificate is a signed statement that a particular public key belongs to a particular identity for a bounded period of time. It holds no secret, it encrypts nothing by itself, and it is safe to publish. What makes it operationally interesting is that a verifier reads roughly eight of its fields and then refuses or accepts a connection on the strength of them. Knowing which eight, and reading them in seconds rather than minutes, is what separates a short incident from a long one.

The shape of the object you are about to read

A certificate is an ASN.1 structure, DER-encoded into bytes and usually stored wrapped in base64 armour. RFC 5280 defines exactly three top-level components, and the names matter because OpenSSL prints them under those names.

Certificate ::= { tbsCertificate, signatureAlgorithm, signatureValue }
TBSCertificate ::= SEQUENCE {
     version [0] EXPLICIT Version DEFAULT v1,   serialNumber, signature,
     issuer, validity, subject, subjectPublicKeyInfo,
     issuerUniqueID  [1] IMPLICIT ... OPTIONAL,
     subjectUniqueID [2] IMPLICIT ... OPTIONAL,
     extensions      [3] EXPLICIT Extensions OPTIONAL }

The abbreviation “tbs” means to-be-signed, and that is the whole trick of the format. The issuing authority hashes the DER encoding of tbsCertificate and signs the hash. Everything a verifier cares about lives inside that structure, so a single flipped byte anywhere in it, including one digit of a validity date, produces a different hash and a signature that no longer verifies. There is no partial trust and no field an operator may quietly edit.

  • serialNumber is the issuer’s own identifier for this certificate. It is unique only within one issuer, never globally.
  • signature inside the body repeats the algorithm identifier that appears again in the outer signatureAlgorithm. A mismatch between the two is a tampering signal.
  • issuer and subject are Distinguished Names, both encoded the same way, which is why an issuer name and a subject name can be compared directly during chain building.
  • validity is a pair of times, notBefore and notAfter, both absolute and both in UTC.
  • subjectPublicKeyInfo carries the algorithm identifier plus the public key bits themselves. This is the field the whole certificate exists to bind.
  • extensions exist only when version is v3. RFC 5280 §4.1.2.9 states the field “MUST only appear if the version is 3”, and every certificate you will meet in production is v3.

That last point explains a small mystery. A certificate with no extensions at all is not a broken v3 certificate, it is a v1 certificate, and everything a v3 profile would have constrained is simply unstated. Ancient self-signed roots occasionally still look like this, and a verifier reading one has no basicConstraints to consult and no purpose to check. It is one of the few situations where an absence of information is more dangerous than a wrong value.

The one command that answers most questions

Four fields answer the majority of first-line certificate questions, and one invocation prints all of them. The -noout flag suppresses the re-encoded certificate body, which is otherwise dumped to your terminal.

CERT=/etc/ssl/certs/app.lab.example.pem
openssl x509 -in "$CERT" -noout -subject -issuer -serial -dates

Run against a leaf certificate issued by a two-tier laboratory CA, that prints:

subject=CN=app.lab.example
issuer=O=RunBook Academy Lab, CN=RunBook Lab Server Issuing CA
serial=21173B360D80F4A69A91164F1067F4F81A1B1B6E
notBefore=Aug 26 21:19:00 2026 GMT
notAfter=Nov 24 21:19:00 2026 GMT

Read it from the bottom up, because that is the order in which the fields usually cause outages. The validity window is absolute UTC, so a host whose clock has drifted will reject a perfectly good certificate, and the symptom is indistinguishable from genuine expiry until you check the clock. The serial is the value you quote to the issuing authority when you ask for revocation or when you match a log line to a specific issuance. The issuer is a name to be matched against the subject of the next certificate up the chain, not a URL and not a hostname. The subject is the field operators most often misuse: it is an identifier for the certificate holder, and for TLS server identity a modern client will not look at it at all.

The notBefore half of the window deserves more attention than it usually gets. A certificate is not usable before that instant, so a freshly issued certificate deployed within seconds of issuance will be rejected by any host whose clock is a few minutes behind the CA. Many issuing authorities set notBefore slightly earlier than the moment of issuance to absorb that skew, but you cannot rely on any particular authority doing so. When you deploy a certificate within moments of minting it and a minority of clients fail while the majority succeed, look at clock skew before you look at anything else.

One field is deliberately absent from that output. The public key sits in subjectPublicKeyInfo, and it is the value the whole certificate exists to bind, but printing it tells you nothing on its own. Its operational use is comparative: derived from the private key it should match, or hashed and pinned so that a renewal does not break a client. Both of those uses come later in this part.

Reading the extensions, and why they come second

The base fields tell you who and when. The extensions tell you what the certificate is permitted to do, and OpenSSL will print any named subset of them.

CERT=/etc/ssl/certs/app.lab.example.pem
openssl x509 -in "$CERT" -noout \
  -ext subjectAltName,keyUsage,extendedKeyUsage,basicConstraints
X509v3 Basic Constraints: critical
    CA:FALSE
X509v3 Key Usage: critical
    Digital Signature, Key Encipherment
X509v3 Extended Key Usage:
    TLS Web Server Authentication
X509v3 Subject Alternative Name:
    DNS:app.lab.example, DNS:www.app.lab.example

Two things in that block deserve attention on a first read. The word critical after an extension name is not decoration: RFC 5280 §4.2 requires a certificate-using system to reject a certificate outright when it meets a critical extension it cannot process, whereas an unrecognised non-critical extension may be ignored. The second is that the names this certificate answers to are listed in the Subject Alternative Name, and the fact that app.lab.example also appears in the subject is a convenience for humans reading the output, not the value a TLS client matches against.

The file on disk is not necessarily the certificate in use

Almost every certificate incident that survives the first ten minutes does so because someone verified the wrong copy. A serving process parses its certificate once, at start or at reload, and holds the parsed structure in memory. Replacing the file underneath a running process changes nothing that a client can observe.

flowchart LR
    A["PEM file on disk"] --> B["Process parses it at start or reload"]
    B --> C["Certificate message in the handshake"]
    C --> D["Client parses and decides"]
    A -- "renewed but never reloaded" --> E["Disk and wire disagree"]

The path from disk to client has one gate on it, and that gate is the reload. A renewal job that writes a perfect new file and never signals the process leaves the old certificate serving traffic until it expires. The only honest way to answer “what is being served” is to ask the listening socket rather than the filesystem.

HOST=app.lab.example
PORT=443
openssl s_client -connect "$HOST:$PORT" -servername "$HOST" </dev/null 2>/dev/null |
  openssl x509 -noout -subject -serial -dates

Compare the serial from that command with the serial from the file. If they differ, you have a reload problem and not a certificate problem, and the fix is a service reload rather than another reissuance. Make this comparison the first step of any expiry investigation, because it costs one command and it eliminates an entire class of wrong answers.

Production discipline

  1. Read the socket before you read the file. The certificate a client receives is the only one that matters. Capture the serial from the wire and from disk, and treat any difference as a deployment fault rather than a certificate fault.
  2. Record the serial in the change ticket. It is the one value that uniquely identifies an issuance to its issuing authority, and it is the value you will need under time pressure if the certificate has to be revoked.
  3. Check the host clock before you believe an expiry. Absolute UTC validity windows mean that clock skew and genuine expiry produce the same client-side error, and only one of them is fixed by reissuing.
  4. Inspect bundles with a tool that reads the whole file. A chain file inspected with openssl x509 reports the leaf and hides the intermediate, which is how missing-intermediate faults survive several rounds of checking.

Cross-course references

  • Linux for Production Sysadmins - Part LXXI (TLS) covers certificate authorities and chains as they appear on a host, which is where the issuer name read in this lesson resolves to an actual file.
  • Observability for Production Sysadmins - Part LXIV (TLSMonitoring) covers expiry gathered as a metric from outside the host, the independent channel this lesson recommends for reading the socket.
  • Kubernetes for Production Sysadmins - Part LXXVI (Certs) covers the cluster PKI whose leaf certificates carry the same fields and are read with the same commands.

Quiz

Knowledge check · 4 questions

  1. Q1. A certificate file was replaced at 02:00 and its on-disk notAfter now reads three months out, yet monitoring still reports the old expiry from the public endpoint. Which explanation fits best?

  2. Q2. The signature on a certificate covers the whole file as it is stored, so converting a certificate from DER to PEM invalidates its signature.

  3. Q3. Which OpenSSL subcommand and flags print the subject, issuer, serial and validity dates of a PEM certificate without re-emitting the encoded certificate body?

  4. Q4. Work out why two engineers looking at the same service disagree about its certificate, and say what you would check first.

    At 09:40 UTC an engineer reports that api.example.com is serving a certificate that expires in eleven days. A second engineer inspects /etc/ssl/certs/api.example.com.pem on the host, reports notAfter three months away, and closes the alert. Twelve days later the service fails for external clients while an on-host health check that uses a Unix socket continues to pass.

Passing score: 75%. Answers are checked in this browser.