Moving a site from HTTP to HTTPS should take two minutes. Add a certificate, flip the redirect, done. That's the story I told myself when I started an HTTP to HTTPS migration on nginx one Monday. By the end of the week I had hit mixed content warnings, a redirect loop, a certificate that expired at 3 AM on a Saturday, and an HSTS preload decision I couldn't undo. Two minutes turned into a two-day education I hadn't planned for.

Here's what actually happens when you do it for real.

The setup looks trivial

Nginx sits at the edge as a reverse proxy. SSL termination happens there. Let's Encrypt issues the certificate. You add a handful of security headers and call it secure. On paper it's four steps. Each one has a gotcha waiting behind it.

  • Certificate chains have to be in the right order. Get it wrong and half your clients trust the cert while the other half throw errors you can't reproduce on your own laptop.
  • Security headers — HSTS, Content-Security-Policy, X-Frame-Options — each one changes browser behavior in ways that only show up in production.
  • Auto-renewal is a cron job you write once and never watch again. That's the trap.

The gotchas that ate my week

Mixed content. The padlock disappeared because one image was still loading over http://. Just one. It was buried in inline CSS, and the browser doesn't tell you which asset — it quietly downgrades your lock icon and logs a warning to a console nobody has open. Mixed content hides in inline styles and third-party scripts you forgot you added.

Redirect loops. I sent HTTP to HTTPS, and something downstream sent it back to HTTP, and the browser spun forever. This is what happens when nginx terminates TLS but the app behind it still thinks it's serving plain HTTP and issues its own redirect. X-Forwarded-Proto exists for exactly this reason, and I learned it the loop-y way.

The expired cert. Not during setup — months later. This is the part almost everyone misses. It's never the initial config that takes you down. It's the second or third renewal, when the cron job silently fails and nobody's checking, because why would you check a thing that worked last time. Your site goes down on a Saturday morning and you're debugging Let's Encrypt at 7 AM in your underwear. Ask me how I know.

HSTS preload is a one-way door

This one scared me the most. HSTS tells browsers "only ever talk to me over HTTPS." The preload list bakes that rule directly into the browser itself. You submit your domain, feel responsible and secure — and then realize there is no undo button. If you break HTTPS after that, your site isn't "shows a scary warning" broken. It's actually unreachable. Browsers refuse to connect at all. Submit that one only when you're genuinely sure.

What "done right" looks like

Once I stopped guessing and started engineering it, the payoff was clean:

  • A+ on SSL Labs — up from the C the default nginx config scores out of the box.
  • 100% HTTPS coverage, zero mixed content warnings, zero browser security errors.
  • The difference between that C and the A+ was about 20 lines of config: TLS 1.2 and up, strong cipher suites, HSTS, OCSP stapling. Same nginx, same certificate — just not the defaults.

And the piece I care about most: a runbook. Every gotcha documented, renewal tested before it actually mattered, monitoring on the cert expiry so the next failure is an alert instead of an outage. The engineer who inherits this won't spend a weekend cursing at certificate chains.

HTTPS is the floor, not the ceiling

"Simple" in infrastructure means "simple if you've already made every mistake once." For everyone else, HTTPS is a system, not a checkbox — certificate management, renewal automation, header hardening, redirect chains, all of it engineered rather than toggled.

HTTPS isn't a feature you ship. It's the bare minimum you owe your users in 2026. And honestly, that gap — treating it as a system instead of a switch — is most of what separates someone who builds websites from someone who builds infrastructure.

What migration sounded simple until you actually started it?