Ronalds Vilciņš

Add Cloudflare Pages Custom Headers With _headers

Cloudflare Pages custom headers belong in a plain-text _headers file in the deployed output directory. For Hugo, put the source file in static/_headers so the build copies it to public/_headers.

The easy mistake is committing the file somewhere that never reaches the deployment. Start with a small rule and inspect the actual response before adding a whole security policy.

Add Cloudflare Pages custom headers to static files

Create static/_headers in a Hugo project:

/*
  X-Content-Type-Options: nosniff
  Referrer-Policy: strict-origin-when-cross-origin

For another generator, use the directory whose files are copied unchanged into its build output. A plain HTML project can put _headers directly beside the files it deploys.

Build the Hugo site:

hugo

Check that public/_headers contains the rules, then deploy. Cloudflare parses that file and applies the rules when serving matching static assets; the browser receives ordinary HTTP response headers.

The Pages headers documentation explains the rule format and matching behavior. A new file in Git has no effect on an existing deployment until that deployment is updated.

Make the pattern match the response you mean

Each unindented line starts a rule. Indented lines supply its headers:

/downloads/*
  X-Robots-Tag: noindex

That example marks matching download responses with noindex. Keep a broad /* rule for headers you deliberately want across the site; use a narrower path when the behavior belongs to one area.

A response can match several rules. Cloudflare combines matching headers, and repeated values for the same header can be joined with commas. Do not assume that the last block simply replaces every earlier setting.

For a Pages project and its preview hostnames, Cloudflare documents these patterns:

https://:project.pages.dev/*
  X-Robots-Tag: noindex

https://:version.:project.pages.dev/*
  X-Robots-Tag: noindex

Test the production and preview URLs separately. Host-specific rules should not accidentally put noindex on the public domain.

Choose header values for the site

nosniff tells the browser to respect the declared content type in the situations covered by that header. It does not repair a server returning an HTML error document for a JavaScript URL. Check the body and Content-Type when a resource stops loading; MDN documents that distinction.

The example’s referrer policy keeps the full referrer for same-origin requests and limits cross-origin information according to the policy. no-referrer is another valid choice if you deliberately want to send none.

Additional rules might include:

/*
  X-Frame-Options: DENY
  Permissions-Policy: fullscreen=(), geolocation=()

Those settings have consequences. A site intended to be embedded should not copy DENY, and a page using fullscreen or geolocation needs a different permissions policy.

For a deliberately public asset, CORS can be scoped to its directory:

/public-data/*
  Access-Control-Allow-Origin: *

A wildcard is not an authentication mechanism. Use it only when other origins should be allowed to read those responses.

The older example used X-XSS-Protection. MDN marks that header as deprecated, so it is no longer part of the starting configuration here. A Content Security Policy deserves its own review against the scripts and embeds the site actually uses.

Verify a GET response after deployment

Run this against a real page on the deployed site:

curl -sS -D - -o /dev/null https://ronaldsvilcins.com/

The command prints response headers while discarding the body. Replace the address with your own page when checking your project.

Confirm the status, Content-Type, and the headers you configured. Repeat the check for a static asset and a preview URL if those have different rules.

If the header is missing, first inspect the deployed output directory. Then check the requested path, redirects, and overlapping rules. A successful build only proves that the generator ran.

Keep Functions and Workers responses separate

Pages _headers rules do not apply to responses generated by Pages Functions. Set those headers in the function’s returned response instead.

Workers Static Assets also supports _headers, but it has its own headers documentation. Responses produced by Worker code need their headers set in that code; do not infer their behavior from a static-file check.

For a static Hugo site, I would keep the initial file short. Add a rule when you can explain which response it changes and verify that response after deployment.

Filed under on

Last updated on

← Back to all articles