Netlify’s custom headers feature is surprisingly powerful for fine-tuning how your site interacts with browsers, especially when it comes to security.

Let’s see it in action. Imagine you want to enforce a strict Content Security Policy (CSP) to prevent XSS attacks. You’d typically define this in your netlify.toml file.

[[headers]]
  for = "/*"
  [headers.values]
    Content-Security-Policy = "default-src 'self'; script-src 'self' 'unsafe-inline'; object-src 'none'; base-uri 'self';"

When a browser requests any file (/*) on your site, Netlify will inject the Content-Security-Policy header with the specified value. If a browser tries to load a script from an unauthorized domain, it will be blocked, and you’ll see an error in the browser’s developer console.

This mechanism allows you to control a wide range of HTTP headers, including X-Frame-Options to prevent clickjacking, X-XSS-Protection for older browser XSS filtering, Referrer-Policy to manage referrer information, and Strict-Transport-Security (HSTS) to force HTTPS connections.

The core problem Netlify headers solve is providing a centralized, declarative way to manage these security and caching directives without needing to configure them directly on your origin server or rely on complex server-side logic for static assets. For a static site, you don’t have a traditional origin server to configure these on. Netlify acts as that edge server, and netlify.toml is your configuration interface.

Internally, Netlify intercepts requests at the edge. When a request matches a [[headers]] block in your netlify.toml, it applies the specified [headers.values] to the response before it’s sent to the client. This happens before Netlify even checks if the file exists in your site’s publish directory.

The for field is crucial. It accepts glob patterns that match the path of the requested resource. /* matches everything. You can be more specific, like for = "/admin/*" to apply different headers only to your admin section, or for = "*.js" to apply headers only to JavaScript files.

You can also define multiple [[headers]] blocks. Netlify processes them in the order they appear in your netlify.toml. If a path matches multiple blocks, the last matching block’s headers will be applied. This allows for overrides and fine-grained control. For instance, you might have a general policy for all files, then a more restrictive one for specific types of assets.

[[headers]]
  for = "/*"
  [headers.values]
    X-Frame-Options = "DENY"

[[headers]]
  for = "/images/*"
  [headers.values]
    Cache-Control = "public, max-age=31536000"

In this example, all requests get X-Frame-Options: DENY, but requests for images under /images/ also get a long Cache-Control directive.

One of the most powerful, yet often overlooked, aspects of Netlify headers is their interaction with Netlify’s built-in redirect and rewrite rules. When you define a redirect or rewrite, Netlify processes those rules before checking your header configuration. This means you can direct a request to a different file or URL, and then apply specific headers to the response that Netlify generates for that new destination. This opens up possibilities for dynamic header behavior based on routing, even on a static site. For example, you could rewrite a request for /api/user to a serverless function, and then the headers you’ve configured for /api/* would be applied to the function’s response.

The most surprising thing is how Cache-Control headers can be used to manage Netlify’s own CDN caching behavior. By setting appropriate Cache-Control directives, you can influence how long Netlify’s edge nodes cache your assets, balancing freshness with performance. For example, Cache-Control: public, max-age=3600 tells Netlify’s CDN to cache the asset for one hour. If you deploy a new version of that asset, Netlify’s cache invalidation mechanisms will typically handle it, but understanding these directives gives you more control over cache behavior during critical deployments or when managing stale content.

The next logical step is exploring how to leverage Netlify Functions in conjunction with custom headers for truly dynamic edge logic.

Want structured learning?

Take the full Netlify course →