Custom HTTP headers
You can attach your own HTTP response headers to any static site — for
example Strict-Transport-Security, X-Frame-Options,
Content-Security-Policy or Referrer-Policy. They are declared in a
_headers file at the root of your built site (Netlify-style syntax).
There is no "Custom Headers" panel in the dashboard — header management lives in this file so the config is versioned in git next to your code.
Why a file, not app code
A static site has no server process of yours: it is a set of files served
by Layero's CDN and edge nginx. HTTP response headers are part of the
server's response, so they are set by whatever serves the bytes — not
by the HTML file itself. A <meta http-equiv> tag does not help here:
browsers ignore it for Strict-Transport-Security, X-Frame-Options and
Content-Security-Policy — those policies only take effect as real HTTP
headers.
The _headers file is how you pass that intent down to the serving layer:
the builder reads it and attaches the listed headers to edge responses.
:::note SSR and runtime apps
Everything here is about static sites. If you run an SSR or runtime app
(Next.js in server mode, Streamlit, Flask), the process runs on the server
and can set headers itself, in code (res.setHeader(...),
next.config.js → headers(), etc.). Such projects don't need _headers.
:::
Quick start
Put a file named _headers (no extension) in the root of your build
output folder, next to index.html:
/*
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
X-Frame-Options: SAMEORIGIN
Referrer-Policy: strict-origin-when-cross-origin
Deploy — and the headers start being served on every page. Verify with any
-I request:
curl -sI https://<org>-<project>.layero.ru/ | grep -i -E 'strict-transport|x-frame|referrer'
Syntax
The file is a set of blocks. A block starts with a path line at the left
margin (no indent, must start with /). Indented lines that follow are
Header-Name: value pairs belonging to that path. Blank lines and lines
starting with # are ignored.
# A comment — ignored
/*
X-Frame-Options: DENY
Strict-Transport-Security: max-age=31536000
/about/
X-Robots-Tag: noindex
/assets/*
Cache-Control: public, max-age=31536000, immutable
Supported paths
| Pattern | Matches | Example |
|---|---|---|
/* | Every page (catch-all) | the whole site |
| Exact path | Exactly this URL | /about/, /index.html |
/prefix/* | Everything under a prefix | /assets/*, /docs/* |
A wildcard in the middle of a path (/foo/*/bar) is not supported yet —
such a line is silently skipped and a warning is written to the build log.
How overlaps resolve
File order is preserved, and when several blocks match the same header the
last one wins. So a broad /* block usually goes first, with narrower
overrides below:
/*
Cache-Control: public, max-age=0, must-revalidate
/assets/*
Cache-Control: public, max-age=31536000, immutable
Here /assets/style.css gets the long cache, everything else gets
must-revalidate.
What you can and can't set
Any header is allowed, including Content-Security-Policy,
Permissions-Policy, X-Content-Type-Options, Cross-Origin-*, etc.
A few headers are reserved by the platform and cannot be overridden — they are skipped with a warning in the build log:
Set-CookieX-Layero-Cache,X-Layero-Deploy-Id,X-Layero-Project-Id,X-Layero-S3-Path,X-Layero-Redirects,X-Layero-Headers(Layero's internal headers).
Layero does not add security headers for you. HSTS, X-Frame-Options
and the like appear only after you declare them in _headers.
Where to put the file
_headers must land in the root of the artifact — the folder whose
contents get published (output in project settings or in
layero.json).
- Many generators copy the static folder (
public/,static/,assets/) verbatim. Put_headersthere and it will end up at the build root. - Regular project: the file shows up next to
index.htmlin the built folder. - Monorepo: inside the project's
root_directory, so it lands at the root ofoutputafter the build.
After deploying, confirm the headers applied with curl -sI (see
Quick start). If they're missing, the file almost always
didn't land in the output root.
A broken file doesn't fail the deploy
The parser is fault-tolerant: malformed lines (a path without /, a header
without :, an unsupported wildcard, a reserved name) are skipped, and
valid ones are applied. Every issue is logged; the deploy doesn't fail. An
empty or missing _headers clears previously set rules — so the header
state always reflects the latest build.
Works on custom domains too
Headers apply the same way on <org>-<project>.layero.ru and on a linked
custom domain — rules are bound to the deploy, not to a
specific host.
Redirects
Per-path redirects are configured with the sibling _redirects file
(also Netlify-compatible, matched by path). Note that _redirects matches
by path, not host, so a host-based redirect like www.site.com → site.com can't be done with it — configure that at the DNS/registrar level
(see Custom domains).