Secrets, PKI & CertificatesV · X.509 Certificates in DepthX509
Reading a certificate: the fields an operator actually uses
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
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.
serialNumberis the issuer’s own identifier for this certificate. It is unique only within one issuer, never globally.signatureinside the body repeats the algorithm identifier that appears again in the outersignatureAlgorithm. A mismatch between the two is a tampering signal.issuerandsubjectare Distinguished Names, both encoded the same way, which is why an issuer name and a subject name can be compared directly during chain building.validityis a pair of times,notBeforeandnotAfter, both absolute and both in UTC.subjectPublicKeyInfocarries the algorithm identifier plus the public key bits themselves. This is the field the whole certificate exists to bind.extensionsexist only whenversionis 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
- 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.
- 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.
- 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.
- Inspect bundles with a tool that reads the whole file. A chain
file inspected with
openssl x509reports 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
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?
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.
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?
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.