<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:

  1. The licence on the wall — your certificate. Customers can see it.
  2. The paperwork showing who licensed you — the chain. Also public, and you must hand it over, because the customer cannot look it up.
  3. The key to the front door — your private key. Nobody sees this. Ever.

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>

Part A · Getting the files right

A1 · Three files, and only three

<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

🎯 Interview questions — The files


A2 · The chain-order trap

<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>