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_ERRORThe 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
Post a Comment