Traefik’s Headers middleware is the swiss army knife for controlling CORS headers, and it’s often the only tool you need.

Let’s see it in action. Imagine you have an API running behind Traefik, and your frontend, hosted on a different domain, needs to access it. Without proper CORS configuration, browsers will block these requests.

Here’s a Traefik configuration snippet using the Headers middleware to enable CORS for an API:

http:
  routers:
    my-api-router:
      rule: "Host(`api.example.com`)"
      service: "my-api-service"
      middlewares:
        - "cors-headers"

  services:
    my-api-service:
      loadBalancer:
        servers:
          - url: "http://192.168.1.100:8080"

  middlewares:
    cors-headers:
      headers:
        customResponseHeaders:
          Access-Control-Allow-Origin: "https://app.example.com"
          Access-Control-Allow-Methods: "GET,POST,PUT,DELETE,OPTIONS"
          Access-Control-Allow-Headers: "Content-Type,Authorization"
          Access-Control-Max-Age: "3600"
        allowedOrigins:
          - "https://app.example.com"

This configuration does a few key things. The my-api-router directs traffic for api.example.com to our my-api-service. Crucially, it applies the cors-headers middleware.

Inside the cors-headers middleware, we define the headers configuration. customResponseHeaders directly injects headers into the HTTP response. Access-Control-Allow-Origin: "https://app.example.com" tells the browser that only requests originating from https://app.example.com are allowed to access the resource. This is the most fundamental CORS header.

Access-Control-Allow-Methods: "GET,POST,PUT,DELETE,OPTIONS" specifies which HTTP methods are permitted for cross-origin requests. Including OPTIONS is vital for CORS preflight requests.

Access-Control-Allow-Headers: "Content-Type,Authorization" lists the request headers that the browser is allowed to send in a cross-origin request. If your API requires custom headers like Authorization for tokens, you must list them here.

Access-Control-Max-Age: "3600" tells the browser how long (in seconds) it can cache the results of a preflight OPTIONS request. This reduces the number of preflight requests the browser needs to make.

The allowedOrigins list provides an alternative and often preferred way to manage Access-Control-Allow-Origin. When allowedOrigins is used, Traefik will automatically set the Access-Control-Allow-Origin header to match the incoming request’s origin if it’s present in the list. This is more dynamic if you have multiple frontend origins. If allowedOrigins is present, it takes precedence over Access-Control-Allow-Origin in customResponseHeaders.

The Headers middleware also has options for accessControlExposeHeaders, accessControlAllowCredentials, and accessControlRequestHeaders (though accessControlRequestHeaders is less common as accessControlAllowHeaders in customResponseHeaders usually suffices). If your frontend needs to access response headers other than the simple ones (like Cache-Control, Content-Language, Content-Length, Content-Type, Last-Modified, ETag), you’d list them in accessControlExposeHeaders.

The core problem Traefik’s CORS configuration solves is enabling secure, cross-origin communication between your frontend and backend services, which is a fundamental requirement for most modern web applications where the frontend and backend are served from different domains or ports. It acts as a central point of control, abstracting the CORS logic away from your individual backend services, allowing them to focus solely on their business logic.

When a browser makes a cross-origin request, it first checks the Access-Control-Allow-Origin header. If the origin of the request isn’t listed or doesn’t match, the browser blocks the request before it even reaches your API. For methods other than simple GET, POST, and HEAD, or for requests with custom headers, the browser first sends an OPTIONS request (a "preflight" request). Traefik intercepts this OPTIONS request and, if the origin, method, and headers are allowed according to your middleware configuration, it responds with the appropriate Access-Control-Allow-* headers, signaling to the browser that the actual request is safe to proceed.

A common pitfall is forgetting to include OPTIONS in Access-Control-Allow-Methods. Without it, preflight requests will fail, and your actual requests will be blocked by the browser. Another is not listing all custom headers your frontend might send in Access-Control-Allow-Headers.

The next challenge you’ll likely face is managing different CORS policies for different parts of your application or for different environments.

Want structured learning?

Take the full Traefik course →