How to Implement Secure Headers using Cloudflare Workers?

A step-by-step guide to implementing secure HTTP headers on websites powered by Cloudflare using Cloudflare Workers.

There are many ways to implement HTTP response headers to secure sites from common vulnerabilities, such as XSS, Clickjacking, MIMI sniffing, cross-site injection, and many more. Its widely adopted practice and recommended by OWASP.

Previously, I wrote about implementing headers in a web server like Apache, Nginx, and IIS. However, if you are using Cloudflare to protect and supercharge your sites, you may take advantage of Cloudflare Workers to manipulate the HTTP response headers.

Cloudflare Workers is a serverless platform where you can run JavaScript, C, C++, Rust code. It gets deployed on every Cloudflare data center, which is more than 200 worldwide.

The implementation is very straightforward and flexible. It gives you the flexibility to apply the headers on the entire site, including the subdomain or specific URI with a matching pattern using Regex.

For this demonstration, I'll be using the code by Scott Helme.

Let's get it startedβ€¦πŸ‘¨β€πŸ’»

const securityHeaders = {         "Content-Security-Policy": "upgrade-insecure-requests",         "Strict-Transport-Security": "max-age=1000",         "X-Xss-Protection": "1; mode=block",         "X-Frame-Options": "DENY",         "X-Content-Type-Options": "nosniff",         "Referrer-Policy": "strict-origin-when-cross-origin"     },     sanitiseHeaders = {         Server: ""     },     removeHeaders = [         "Public-Key-Pins",         "X-Powered-By",         "X-AspNet-Version"     ];  async function addHeaders(req) {     const response = await fetch(req),         newHeaders = new Headers(response.headers),         setHeaders = Object.assign({}, securityHeaders, sanitiseHeaders);      if (newHeaders.has("Content-Type") && !newHeaders.get("Content-Type").includes("text/html")) {         return new Response(response.body, {             status: response.status,             statusText: response.statusText,             headers: newHeaders         });     }      Object.keys(setHeaders).forEach(name => newHeaders.set(name, setHeaders[name]));      removeHeaders.forEach(name => newHeaders.delete(name));      return new Response(response.body, {         status: response.status,         statusText: response.statusText,         headers: newHeaders     }); }  addEventListener("fetch", event => event.respondWith(addHeaders(event.request)));

Don't save yet; you may want to adjust the following headers to meet the requirement.

Content-Security-Policy – if you need to apply your application policy, you can do it here.

Ex – if you need to source content through iFrame on multiple URLs, then you may take advantage of frame-ancestors as below.

"Content-Security-Policy" : "frame-ancestors 'self'",

The above will allow loading the content from,, and self site.

X-Frame-Options – you can change to SAMEORIGIN if you intend to show your site's content on some page within the same site using iframe.

"X-Frame-Options": "SAMEORIGIN",

Server – you can sanitize the server header here. Put whatever you like.

"Server" : "Geekflare Server",

RemoveHeaders – do you need to remove some headers to hide the versions to mitigate the information leakage vulnerability?

You can do it here.

let removeHeaders = [ 	"Public-Key-Pins", 	"X-Powered-By", 	"X-AspNet-Version", ]

Adding new Headers – if you need to pass some custom headers to your applications, you can add them under securityHeaders section as below.

let securityHeaders = { 	"Content-Security-Policy" : "frame-ancestors 'self'", 	"Strict-Transport-Security" : "max-age=1000", 	"X-Xss-Protection" : "1; mode=block", 	"X-Frame-Options" : "SAMEORIGIN", 	"X-Content-Type-Options" : "nosniff", 	"Referrer-Policy" : "strict-origin-when-cross-origin",         "Custom-Header"  : "Success", }

Once you are done adjusting all the headers you require, name the worker, and click Save and Deploy.

Great! the worker is ready, and next, we need to add this to the site where you want to apply the headers. I'll apply this to my lab site.

That's all; within a second, you will notice all the headers are implemented to the site.

Here is how it looks like from Chrome Dev Tools. You can also test the header through an HTTP header tool.

I don't know why the Server header is not reflected. I guess Cloudflare is overriding this.

You see, the overall implementation takes ~15 minutes, and no downtime or restart is required like Apache or Nginx. If you are planning to apply this to a production site, I would suggest first testing on a lower environment, or with the help of a route, you can apply on the test pages to verify the results. Once satisfied, push to wherever you want.

This is awesome!

Thanks to Scott for the code.