Skip to main content

🔒 SSL/TLS Certificates

That little padlock in the address bar carries a surprising amount of weight: it means no one between your user and your server can read or tamper with the traffic, and that your server is who it claims to be. This lesson explains how that guarantee works — and how to get it for free.

Week 13 · Day 5 (Friday: Production Deployment) · Lecture 2

🎯 Learning Objectives

By the end of this lesson, you will be able to:

  • Explain what TLS provides — encryption, integrity, and identity — and why HTTPS is now mandatory
  • Describe the TLS handshake and how a certificate is verified through the CA chain of trust
  • Obtain a free, auto-renewing certificate with Let's Encrypt and certbot
  • Configure TLS termination at a reverse proxy or load balancer with Nginx
  • Redirect HTTP to HTTPS and enable HSTS to lock in secure connections
  • Choose between single-domain, wildcard, and SAN certificates

Estimated Time: 70 minutes

Practice: Secure a domain end-to-end with certbot and a hardened Nginx config.

In This Lesson

Why HTTPS?

Imagine mailing a bank withdrawal slip on a postcard. Every postal worker along the route can read your account number, and any of them could cross out the amount and write in a bigger one. Plain HTTP is exactly that postcard: it travels in clear text across dozens of routers, Wi-Fi access points, and ISPs, any of which can read or rewrite it.

HTTPS is HTTP inside a sealed, tamper-evident envelope. That envelope is TLS (Transport Layer Security), the modern successor to the older SSL protocol. The names are used interchangeably — "SSL certificate" is the common phrase — but every secure connection today speaks TLS, not the long-deprecated SSL.

Since 2018, browsers mark plain HTTP pages as "Not Secure." Beyond trust, HTTPS is now a hard requirement for modern web features — service workers, HTTP/2, the geolocation and camera APIs — and it's a Google ranking signal. There is no longer a reason to run a public site without it.

📖 SSL vs TLS in one line

SSL 2.0/3.0 and TLS 1.0/1.1 are all deprecated and insecure. Use TLS 1.2 as a minimum and TLS 1.3 as the preferred protocol — 1.3 has a faster handshake and drops every weak option.

What TLS Actually Does

TLS delivers three distinct guarantees. It helps to keep them separate, because a certificate is only responsible for one of them.

GuaranteeMeansProvided by
EncryptionEavesdroppers see only scrambled bytesA shared session key agreed during the handshake
IntegrityData can't be altered in transit undetectedMessage authentication codes on every record
IdentityYou're talking to the real server, not an impostorThe certificate, signed by a trusted CA

This is the key insight: encryption alone is not enough. Anyone can generate an encryption key. Without identity, you might be exchanging perfectly encrypted messages with an attacker sitting in the middle. The certificate is what binds a public key to a verified domain name, so identity and encryption together defeat the "man in the middle."

A certificate binds a domain identity to a public key, enabling encrypted and tamper-proof traffic 🔐 Encryption nobody can read the traffic 🛡️ Integrity nobody can alter the traffic 🪪 Identity the server is who it claims
TLS provides all three. The certificate supplies identity; the handshake bootstraps encryption and integrity from it.

The TLS Handshake

Before the first byte of your web page moves, the browser and server run a quick negotiation called the handshake. Its job: verify the server's identity and agree on a secret key that only the two of them know.

sequenceDiagram participant B as Browser participant S as Server B->>S: Client Hello with supported TLS versions and ciphers S->>B: Server Hello with chosen cipher plus the certificate B->>B: Verify the certificate against trusted CAs B->>S: Agree on a shared secret using the server public key S->>B: Confirm and switch to encrypted records B->>S: All further traffic is encrypted

Walking through it: the browser lists what it supports, the server picks the strongest common option and presents its certificate, the browser checks that certificate is trustworthy (next section), and then both sides derive a fresh session key. From that point every HTTP request and response is encrypted with that key. TLS 1.3 compresses this into a single round trip, so the security cost is milliseconds.

💡 Asymmetric to symmetric

The certificate's public/private key pair (slow, asymmetric) is used only to safely agree on a session key. The bulk data is then encrypted with that session key (fast, symmetric). You get the trust of public-key crypto with the speed of symmetric crypto.

Certificates & the CA Chain

How does your browser decide a certificate is legitimate? It doesn't trust your server directly — it trusts a small set of Certificate Authorities (CAs) whose root certificates ship with the operating system and browser. Your certificate is trusted because a CA signed it, forming a chain up to one of those pre-trusted roots.

