Redirects and headers
Rules can come from two places, with the same behaviour:
- a
_redirectsfile and a_headersfile at the root of the deploy; [[redirects]]and[[headers]]innetlify.tomlorseedrop.toml(same format;seedrop.tomlwins if both exist). These come after the_redirects/_headersrules.
Lines that are not understood are listed in the deploy report under rules.problems (file and line) and are ignored. The rule files themselves (_redirects, _headers, netlify.toml, seedrop.toml) are never served as public files.
_redirects
One rule per line: from to [status][!] [Condition=a,b]. Text after # is a comment.
# permanent redirect (301 is the default)
/old-page /new-page
/blog/* /articles/:splat 301
/products/:id /p/:id 302
# rewrite: serve another file, URL stays the same
/app/* /app/index.html 200
# custom 404 for a section
/docs/* /docs/404.html 404
# force: applies even if /promo.html exists
/promo /sale 302!
# only for requests that arrive on this host
https://example.com/* https://www.example.com/:splat 301!
# conditions
/ /pt/ 302 Language=pt
/ /br/ 302 Country=br
# proxy to a public address
/api/* https://api.example.com/:splat 200
# send to a function (needs SDH_FUNCTIONS=1 on the server)
/api/* /.netlify/functions/:splat 200
What is supported
- Status codes: 200, 301, 302, 303, 307, 308, 404, 410. Default: 301.
- Splat:
*captures the rest of the path; use it as:splatin the target./blog/*also matches/blog. - Placeholders:
:namecaptures one path segment. - Absolute origin:
https://www.example.com/*only matches requests whoseHostis that name. The protocol only counts when the server knows it (TLS on the socket, orX-Forwarded-ProtowithSDH_TRUST_PROXY=1). - Order: the first matching rule wins;
_redirectsbefore the config file. - Shadowing: a rule without
!does not apply when the requested path exists as a file (404 rules always apply). Add!to force it. - Query string: kept on redirects when the target has none.
- Conditions:
Language=matches theAccept-Languageheader (ptalso matchespt-BR).Country=uses theCF-IPCountryheader, so it only means something with Cloudflare in front — do not use it for anything that must be secure. - Targets: a path starting with
/(not//), or a fullhttp:///https://address. Anything else (//host,javascript:…) is refused with a problem in the report. If a path target turns into an outside address after:splatis filled in, the rule is ignored. - Proxy: a 200 rule whose target is
http(s)://…fetches that address and returns its answer. Only public internet addresses are allowed — checked again at connection time, so DNS cannot switch to a private address afterwards — with no redirect following, a 10-second timeout and a 10 MB limit read as a stream. OnlyGET/HEADreach it, and only theAcceptheader is forwarded.
Not supported
- Other conditions (
Role=,Cookie=) and query-parameter matching such asid=:id: the rule is parsed but never matches. Nothing redirects by mistake. - In
netlify.toml:query,signedandheadersinside[[redirects]]are ignored with a warning in the report. - Anonymous drops ignore rules that point outside the site.
_headers
A path line (starting with /, wildcards and :placeholders allowed) followed by indented Name: value lines.
/*
X-Frame-Options: DENY
Referrer-Policy: strict-origin-when-cross-origin
/assets/*
Cache-Control: public, max-age=31536000, immutable
/*.md
Content-Type: text/markdown; charset=utf-8
The same in netlify.toml:
[[headers]]
for = "/assets/*"
[headers.values]
Cache-Control = "public, max-age=31536000, immutable"
All matching blocks apply, in order; a later value replaces an earlier one with the same name.
- **
Set-Cookieis always dropped** (with a problem in the report): every site shares the base domain with the dashboard, so a cookie could leak to other sites. Use a function to set cookies. - On anonymous drops,
Content-Security-Policy,Service-Worker-AllowedandAccess-Control-*are dropped too, and the page is alwaysnoindex.
Headers SeeDropHost sets by itself
| Header | Value |
|---|---|
Content-Type | from the file extension (HTML, CSS, JS, JSON, TXT, XML, SVG, images, fonts, PDF, MP4, webmanifest); others application/octet-stream |
ETag | from the content hash; If-None-Match answers 304 |
Cache-Control | public, max-age=0, must-revalidate |
X-Content-Type-Options | nosniff |
X-SDH-Deploy | id of the deploy that answered |
X-Robots-Tag | noindex on drafts, older deploys and anonymous drops |
X-SDH-Preview | 1 when that noindex was added by SeeDropHost (not by your _headers) — the migration check uses it |
Clean URLs, trailing slash and 404
/aboutserves/about.html;/docs/serves/docs/index.html.- Each page has one address: if only
about/index.htmlexists,/aboutanswers301to/about/; if onlyabout.htmlexists,/about/answers301to/about. The query string is kept. - When nothing matches,
/404.htmlis served with status 404 if it exists.