Headers
Response headers go in a _headers file at the root of the deploy. The format is a subset of the one Netlify and Cloudflare Pages read.
The default headers
Every response carries these three, except a redirect of a whole hostname to the canonical domain, which carries only Location:
X-Frame-Options: DENYX-Content-Type-Options: nosniffReferrer-Policy: strict-origin-when-cross-origin
HTML (an extensionless path, .html or .htm) is served with Cache-Control: public, max-age=600, and everything else with public, max-age=86400. Your files also carry ETag and Last-Modified, so a returning browser can revalidate and get a 304, and range requests are supported.
_headers can override or drop the three default headers, and override Cache-Control, on the paths you choose. Two things stay as they are. The Kamakiri not-found page always carries the default set. Every 4xx and 5xx response is served no-store, whatever Cache-Control a block sets; redirects stay cacheable.
There is no Strict-Transport-Security, and you cannot add one.
The file
A _headers file is a series of blocks. A block is a path line, starting with / and not indented, followed by indented header lines.
# a full-line comment starts with #
/path/*
X-Custom-Header: value
Cache-Control: public, max-age=3600
! X-Frame-Options
- Indentation is one or more spaces or tabs.
- A blank line, or the end of the file, closes a block. A comment does not.
- Header names are case-insensitive. Paths are case-sensitive.
- The file must be UTF-8. A leading byte-order mark is ignored, and
\nand\r\nline endings are accepted.
Only a file named exactly _headers at the root of the deploy is read as headers, and it is not served. A _Headers, or a sub/_headers, is served as an ordinary file.
Blocks
- Paths. A path is exact, such as
/about, or a prefix with a single trailing*, such as/assets/*. The*matches everything after the prefix, slashes included, so/blog/*matches/blog/and/blog/2024/postbut not/blog. - Set a header.
Name: value. The name is everything before the first:. The value is trimmed at both ends and kept as written inside, and it cannot be empty. - Several values. Repeat the name, and each line is sent as its own header. For
Cache-Control, write one line with commas instead, since repeatedCache-Controllines are read inconsistently by caches and proxies. - Drop a header.
! Nameremoves a default header on the block’s paths. For a header that is not a default, it does nothing. - Overlapping blocks. Blocks apply in file order, so when two matching blocks set the same header, the later one wins.
A header you set replaces the default of the same name on the block’s paths only. Other paths keep the default.
What is rejected
A rejected line fails the whole deploy with a line-numbered error. When you deploy a directory, kamakiri deploy checks the file before uploading anything. An archive is checked by the service after the upload. Either way, the live site does not change.
- A byte outside printable ASCII in a value. Values are limited to
0x20to0x7e, which excludes tabs, line breaks and non-ASCII text. - A stray carriage return or a NUL byte. A lone
\ror a NUL anywhere in the file is rejected.\r\nis a line ending. - A
{or}in a value. Every value is a literal string. - An invalid header name. The name must be an RFC 7230 token, so it cannot contain a space. The name ends at the first colon, so
Na:me: vsets the headerNatome: v. Set-Cookie. Rejected whatever its letter case, soset-cookieis rejected too.Strict-Transport-Security. Browsers keep enforcing it until itsmax-ageruns out, even after you remove it.- Path placeholders and interpolation. A
:namesegment, a*anywhere but the single trailing position, and:splatin a value. A colon inside a segment, as in/a:b, is an ordinary path. - A structural error. A line that is neither a path, a directive nor a comment; an indented line outside a block; a block with no directives; and a header both set and dropped in the same block.
What is dropped
These names are dropped rather than rejected, so a file from Netlify or Cloudflare Pages still deploys. When you deploy a directory, kamakiri deploy prints a warning for each dropped line and carries on. From an archive, the lines are dropped without a warning.
- Framing and hop-by-hop.
Connection,Proxy-Connection,Keep-Alive,Transfer-Encoding,TE,Trailer,Upgrade,Content-Length. - Set by the platform.
Content-Type, which follows the file extension,Content-Encoding,Content-Range,Accept-Ranges,Host,Date,Server,Age,Alt-Svc,Allow, andLocation, which belongs in_redirects.
Limits
A file over any of these is rejected, not truncated.
- File size. 64 KB.
- Blocks. 100.
- Header lines per block. 64, counting the lines the platform drops.
- Header bytes in total. 32 KB, counting the names and values you set and the names in
! Namelines, but not the lines the platform drops. - Line length. 2,048 bytes.
Portability
Blocks of Name: value lines under an exact path, /*, or a prefix such as /assets/* behave the same on Kamakiri, Netlify and Cloudflare Pages. Netlify ignores ! Name, so a file that drops headers ports to Cloudflare Pages only. A file from either platform that uses a :placeholder or any other * is rejected here.
Examples
# A custom header on every response
/*
X-Custom-Header: hello
# Long-lived caching for hashed assets (one comma-separated line)
/assets/*
Cache-Control: public, max-age=31536000, immutable
# Override a default site-wide
/*
Referrer-Policy: no-referrer
# Drop a default for one path only; other paths keep X-Frame-Options: DENY
/embed/*
! X-Frame-Options