HTTP Strict Transport Security sounds simple: send one response header, force browsers onto HTTPS, done. In practice, the hard part is deciding where to manage it in Kong and how aggressively to roll it out without breaking edge cases.

If you run Kong API Gateway, you usually have a few choices:

  1. Set HSTS in Kong itself
  2. Set HSTS in an upstream app behind Kong
  3. Set HSTS at an outer load balancer or CDN instead of Kong
  4. Use a phased rollout with short max-age first, then increase later

I’m opinionated on this one: if Kong is your main HTTPS entry point, HSTS usually belongs there. It keeps policy consistent across services and avoids every team reinventing the same header logic. But there are trade-offs, and some teams absolutely should not flip on preload-style settings on day one.

Quick HSTS refresher

The header looks like this:

Strict-Transport-Security: max-age=31536000; includeSubDomains

Common directives:

  • max-age=31536000 — browsers remember HTTPS-only for one year
  • includeSubDomains — apply policy to all subdomains
  • preload — request inclusion in browser preload lists, which is a much bigger commitment

HSTS only works when the browser sees it over a valid HTTPS connection. Sending it over plain HTTP does nothing useful.

For API gateways, one caveat: HSTS is a browser control. It protects browser-based clients, not arbitrary backend integrations, mobile SDKs, or server-to-server calls that ignore browser transport rules.

Option 1: Manage HSTS in Kong

This is the cleanest option for most teams.

How to do it

With Kong, a common pattern is to inject the header at the gateway layer using response transformation or server/header configuration depending on your deployment model.

Example with the response-transformer plugin:

curl -s -X POST http://localhost:8001/plugins \
  --data "name=response-transformer" \
  --data "config.add.headers=Strict-Transport-Security:max-age=31536000; includeSubDomains"

If you only want it on a specific service or route:

curl -s -X POST http://localhost:8001/services/my-service/plugins \
  --data "name=response-transformer" \
  --data "config.add.headers=Strict-Transport-Security:max-age=86400"

Declarative config example:

_format_version: "3.0"

services:
  - name: my-api
    url: http://upstream-api:8080
    routes:
      - name: my-api-route
        paths:
          - /api

plugins:
  - name: response-transformer
    config:
      add:
        headers:
          - "Strict-Transport-Security:max-age=31536000; includeSubDomains"

You can verify the result with curl:

curl -I https://api.example.com

Expected output:

HTTP/2 200
strict-transport-security: max-age=31536000; includeSubDomains

You can also run a quick scan with Headertest to confirm the header is actually reaching clients.

Pros

  • Centralized policy: one place to manage transport security behavior
  • Consistent across services: no guessing which app forgot to add the header
  • Good fit for platform teams: app teams don’t need to own low-level header policy
  • Easy phased rollout: start with short max-age, then increase safely

Cons

  • Can hide app-level complexity: some apps may still generate redirects or mixed assumptions you haven’t fixed
  • Risk of duplicate headers: if upstreams also send HSTS, you need to decide who owns it
  • Per-route inconsistency is easy to create: route-level plugins can drift if you’re not disciplined

Best fit

Use this when Kong is the primary public HTTPS boundary and you want one security posture across many APIs.

Option 2: Manage HSTS in the upstream application

This is common when teams fully own their apps and Kong is treated as a thin proxy.

Example in Node/Express:

app.use((req, res, next) => {
  res.setHeader(
    "Strict-Transport-Security",
    "max-age=31536000; includeSubDomains"
  );
  next();
});

Example in NGINX behind Kong:

add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

Pros

  • App teams keep ownership of security behavior
  • Closer to business logic if some responses genuinely need different treatment
  • Portable if the app also runs outside Kong in another environment

Cons

  • Inconsistent rollout across teams and services
  • Easy to miss on legacy services
  • Harder to audit than one gateway config
  • Duplicate ownership when Kong or an outer proxy also injects headers

Best fit

Use this if your organization is highly decentralized and each app already owns its own edge behavior. I still think this is weaker operationally for shared API platforms.

Option 3: Manage HSTS at the CDN or external load balancer

Sometimes Kong sits behind another HTTPS layer like a cloud load balancer, ingress controller, or CDN. In that case, you can set HSTS there instead.

