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.