How One Security Setting Broke My Website During Testing


When I first started deploying my own applications on a VPS, I thought SSL certificates were the complicated part.

I was wrong.

One of the strangest issues I faced came from a security feature that was actually working exactly as designed.

The feature was called HSTS.

And it completely broke my testing workflow for a while.

Everything Was Working Fine

I had:

  • A VPS running Ubuntu

  • Node.js backend

  • PM2 managing processes

  • Nginx configured

  • My application running correctly

While testing, I was opening the application directly through the VPS IP address.

Something like:

http://123.123.123.123:7001

This was convenient because the domain was not fully configured yet.

I could quickly check whether the backend was running.

Everything looked normal.

Until suddenly the browser stopped opening the application.

Instead, I started getting:

ERR_SSL_PROTOCOL_ERROR

At first, I assumed:

  • Nginx was broken

  • PM2 crashed

  • SSL certificate failed

  • Firewall issue

  • Browser cache issue

I checked almost everything.

Nothing seemed wrong.

The Hidden Culprit: HSTS

After a lot of debugging with AI tools, I learned about a security header called:

Strict-Transport-Security

This is commonly called HSTS.

Its purpose is simple:

When a browser receives this header, it remembers:

"Always use HTTPS for this website in the future."

This is actually a very good security feature.

It protects users from:

  • accidentally using HTTP

  • downgrade attacks

  • insecure connections

For a real production website, this is recommended.

Why The Problem Happened

The issue was not HSTS itself.

The issue was when I enabled it.

At that time, I was still testing using the VPS IP address:

http://123.123.123.123:7001

The browser received the HSTS instruction and remembered:

"This address must always use HTTPS."

From that point forward, the browser automatically started trying:

https://123.123.123.123:7001

But there was a problem.

Port 7001 was just my Node.js application.

It was serving plain HTTP.

There was no SSL certificate attached to that port.

So the browser attempted HTTPS.

The server responded with plain HTTP.

And everything failed.

Result:

ERR_SSL_PROTOCOL_ERROR

The Confusing Part

The application itself was still running.

PM2 was healthy.

The backend process was healthy.

The port was open.

Nothing had actually crashed.

The browser was simply refusing to communicate the way I expected.

That made the issue extremely confusing to diagnose.

How We Fixed It

The solution turned out to be simple.

During testing, HSTS was disabled.

The application only sends HSTS headers when:

ENABLE_HSTS=true

During development and IP-based testing:

ENABLE_HSTS=false

This prevents browsers from permanently forcing HTTPS while the application is still being tested.

The Correct Production Flow

Once everything was ready, I followed the proper deployment sequence:

Step 1

Configure domain:

yourdomain.com

Step 2

Install SSL certificate.

Step 3

Configure Nginx reverse proxy.

Step 4

Verify:

https://yourdomain.com

works correctly.

Step 5

Enable:

ENABLE_HSTS=true

Now the browser is supposed to force HTTPS.

Because HTTPS actually exists.

Why HSTS Is Still A Good Thing

This experience taught me something important.

Many production issues happen because a security feature is working exactly as intended.

HSTS was not broken.

My timing was wrong.

I enabled a production security feature during an earlier testing phase.

Once the domain, SSL certificate, and reverse proxy were configured properly, HSTS became beneficial instead of problematic.

What I Learned

As a non-traditional developer using AI tools, I often assumed:

If a security setting sounds important, enable it immediately.

Now I think differently.

Many security features depend on other infrastructure already being in place.

The sequence matters.

For HSTS, the correct order is:

Domain
↓
SSL Certificate
↓
HTTPS Working
↓
Enable HSTS

Not the other way around.

Sometimes the hardest bugs are not caused by broken code.

They are caused by correctly working systems that were enabled at the wrong time.

Comments