graph TD A["Root CA
(in the OS/browser trust store)"] --> B["Intermediate CA"] B --> C["Your certificate
example.com"] C --> D["Browser verifies the
signature up the chain"] D --> E{"Chains to a
trusted root?"} E -->|Yes| F["Show the padlock"] E -->|No| G["Show a security warning"]

Each link is a cryptographic signature: the intermediate signs your certificate, the root signs the intermediate. If the browser can follow the signatures all the way to a root it already trusts — and the domain matches, and it hasn't expired — the padlock appears. Break any link (a missing intermediate, wrong domain, expired date) and the user sees a warning.

⚠️ Always serve the full chain

The most common "works in my browser but not for some users" bug is a missing intermediate certificate. Serve fullchain.pem (your cert plus the intermediates), not just cert.pem. Let's Encrypt gives you fullchain.pem for exactly this reason.

What's inside a certificate

  • Subject — the domain(s) it's valid for (the Common Name and Subject Alternative Names)
  • Public key — used during the handshake
  • Issuer — which CA signed it
  • Validity period — not-before and not-after dates
  • Signature — the CA's cryptographic stamp of approval
# Inspect the certificate a live server is serving
echo | openssl s_client -servername example.com -connect example.com:443 2>/dev/null \
  | openssl x509 -noout -subject -issuer -dates

Let's Encrypt & certbot

Not long ago, certificates cost money and were installed by hand once a year. Let's Encrypt, a non-profit CA launched in 2016, changed the web by issuing certificates that are free, automated, and open. It now secures hundreds of millions of domains.

The magic is the ACME protocol (Automated Certificate Management Environment). Instead of a human proving domain ownership, a program does it: it proves you control the domain, and the CA issues a certificate — all in seconds, all repeatable.

sequenceDiagram participant C as certbot participant LE as Let's Encrypt C->>LE: Request a certificate for example.com LE->>C: Prove you control the domain with this challenge C->>C: Place the challenge token where Let's Encrypt can reach it LE->>C: Verification succeeded LE->>C: Here is your certificate valid for ninety days C->>C: Install it and schedule automatic renewal

Domain validation challenges

  • HTTP-01: certbot places a token at /.well-known/acme-challenge/ and Let's Encrypt fetches it over port 80. Simplest for a single server.
  • DNS-01: certbot creates a TXT record on your domain. Required for wildcard certificates and works even when the server isn't publicly reachable.

Getting a certificate with certbot

# Install certbot and the Nginx plugin (Ubuntu/Debian)
sudo apt update
sudo apt install certbot python3-certbot-nginx

# Obtain a certificate AND auto-configure Nginx to use it
sudo certbot --nginx -d example.com -d www.example.com

# Or just fetch certificates without touching server config
sudo certbot certonly --nginx -d example.com

# Wildcard certificate (requires the DNS challenge)
sudo certbot certonly --manual --preferred-challenges dns \
  -d example.com -d '*.example.com'

✅ Auto-renewal is the whole point

Let's Encrypt certificates last only 90 days — deliberately, to force automation. certbot installs a systemd timer (or cron job) that renews them well before expiry. Verify it works with a dry run:

# Simulate a renewal without actually issuing a new cert
sudo certbot renew --dry-run

# Check the timer is active
systemctl status certbot.timer

Set this up once and certificate expiry — a classic cause of outages — simply stops being your problem.

TLS Termination & Nginx

Where does encryption get decrypted? In almost every real deployment, not in your Node.js app. Instead a reverse proxy or load balancer sits in front, decrypts TLS, and forwards plain HTTP to your app over a trusted internal network. This is called TLS termination.

graph LR A["Browser"] -->|"HTTPS (encrypted)"| B["Nginx / Load Balancer
TLS termination"] B -->|"HTTP (internal, trusted)"| C["Node.js app"] C --> D["Database"]

Terminating at the proxy means your app doesn't manage certificates, the proxy handles TLS efficiently for every backend, and you can scale app instances freely behind one certificate. Managed platforms (Render, Vercel, AWS ALB, Cloudflare) do this for you automatically — you often never touch a .pem file.

Nginx TLS configuration

# /etc/nginx/sites-available/example.com
server {
    listen 443 ssl;
    http2 on;
    server_name example.com www.example.com;

    # Certificate paths from certbot
    ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    # Modern, secure protocols only
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers off;      # let TLS 1.3 pick; modern guidance
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 1d;

    # Forward to the Node app over plain HTTP on the internal network
    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;   # so the app knows it was HTTPS
    }
}

⚠️ Protect the private key

The privkey.pem file is the crown jewel — anyone with it can impersonate your site. Keep it readable only by root (chmod 600), never commit it, and never copy it into a Docker image or a chat message.

Redirects, HSTS & Cert Types

Redirect HTTP to HTTPS

Serving HTTPS isn't enough if users can still reach the insecure version. Redirect every HTTP request to HTTPS with a permanent 301.

# A second server block catches plain HTTP and bounces it to HTTPS
server {
    listen 80;
    server_name example.com www.example.com;
    return 301 https://$host$request_uri;
}

HSTS — remove the insecure window

Even a redirect has a gap: the very first request travels over HTTP and could be hijacked. HSTS (HTTP Strict Transport Security) closes it. Once a browser sees this header, it refuses to talk to your domain over HTTP at all — for the entire max-age.

# Add inside the HTTPS server block. Two years, including subdomains.
add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;

⚠️ HSTS is a commitment

While the header is cached, browsers cannot reach your domain over HTTP — there's no override. Only add includeSubDomains and preload once every subdomain is HTTPS-ready. Start with a short max-age, confirm everything works, then raise it.

Choosing a certificate shape

TypeCoversUse when
Single-domainexample.comOne hostname, simplest case
SAN / multi-domainexample.com, example.org, …A few specific hostnames on one cert
Wildcard*.example.comMany subdomains (app, api, blog)

A wildcard covers only one level (api.example.com, not v1.api.example.com) and requires the DNS-01 challenge to issue. For most projects, a single cert listing example.com and www.example.com via SAN is exactly right.

💡 DV, OV, EV

Certificates also differ by validation level: Domain Validation (DV, proves domain control — what Let's Encrypt issues), Organization Validation, and Extended Validation. Modern browsers no longer show special UI for OV/EV, so for the vast majority of apps a free DV certificate is all you need.

Practice & Quiz

🏋️ Exercise 1: HTTP to HTTPS redirect

Goal: Write the Nginx server block that listens on port 80 for myapp.com and permanently redirects every request to the HTTPS version, preserving the path.

💡 Hint

Use listen 80;, a server_name, and return 301 with the $host and $request_uri variables so the full path is carried over.

✅ Solution
server {
    listen 80;
    server_name myapp.com www.myapp.com;
    return 301 https://$host$request_uri;
}

🏋️ Exercise 2: Issue a wildcard certificate

Goal: Write the certbot command to obtain a certificate covering both shop.io and every one of its subdomains, and name the challenge type it requires.

✅ Solution
# Wildcards require the DNS-01 challenge (a TXT record proves control)
sudo certbot certonly --manual --preferred-challenges dns \
  -d shop.io -d '*.shop.io'

Quote '*.shop.io' so the shell doesn't expand the asterisk.

🎯 Quick Quiz

Question 1: A certificate primarily proves which TLS guarantee?

Question 2: Why do Let's Encrypt certificates expire after only 90 days?

Question 3: Which file should Nginx's ssl_certificate point to?

Best Practices & Pitfalls

✅ Do

  • Use free, auto-renewing Let's Encrypt certificates and verify the renewal timer
  • Serve fullchain.pem so the full CA chain validates for everyone
  • Restrict to TLS 1.2 and 1.3; disable everything older
  • Redirect HTTP → HTTPS and add HSTS once you're confident
  • Terminate TLS at a proxy/load balancer and keep the private key at chmod 600

❌ Don't

  • Commit private keys to git or copy them into container images
  • Let renewal be a manual calendar reminder (it will be missed)
  • Enable HSTS preload before every subdomain is HTTPS-ready
  • Serve mixed content — HTTP assets on an HTTPS page get blocked

✅ Verify your setup

After configuring TLS, grade it with the free Qualys SSL Labs Server Test. Aim for an A. It flags weak protocols, missing intermediates, and expiry problems before your users hit them.

Summary

🎉 Key Takeaways

  • TLS provides encryption, integrity, and identity; the certificate supplies identity
  • The handshake verifies the cert, then derives a fast symmetric session key
  • Trust flows through the CA chain up to a root in the browser's trust store — serve fullchain.pem
  • Let's Encrypt + certbot give free, 90-day, auto-renewing certificates
  • Terminate TLS at a proxy, redirect HTTP → HTTPS, and lock it in with HSTS

📚 Additional Resources

🚀 What's Next?

Your certificate is issued for a domain name — but a certificate is useless if that name doesn't point at your server. The next lesson covers Domain Configuration: DNS records (A, AAAA, CNAME, TXT, MX), apex vs www, TTL and propagation, and pointing your domain at a host or load balancer.

🔒 Padlock earned

You understand what that lock icon promises and how to deliver it — for free, on autopilot. Your users' data is safe in transit.