<aside> 🧭
Module 08 · Deploying TLS: NGINX, Apache & the Chain-Order Trap
You can issue a certificate, build a chain, and explain exactly why a client rejected one. This module puts it on a real web server — the files, the config lines, the permissions, and the four mistakes that cause almost every TLS deployment incident.
🧠 concept → 🧪 exercise → ✅ expected result (hidden) → 🎯 interview questions (answers hidden)
Prerequisite: Modules 01–07. You need fullchain from Module 02, chain order from Module 05, the handshake from Module 06, and the failure signatures from Module 07.
</aside>
<aside> 🏪
The picture to hold in your head for this whole module — opening a shop.
To trade legally you need three things on the premises:
Almost every deployment failure in this module is one of two things: you forgot to hand over the paperwork, or you left the key where someone could take it.
</aside>
<aside> 🖥️
About this module. The configuration here is verified against the official nginx and Apache mod_ssl documentation, directive by directive, including defaults.
The file-level exercises — chain order, permissions, verification — run offline with openssl and the certificates from Module 05. You do not need to install nginx to do them, and on a work laptop you should not.
If you want to try the configs for real, use a container:
docker run --rm -it -p 8443:443 -v ~/tls-lab/m05:/certs nginx:alpine sh
Nothing touches your host, and --rm deletes it when you exit.
</aside>
<aside> 📖
Official docs: nginx ssl_certificate / ssl_certificate_key · Apache SSLCertificateFile · RFC 8446 §4.4.2 — the Certificate message
</aside>
| File | Secret? | What it is, and where it came from |
|---|---|---|
fullchain.pem |
No — public | Your certificate, then the intermediates. Handed to every client (Module 05, D1) |
privkey.pem |
YES | The private key. 0600, never leaves the host, never in git (Module 02, A3) |
chain.pem |
No — public | The intermediates alone. Only needed for OCSP stapling (Part D2) |
<aside> 🔑
The root does not appear in that table, and that is deliberate.
You never serve the root. The client either has it or it does not, and a root arriving from a server is ignored either way (Module 05, A1.1). Including it just makes every handshake bigger.
So: leaf + intermediates in one file, key in another, root nowhere.
</aside>
🧪 Exercise A1.1 — Assemble a deployable set from Module 05's lab
cd ~/tls-lab/m05
cat app.crt int/int.crt > fullchain.pem
cp app.key privkey.pem
cp int/int.crt chain.pem
chmod 600 privkey.pem
ls -l fullchain.pem privkey.pem chain.pem
echo "=== what is in each? ==="
for f in fullchain.pem chain.pem; do
printf '%-16s %s certificate(s)\n' "$f" "$(grep -c 'BEGIN CERTIFICATE' $f)"
done
head -1 privkey.pem
<aside> 📖
Official docs: nginx ssl_certificate — chain handling · Apache SSLCertificateFile · RFC 8446 §4.4.2
</aside>
<aside> 📄
The analogy — a stack of papers, stapled in order.
You hand the customer a small stack: your licence on top, then the document that licensed you underneath it.
The web server does not read your stack. It just takes whatever is on top and treats that as your licence, and hands the rest over as supporting paperwork.
So if you staple them the other way round, the server confidently presents the licensing office's document as if it were yours. It is a perfectly genuine document. It is not yours, and it has the wrong name on it.
</aside>