HTTP Strict Transport Security looks simple: send one header, browsers stop using HTTP, done.
That’s the theory. In real Quarkus apps, HSTS usually goes wrong in boring, expensive ways: wrong environment, wrong proxy setup, bad preload assumptions, or turning it on before the whole domain is actually HTTPS-ready.
If you’re building Java services with Quarkus, these are the mistakes I see most often and how to fix them without breaking production.
Mistake 1: Enabling HSTS before all traffic is really HTTPS
This is the classic one.
You add:
quarkus.http.header."Strict-Transport-Security".value=max-age=31536000; includeSubDomains
```text
Ship it, feel secure, and then find out a subdomain still serves plain HTTP, or some old internal endpoint redirects badly, or your CDN/proxy setup isn’t consistently terminating TLS.
HSTS is sticky. Once a browser sees it, that browser will refuse HTTP for the duration of `max-age`. If you include subdomains, you’ve committed every subdomain too.
### Fix
Start small and verify your whole estate first.
A safer rollout looks like this:
```properties
quarkus.http.header."Strict-Transport-Security".value=max-age=300
That gives you a 5-minute policy. Test the actual behavior in browsers, behind your reverse proxy, through your load balancer, and across any subdomains that matter.
Then increase gradually:
quarkus.http.header."Strict-Transport-Security".value=max-age=86400
```text
Then later:
```properties
quarkus.http.header."Strict-Transport-Security".value=max-age=31536000
If you want a quick check of what headers your app is actually returning in production, run a scan with HeaderTest.
Mistake 2: Sending HSTS on HTTP responses
Browsers ignore HSTS over plain HTTP. That’s by design. The header only matters when delivered over HTTPS.
People still end up configuring it globally and assuming it works everywhere.
Fix
Make sure your app is actually serving HTTPS traffic, or more commonly with Quarkus, that your proxy forwards HTTPS correctly and Quarkus knows the original request was secure.
If TLS terminates at a reverse proxy, configure proxy handling properly in Quarkus.
For example:
quarkus.http.proxy.proxy-address-forwarding=true
quarkus.http.proxy.allow-forwarded=true
```text
Depending on your infrastructure, you may also need forwarded header support adjusted to match what your proxy sends. Check the official Quarkus HTTP reference for the exact proxy header options your setup uses:
[https://quarkus.io/guides/http-reference](https://quarkus.io/guides/http-reference)
Then verify that HTTPS requests are the ones returning:
Strict-Transport-Security: max-age=31536000; includeSubDomains
I always test this from outside the cluster, not just from local dev or an internal pod. Internal traffic can lie to you.
## Mistake 3: Trusting redirect logic instead of HSTS
A lot of teams think “we already redirect HTTP to HTTPS, so we’re covered.”
Not really.
Redirects help, but the first request can still start over HTTP. HSTS tells the browser not to even try HTTP next time. That closes off downgrade and SSL stripping attacks after the policy is learned.
### Fix
Use both:
1. Redirect HTTP to HTTPS
2. Send HSTS on HTTPS responses
In Quarkus, if you want insecure requests redirected, configure that behavior explicitly:
```properties
quarkus.http.insecure-requests=redirect
And pair it with the HSTS header:
quarkus.http.header."Strict-Transport-Security".value=max-age=31536000; includeSubDomains
```text
That’s the combination you want in production.
## Mistake 4: Using `includeSubDomains` without checking subdomains
`includeSubDomains` is great when your domain is clean and consistently HTTPS-only.
It’s a mess when you’ve got forgotten admin panels, old staging hosts, random marketing microsites, or mail-related subdomains managed by another team. I’ve seen one HSTS rollout break things that nobody had touched in years.
### Fix
Don’t add `includeSubDomains` until you’ve audited the domain properly.
If you’re not ready, start with:
```properties
quarkus.http.header."Strict-Transport-Security".value=max-age=31536000
Later, once every relevant subdomain is HTTPS-capable and correctly configured:
quarkus.http.header."Strict-Transport-Security".value=max-age=31536000; includeSubDomains
```text
Be honest about ownership here. If your team does not control all subdomains, `includeSubDomains` may be the wrong move for now.
## Mistake 5: Jumping straight to preload
Preload is where people get overconfident.
They see examples like this:
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
and assume `preload` itself changes browser behavior immediately. It doesn’t. It’s just a signal that your site wants to be included in browser preload lists, assuming you meet the requirements and submit it through the proper process.
Once preloaded, backing out is painful and slow.
### Fix
Treat preload as the last step, not the first.
Before you even think about it, make sure:
- HTTPS works everywhere
- All subdomains are covered if you use `includeSubDomains`
- You’re already using a long `max-age`
- Your operational reality matches the policy, not just today but long term
The final config might be:
```properties
quarkus.http.header."Strict-Transport-Security".value=max-age=31536000; includeSubDomains; preload
But don’t start there unless you’re very sure.
For Quarkus config behavior and header setup, stick to the official docs: https://quarkus.io/guides/http-reference
Mistake 6: Turning on HSTS in local dev and test environments
This one wastes time.
You enable HSTS everywhere, test in a browser, and suddenly your localhost behavior gets weird because the browser cached a strict policy. Then people start debugging Quarkus when the real problem is browser state.
Fix
Scope HSTS to the right profile.
Quarkus profiles make this easy:
%prod.quarkus.http.header."Strict-Transport-Security".value=max-age=31536000; includeSubDomains
```text
If you want a shorter value in staging:
```properties
%staging.quarkus.http.header."Strict-Transport-Security".value=max-age=300
%prod.quarkus.http.header."Strict-Transport-Security".value=max-age=31536000; includeSubDomains
That’s much better than accidentally teaching your browser that some test hostname should always be HTTPS.
Mistake 7: Forgetting that proxies and gateways may overwrite headers
Your Quarkus app can send the perfect HSTS header and still lose because your ingress controller, API gateway, CDN, or load balancer strips it, replaces it, or injects a different value.
I’ve seen apps configured for one year while the edge was sending max-age=0. Guess which one the browser got.
Fix
Decide where HSTS is owned.
Usually, the cleanest choice is the outermost HTTPS layer, often the reverse proxy or edge gateway. But if you manage it in Quarkus, verify nothing upstream is modifying it.
Check the real response from the public endpoint, not just your application logs.
If you do want Quarkus to own it, keep the config explicit:
%prod.quarkus.http.header."Strict-Transport-Security".value=max-age=31536000; includeSubDomains
```text
Then test from outside using curl:
curl -I https://app.example.com
You should see exactly one HSTS header with the value you expect.
## Mistake 8: Using `max-age=0` without understanding the rollback
Yes, `max-age=0` disables HSTS for future responses. No, it does not magically erase every operational problem instantly.
If your site was preloaded, `max-age=0` is not enough. If users already have a cached policy, behavior may persist until browsers process the updated header under valid HTTPS conditions.
### Fix
Use rollback deliberately.
If you need to disable HSTS temporarily:
```properties
%prod.quarkus.http.header."Strict-Transport-Security".value=max-age=0
Serve that over HTTPS and understand the limits:
- It affects browser-stored policy updates
- It does not instantly fix preload status
- It does not help if users cannot reach a valid HTTPS endpoint to receive the new header
That’s another reason to roll out cautiously in the first place.
Mistake 9: Assuming HSTS means “we have HTTPS security handled”
HSTS is one header. A good one, but still just one header.
It doesn’t replace certificate management, secure cookies, CSP, X-Content-Type-Options, or correct proxy configuration. It also doesn’t protect first-time visitors unless preload is in play.
Fix
Treat HSTS as part of your baseline, not the finish line.
A production Quarkus setup usually wants at least:
quarkus.http.insecure-requests=redirect
%prod.quarkus.http.header."Strict-Transport-Security".value=max-age=31536000; includeSubDomains
%prod.quarkus.http.header."X-Content-Type-Options".value=nosniff
%prod.quarkus.http.header."X-Frame-Options".value=DENY
```text
And if you want to validate what actually ships, scan the live site with [HeaderTest](https://headertest.com?utm_source=hsts-guide&utm_medium=blog&utm_campaign=article-link).
## A sensible Quarkus HSTS rollout
If I were setting this up on a real team, I’d do it like this:
### Stage 1: Redirect only
```properties
quarkus.http.insecure-requests=redirect
Stage 2: Short HSTS
%prod.quarkus.http.header."Strict-Transport-Security".value=max-age=300
```text
### Stage 3: Longer HSTS after validation
```properties
%prod.quarkus.http.header."Strict-Transport-Security".value=max-age=31536000
Stage 4: Add subdomains if fully ready
%prod.quarkus.http.header."Strict-Transport-Security".value=max-age=31536000; includeSubDomains
```text
### Stage 5: Consider preload only if you really mean it
```properties
%prod.quarkus.http.header."Strict-Transport-Security".value=max-age=31536000; includeSubDomains; preload
That sequence is boring, and boring is good in security.
For the Quarkus-specific configuration details, the official references are the ones worth checking: https://quarkus.io/guides/http-reference
HSTS is easy to add. The hard part is making sure your infrastructure deserves it. That’s where most mistakes happen.