Pros

  • Applies before traffic reaches Kong
  • Single policy for many origins
  • Good for organizations with a strict edge security model

Cons

  • Kong no longer reflects final client-facing policy in its own config
  • Can get confusing fast when troubleshooting
  • Environment drift is common between staging, production, and regional edges

Best fit

Use this when the outer edge is the true public boundary and Kong is effectively internal. If browsers never directly see Kong’s TLS endpoint, outer-edge ownership makes sense.

Comparison: which layer should own HSTS?

Here’s the blunt version.

Kong-owned HSTS

Pros

  • Centralized
  • Consistent
  • Easy for platform teams
  • Strong default for shared gateways

Cons

  • Needs coordination with upstream headers
  • Can create false confidence if HTTPS redirects and cert coverage are messy

App-owned HSTS

Pros

  • Team autonomy
  • Portable config
  • Fine-grained control

Cons

  • Inconsistent
  • Hard to audit
  • More chances to forget it

CDN/LB-owned HSTS

Pros

  • Closest to the real browser-facing edge
  • Broad coverage
  • Good for large edge-managed estates

Cons

  • Less visible in Kong
  • More moving parts
  • Debugging can be annoying

If you want one recommendation: own HSTS at the outermost browser-facing HTTPS layer. If that layer is Kong, configure it in Kong. If that layer is your CDN, do it there. Don’t spread ownership across all three.

Rollout strategy: the part people rush

The biggest HSTS mistake is not technical. It’s turning on a long max-age before you’ve verified every relevant hostname is cleanly HTTPS-only.

A safer rollout for Kong looks like this:

Phase 1: Start small

curl -s -X POST http://localhost:8001/plugins \
  --data "name=response-transformer" \
  --data "config.add.headers=Strict-Transport-Security:max-age=300"

That’s 5 minutes. Enough to test behavior without locking users in for months.

Phase 2: Increase after validation

Move to one day:

Strict-Transport-Security: max-age=86400

Then one month:

Strict-Transport-Security: max-age=2592000

Then one year:

Strict-Transport-Security: max-age=31536000; includeSubDomains

Phase 3: Consider preload only if you really mean it

Strict-Transport-Security: max-age=31536000; includeSubDomains; preload

preload is not a casual checkbox. You’re declaring that the whole domain tree is HTTPS-only and should stay that way. If you still have oddball subdomains, old admin panels, or forgotten non-TLS services, don’t do this yet.

Common mistakes with Kong and HSTS

1. Sending HSTS on HTTP listeners

If someone can hit Kong on port 80 and get a plain HTTP response with HSTS, that header doesn’t buy you much. You still need clean HTTP-to-HTTPS redirects and a proper TLS-first posture.

2. Using includeSubDomains too early

This breaks forgotten subdomains faster than people expect. Inventory first, then enable.

3. Assuming HSTS protects API clients universally

It helps browsers. It does not magically fix insecure integrations that hardcode http:// or skip certificate validation.

4. Duplicating the header in multiple layers

Kong, upstream NGINX, and CDN all setting different HSTS values is a classic mess. Pick an owner.

5. Preloading before certificate coverage is complete

If every subdomain isn’t consistently HTTPS with valid certs, preload is reckless.

Practical recommendation

For most Kong deployments, I’d do this:

  1. Make sure Kong is only serving public traffic over valid HTTPS
  2. Redirect HTTP to HTTPS cleanly
  3. Set HSTS in Kong with a short max-age
  4. Validate all relevant routes and subdomains
  5. Increase to one year
  6. Add includeSubDomains only after inventory and testing
  7. Treat preload as a separate project, not a default

A solid starting header for many teams is:

Strict-Transport-Security: max-age=86400

A mature production header is usually:

Strict-Transport-Security: max-age=31536000; includeSubDomains

And only after serious validation:

Strict-Transport-Security: max-age=31536000; includeSubDomains; preload

For Kong-specific behavior and plugin options, check the official Kong docs:

If you want a fast sanity check after rollout, run your hostname through Headertest. I like doing that right after a gateway change because it catches the obvious “configured but not actually returned” mistake in seconds.