Official blog of Daniel Uhlmann

Hardening Your Traefik with Security Headers

For quite some time now, I’ve been using Traefik as the reverse proxy and ingress controller in several of my personal and client projects. Its clean configuration model, native support for Docker and Kubernetes, and automatic TLS management make it a no-brainer for me. But as my services grew more public, I wanted to make sure to follow basic security good practices — especially when it comes to HTTP security headers.

Headers can protect against XSS attacks, clickjacking, content sniffing, and more. While Traefik already provides good support for these headers through its middleware system, I noticed that a lot of people either don’t enable them or leave them misconfigured. So I decided to dive in, tweak my setup, and see how far I could push the security grade.

Spoiler: After a few changes, I was able to bump several of my domains up to an A+ rating, using a quick scanning tool like https://developer.mozilla.org/en-US/observatory/ .

Middleware for Security Headers

Traefik allows you to define reusable middleware in a static or dynamic configuration file. I defined the secHeaders middleware in Traefik’s dynamic configuration, using a separate YAML file (e.g., middlewares.yml) placed in a directory that Traefik watches for dynamic configuration. To enable this, make sure your static configuration file contains something like:

1providers:
2  file:
3    directory: /etc/traefik/dynamic
4    watch: true

This tells Traefik to load any valid config files from that path and apply them live. Here’s how the middleware looks inside such a dynamic file:

 1http:
 2  middlewares:
 3    secHeaders:
 4      headers:
 5        browserXssFilter: true
 6        contentTypeNosniff: true
 7        frameDeny: true
 8        customFrameOptionsValue: "SAMEORIGIN"
 9        stsIncludeSubdomains: true
10        stsPreload: true
11        stsSeconds: 31536000
12        contentSecurityPolicy: >-
13          default-src 'self';
14          script-src 'self' https://stats.example.com
15          connect-src 'self' https://stats.example.com
16          img-src 'self' data: https://stats.example.com
17          style-src 'self' 'unsafe-inline';
18          object-src 'none';
19          base-uri 'none';
20          frame-ancestors 'none';
21        customResponseHeaders:
22          server: ""
23          x-powered-by: ""

Let’s break down what this does:

Note: Depending on what you’re building, you might need to fine-tune your Content Security Policy, especially if you’re embedding third-party scripts or using CDNs. For example, the above CSP allows scripts and network connections from stats.example.com and images from api.examplecdn.com. You’ll want to update these to reflect the actual domains your application needs.

Connecting Middleware to Your Services

Once the middleware is defined, you can hook it into any of your applications using Traefik labels. For example, if you’re deploying a Docker service, you might add something like this to your container labels:

1- "traefik.http.routers.backend.middlewares=secHeaders@file"

This attaches the secHeaders middleware to the router named backend, and the @file part tells Traefik to look for the middleware definition in your dynamic configuration files.

That’s it. Every request hitting that service now gets the full suite of security headers injected into the response.

Testing and Results

After applying the changes and deploying, I used https://developer.mozilla.org/en-US/observatory/ to test one of my domains.

Before:

After:

HTTP Observatory Report

Sources

<< Previous Post

|

Next Post >>