HTTP Strict Transport Security Policy in NGINX Ingress Controller

by

in , ,

NGINX Ingress Controller 5.6.0 introduces HSTS as a Policy type for the VirtualServer CRD. This blog gives a quick overview of the feature with some deployment examples and tips on how to correctly configure this policy.

Across this blog, we’re focused on:

  • How HSTS policy works in NGINX Ingress Controller.
  • Where to attach it in VirtualServer.

What is HSTS?

HTTP Strict Transport Security (HSTS) is a security feature that tells browsers to only communicate with your site over HTTPS, even if the user types in “http://” or follows an insecure link. It helps prevent man-in-the-middle attacks and cookie hijacking by ensuring that all communication is encrypted.

When a browser receives an HSTS header from a site, it will remember to only use HTTPS for that site for a specified period of time. This means that even if a user tries to access the site via HTTP, the browser will automatically redirect them to HTTPS.

Why Use a Policy for HSTS?

Using a Policy resource for HSTS in NGINX Ingress Controller allows you to define the HSTS settings in a reusable and centralized way. Instead of configuring HSTS headers directly in each VirtualServer or VirtualServer Route resource, you can create a Policy that specifies the desired HSTS parameters and then attach that Policy to any VirtualServer that requires it.

How HSTS Policy Works in NGINX Ingress Controller

At a high level:

  1. Create a Policy resource with spec.hsts.
  2. Attach it to a VirtualServer, at VirtualServer.spec.policies.
  3. NGINX Ingress Controller generates the Strict-Transport-Security header with the configured directives.

Example HSTS policy:

apiVersion: k8s.nginx.org/v1
kind: Policy
metadata:
  name: hsts-policy
spec:
  hsts:
    maxAge: 31536000
    includeSubDomains: false
    behindProxy: false
    preload: false

When includeSubDomains is enabled the policy extends to all subdomains of the host, so browsers will enforce HTTPS for api.example.com, app.example.com, and any other subdomain.

If behindProxy is disabled, TLS termination is required at the Ingress Controller. If behindProxy is enabled, the Ingress Controller will trust the X-Forwarded-Proto header to determine if the original request was over HTTPS.

Enabling preload signals that the domain should be included in browser HSTS preload lists, which enforce HTTPS on the very first visit before any header is received. This requires includeSubDomains to be enabled and the maxAge to be at least 31536000 seconds (1 year).

Example VirtualServer Referencing an HSTS Policy

You can deploy the policy with the VirtualServer HSTS example on GitHub. Policies can only be applied at the server level. If applied at the route level, the Policy will be rejected.

This VirtualServer references the HSTS policy and the routes inherit that policy:

apiVersion: k8s.nginx.org/v1
kind: VirtualServer
metadata:
   name: webapp
spec:
   host: webapp.example.com
   tls:
      secret: tls-secret
   policies:
      - name: hsts-policy
   upstreams:
      - name: webapp
        service: webapp-svc
        port: 80
   routes:
      - path: /
        action:
           pass: webapp

Example Combining HSTS with Other Policies

You can deploy the HSTS policy alongside any other policy. For example, the following VirtualServer references both an HSTS policy at the spec level and a CORS policy at the route level. NGINX Ingress Controller ensures both policies work together without additional configuration, even when route-level policies add their own custom headers.

This VirtualServer references the HSTS policy at the spec level and a CORS policy at the route level:

apiVersion: k8s.nginx.org/v1
kind: VirtualServer
metadata:
   name: webapp
spec:
   host: webapp.example.com
   tls:
      secret: tls-secret
   policies:
      - name: hsts-policy
   upstreams:
      - name: webapp
        service: webapp-svc
        port: 80
   routes:
      - path: /
        action:
           pass: webapp
        policies:
          - name: cors-policy

Safely Removing HSTS

In order to safely remove HSTS, it is important to understand that deleting the policy does not clear the browser’s cached directive, and the browser will continue enforcing HTTPS until maxAge expires, which can lock users out if TLS is later removed.

To safely remove HSTS, you should:

  1. Update the policy to set maxAge to 0 and apply the change:

apiVersion: k8s.nginx.org/v1
kind: Policy
metadata:
name: hsts-policy
spec:
hsts:
   maxAge: 0

This tells browsers to immediately expire the cached HSTS directive.

2. Allow time for clients to receive the updated header. Any client that visits the site will pick up the maxAge=0 directive and will stop enforcing HTTPS.

3. Once the updated header has been served, remove the policy reference from the VirtualServer and delete the policy resource.

For more details on how HSTS expiration works in browsers, see the MDN documentation on HSTS expiration.

Important Behavior to Remember

  • HSTS policy is spec-level only – referencing it in a route or subroute is rejected.
  • Only one HSTS policy per VirtualServer is applied. If multiple HSTS policies are referenced on the same VirtualServer, the first one is used and subsequent ones are ignored.
  • TLS must be enabled unless behindProxy is enabled.
  • The generated Strict-Transport-Security header is only sent on HTTPS responses. If the request is HTTP, the header is not sent.

You can find complete working examples on GitHub and more documentation in our docs:

NGINX Community Forum