TLS Mastery — Part 5: Build Your Own CA — Root → Intermediate → Leaf, CSR & SAN | Shantayya Swami
In Part 4 we made a self-signed certificate — encrypted but untrusted. Now we’ll build trust properly by standing up our own Certificate…
TLS Mastery — Part 5: Build Your Own CA — Root → Intermediate → Leaf, CSR & SAN | Shantayya Swami

In Part 4 we made a self-signed certificate — encrypted but untrusted. Now we’ll build trust properly by standing up our own Certificate Authority and issuing a certificate through a real root → intermediate → leaf chain.
In this part:
- A CA is “just” a key pair + a self-signed certificate
- Build a root CA, an intermediate CA, and a leaf certificate
- Subject Alternative Name (SAN) — why modern browsers require it
- The OpenSSL extension-stripping gotcha and its fix
- Assemble the full chain and verify it
- Install on nginx and trust your CA in the browser
This is the corporate/private-CA build (the one you create yourself). For a public CA, you’d run only the leaf’s key + CSR steps and let them sign. For self-signed, you’d stop at the single cert from Part 4.
A CA is “just” a key pair + a self-signed certificate
A CA holds a public key, a private key, and a certificate — and a root CA’s certificate is self-signed (no one signs the root, because there’s no higher authority). In real life the root rarely signs leaves directly; it signs an intermediate, which does the day-to-day signing. That keeps the precious root key offline and lets a compromised intermediate be revoked without burning the root. So we’ll build all three tiers.
Because OpenSSL needs certificate extensions supplied explicitly at signing time (you’ll see why under “the gotcha” below), we’ll use small extension files.
Step 1 — Root CA (self-signed, the top of trust)
CA:TRUE marks it as a CA; keyCertSign lets it sign other certificates. Keep rootCA.key offline and protected - it's the root of all trust.
Create intermediate.ext:
basicConstraints = critical, CA:TRUE, pathlen:0 keyUsage = critical, keyCertSign, cRLSign
( pathlen:0 means this intermediate can sign leaves but not further sub-CAs.) Sign it with the root:
First the key and the CSR — the request you’d normally send to a CA. It bundles your public key with identity info (Common Name, Org, Country, email):
Never send your private key. A CA only needs the public key inside the CSR. Lose the private key and the certificate is useless.
Subject Alternative Name (SAN) — don’t skip this
Historically the domain went in the Common Name (CN) field. Modern browsers have deprecated CN for hostname matching and now require a Subject Alternative Name (SAN). A certificate with a CN but no SAN is rejected:
“NET::ERR_CERT_COMMON_NAME_INVALID”
SAN lists every name (and IP) the certificate is valid for. Create app.ext - note the SAN and that this is not a CA:
SAN is not a wildcard. A wildcard (`.example.com`) matches subdomains by pattern; SAN is an explicit list of specific names/IPs. (We dig into wildcard depth and SAN strategy across environments in Part 10.)*
The gotcha: extensions get stripped on signing
If you sign the CSR without re-supplying extensions:
…then decode app.crt - the SAN is gone! OpenSSL drops requested extensions during signing. The CSR had SAN, but the issued certificate doesn't, so the browser still rejects it. (This is also why Step 2 used an -extfile for the intermediate's CA:TRUE.)
The fix: pass the extension file at signing time. Sign the leaf with the intermediate (not the root):
(397 days reflects the public-CA maximum for leaves; an internal CA can choose its own policy.) Confirm the SAN survived:
Assemble the full chain and verify
Servers send leaf first, then intermediate(s) — never the root:
What goes where:
fullchain.crt (leaf + intermediate) ---> Web server / Ingress Secret rootCA.crt ---> Client / system trust store
The server gets fullchain.crt + app.key. Clients must trust rootCA.crt; they don't need the intermediate separately - the server sends it.
Install on nginx and trust your CA
Point nginx at the chain and key, then restart:
For testing, map the name to your server in the hosts file ( C:\Windows\System32\drivers\etc\hosts on Windows, then ipconfig /flushdns; /etc/hosts on Linux/macOS):
Browse to https://app.dev.com. You'll still see "Not secure" - the certificate is real and CA-signed, but the browser doesn't trust your root yet. Import rootCA.crt:
Settings → Privacy and security → Security → Manage certificates → Trusted Root Certification Authorities → Import
Refresh — padlock, “Connection is secure”, issued to app.dev.com, signed by your CA.
In organisations, IT distributes the internal root to every managed device automatically, so internal HTTPS “just works” for employees. (We automate all of this with cert-manager in Part 8 and design it across environments in Part 10.)
x509 decoding cheat sheet
A certificate is wrapped between -----BEGIN CERTIFICATE----- and -----END CERTIFICATE----- (both lines required).
Coming up
You can now build a full root → intermediate → leaf chain, issue SAN-correct certificates, dodge the extension-stripping trap, and earn the green padlock.
In Part 6 we cover the real-world bits the tutorials skip: certificate file formats (PEM/DER/PFX), revocation (CRL/OCSP/stapling), Let’s Encrypt + ACME for free automated certs, and HSTS.
Previous: Part 4 — Hands-On OpenSSL: Self-Signed Cert + nginx “ · Next: Part 6 — Formats, Revocation, Let’s Encrypt & HSTS “
Originally published at https://www.shantayyaswami.com on June 13, 2026.
메타데이터
- post_id
- 36b71928eefb
- slug
- tls-mastery-part-5-build-your-own-ca-root-intermediate-leaf-csr-san-shantayya-swami-36b71928eefb
- url
- https://medium.com/@shantayyaswami/tls-mastery-part-5-build-your-own-ca-root-intermediate-leaf-csr-san-shantayya-swami-36b71928eefb
- canonical_url
- https://medium.com/@shantayyaswami/tls-mastery-part-5-build-your-own-ca-root-intermediate-leaf-csr-san-shantayya-swami-36b71928eefb
- author_url
- https://medium.com/@shantayyaswami
- status
- ok
- fetched_at
- 2026-06-21 07:44:09