Аддавайце асэты з хэшам у імені з загалоўкам Cache-Control: public, max-age=31536000, immutable, а HTML аддавайце браўзерам з no-cache і задавайце для Cloudflare асобны кароткі edge TTL. Зрабіце HTML прыдатным для кэшавання праз Cache Rule, пакіньце Browser Cache TTL у рэжыме Respect Existing Headers і ачышчайце HTML апошнім крокам кожнага дэплою. Тады браўзер пры кожным візіце правярае невялікі дакумент, edge сам адказвае на большасць такіх праверак, а новы рэліз бачны адразу пасля адказу на запыт purge.
Дзве палітыкі кэша для двух відаў файлаў
Статычная зборка стварае файлы двух відаў. Файлы з адбіткам, напрыклад /_astro/index.3f9c1a.js, маюць у імені хэш змесціва. Калі змесціва мяняецца, мяняецца і URL, таму копія старога URL можа год ляжаць у любым кэшы. HTML, фіды, sitemap і robots.txt захоўваюць сталыя URL, і любая іх кэшаваная копія можа састарэць.
Таму хэшаваныя файлы атрымліваюць найдаўжэйшы тэрмін жыцця і ніколі не патрабуюць ачысткі. Сталыя URL атрымліваюць браўзерную палітыку з паўторнай праверкай і edge-палітыку, якую можна скінуць. Асноўны выйгрыш у хуткасці дае першая група: наведвальнік, які вярнуўся, загружае невялікі HTML-дакумент, часта як адказ 304, а ўсе скрыпты, стылі і шрыфты бярэ з лакальнага кэша. Менавіта таму кэшаванне з'яўляецца адным з самых танных спосабаў палепшыць Time to First Byte і Largest Contentful Paint, як тлумачыць дапаможнік па Core Web Vitals.
Два правілы дэплою робяць гэтую схему бяспечнай. Загружайце новыя хэшаваныя файлы да таго, як HTML пераключыцца на новы рэліз. Захоўвайце хэшаваныя файлы папярэдняга рэлізу, пакуль HTML, які на іх спасылаецца, можа заставацца ў якім-небудзь кэшы, бо ўкладка, адкрытая да дэплою, запытае старыя імёны.
Browser TTL і edge TTL: два розныя гадзіннікі
Кэш браўзера належыць наведвальніку, і ніякія вашы дзеянні пасля адказу не выдаляць з яго запіс. Edge-кэш належыць Cloudflare, і purge API ачышчае яго за секунды. Таму доўгі browser TTL дапушчальны толькі для URL, змесціва якіх ніколі не мяняецца. Для HTML дырэктыва no-cache дазваляе браўзеру захаваць старонку, але патрабуе ўмоўнага запыту перад кожным паўторным выкарыстаннем.
Edge TTL можна задаць праз s-maxage, праз Cloudflare-CDN-Cache-Control або CDN-Cache-Control ці праз Cache Rule. Паколькі яго можна скінуць, edge TTL для HTML з'яўляецца страхоўкай, а не механізмам абнаўлення. Калі purge не спрацаваў, найгоршая затрымка на edge роўная тэрміну свежасці плюс акно stale-while-revalidate: для max-age=300, stale-while-revalidate=60 гэта 360 секунд.
Спачатку праверце адну наладу зоны. Browser Cache TTL па змаўчанні роўны чатыром гадзінам на ўсіх тарыфах, і Cloudflare перавызначае тэрміны origin, меншыя за гэтае значэнне. HTML, які павінен правярацца кожны раз, тады можа гадзінамі жыць у браўзерах, куды не дацягнецца ніводны purge. Усталюйце Browser Cache TTL у Respect Existing Headers, як апісана ў дакументацыі пра edge і browser TTL.
Як Cloudflare чытае s-maxage, stale-while-revalidate і stale-if-error
Браўзеры ігнаруюць s-maxage, таму Cache-Control: public, max-age=0, s-maxage=300 здаецца відавочным загалоўкам для HTML, і Cloudflare сапраўды выкарыстоўвае яго як edge TTL. Падвох апісаны ў RFC 9111: s-maxage таксама нясе семантыку proxy-revalidate, гэта значыць агульны кэш не павінен аддаваць састарэлы адказ без папярэдняй праверкі.
На тарыфах Free, Pro і Business функцыя Origin Cache Control уключана заўсёды, і Cloudflare выконвае гэтае правіла. Яго дакументацыя па рэвалідацыі называе s-maxage, must-revalidate, proxy-revalidate і no-cache дырэктывамі, якія адключаюць выдачу састарэлых копій. Побач са stale-while-revalidate яны ператвараюць UPDATING у EXPIRED, і наведвальнік чакае origin. Гэтыя ж дырэктывы прымушаюць Cloudflare ігнараваць stale-if-error. Загаловак накшталт max-age=0, s-maxage=300, stale-while-revalidate=60, stale-if-error=86400 дае толькі edge TTL у 300 секунд і нічога больш.
Калі stale-while-revalidate дзейнічае, рэвалідацыя асінхронная: першы запыт пасля заканчэння тэрміну атрымлівае састарэлую копію з cf-cache-status: UPDATING, а Cloudflare абнаўляе яе ў фоне. stale-if-error спрацоўвае толькі на адказы 5xx ад origin. Always Online адключае абедзве дырэктывы, а значэнні TTL павінны быць цэлымі лікамі.
Каб задаць браўзерам і edge розныя палітыкі без s-maxage, выкарыстоўвайце адрасны загаловак. Cloudflare правярае спачатку Cloudflare-CDN-Cache-Control, потым CDN-Cache-Control, потым Cache-Control. Калі CDN-загаловак ёсць, Cache-Control даходзіць да браўзера без змен і не ўплывае на edge, а Cloudflare-CDN-Cache-Control Cloudflare далей не перадае. Для HTML:
Cache-Control: no-cache
Cloudflare-CDN-Cache-Control: max-age=300, stale-while-revalidate=60, stale-if-error=86400
Браўзер правярае старонку кожны раз. Cloudflare захоўвае яе пяць хвілін, да хвіліны аддае састарэлую копію падчас абнаўлення і суткі аддае апошнюю працоўную копію, калі origin падае. Правілы прыярытэту апісаны ў дакументацыі пра CDN-Cache-Control.
Cache Rules: давяраць origin ці перавызначаць
Па змаўчанні Cloudflare вырашае, ці кэшаваць адказ, па пашырэнні файла і не кэшуе HTML і JSON. URL накшталт /writing/post/ не мае пашырэння, таму без правіла кожны запыт старонкі атрымлівае DYNAMIC і ідзе на origin.
Cache Rule з Eligible for cache мае тры рэжымы Edge TTL. respect_origin выконвае вашы загалоўкі, а без іх бярэ значэнні па змаўчанні, напрыклад 120 хвілін для адказу 200. bypass_by_default выконвае загалоўкі, а без іх не кэшуе. override_origin ігнаруе загалоўкі і задае TTL прымусова, прычым мінімум залежыць ад тарыфу: 2 гадзіны на Free, 1 гадзіна на Pro і 1 секунда на Business і Enterprise. Browser TTL можна ўзяць з origin, перавызначыць або адключыць.
Калі origin аддае прадуманыя загалоўкі, давярайце ім. Гэты набор правілаў для фазы http_request_cache_settings робіць хост прыдатным для кэшавання, а потым выключае дынамічныя шляхі. Для налад кэша перамагае апошняе правіла, якое супала, таму правіла bypass стаіць апошнім.
{
"rules": [
{
"description": "Cache the site according to origin headers",
"expression": "http.host eq \"example.com\"",
"action": "set_cache_settings",
"action_parameters": {
"cache": true,
"edge_ttl": { "mode": "respect_origin" },
"browser_ttl": { "mode": "respect_origin" }
}
},
{
"description": "Never cache API routes and previews",
"expression": "http.host eq \"example.com\" and (starts_with(http.request.uri.path, \"/api/\") or starts_with(http.request.uri.path, \"/preview/\"))",
"action": "set_cache_settings",
"action_parameters": { "cache": false }
}
]
}
Не дадавайце http.request.method eq "GET" у правіла кэшавання. Cloudflare папярэджвае, што ачыстка па асобным URL можа не спрацаваць, калі правіла супадае толькі з GET, бо запыты purge унутры выкарыстоўваюць іншы метад. Поўны пералік параметраў прыведзены ў наладах Cache Rules.
Загалоўкі Nginx без пасткі наследавання add_header
Nginx наследуе дырэктывы add_header з верхняга ўзроўню, толькі калі на бягучым узроўні іх няма. Як толькі location дадае ўласны Cache-Control, усе загалоўкі ўзроўню server у ім моўчкі знікаюць, у тым ліку загалоўкі бяспекі.
server {
add_header X-Content-Type-Options "nosniff" always;
location ^~ /_astro/ {
# X-Content-Type-Options is no longer sent for this location.
add_header Cache-Control "public, max-age=31536000, immutable";
}
}
Пераноснае рашэнне: вынесці агульныя загалоўкі ў сніпет і падключаць яго праз include у кожным блоку з уласным add_header. У Nginx 1.29.3 і навейшых, у тым ліку ў стабільнай галіне 1.30, дырэктыва add_header_inherit merge замест гэтага дапісвае наследаваныя загалоўкі. З merge не трымайце Cache-Control у блоку server, інакш location будуць адпраўляць яго двойчы.
Паводле дакументацыі модуля headers, add_header без always ужываецца толькі да адказаў 200, 201, 204, 206, 301, 302, 303, 304, 307 і 308. Выкарыстоўвайце always для загалоўкаў бяспекі і для no-store на старонках памылак, але ніколі для доўгатэрміновых загалоўкаў кэша, інакш 404 на памылку ў імені асэта стане immutable на год. Таксама не змешвайце expires з add_header Cache-Control, бо гэта дае два палі загалоўка. Блок server для статычнай зборкі, без TLS і журналявання:
server {
listen 443 ssl;
http2 on;
server_name example.com;
root /srv/example.com/current;
index index.html;
gzip on;
gzip_vary on;
gzip_static on;
gzip_types text/css application/javascript application/json
application/xml application/rss+xml image/svg+xml text/plain;
location ^~ /_astro/ {
include snippets/security-headers.conf;
add_header Cache-Control "public, max-age=31536000, immutable";
try_files $uri =404;
}
location ~* \.(?:avif|webp|png|jpe?g|gif|svg|ico|woff2)$ {
include snippets/security-headers.conf;
add_header Cache-Control "public, max-age=86400";
try_files $uri =404;
}
location ~* \.(?:xml|txt)$ {
include snippets/security-headers.conf;
add_header Cache-Control "public, max-age=300";
add_header Cloudflare-CDN-Cache-Control "max-age=3600, stale-if-error=86400";
add_header Cache-Tag "html";
try_files $uri =404;
}
location / {
include snippets/security-headers.conf;
add_header Cache-Control "no-cache";
add_header Cloudflare-CDN-Cache-Control "max-age=300, stale-while-revalidate=60, stale-if-error=86400";
add_header Cache-Tag "html";
try_files $uri $uri/ =404;
}
error_page 404 /404.html;
location = /404.html {
internal;
include snippets/security-headers.conf;
add_header Cache-Control "no-store" always;
}
}
Для gzip_static патрэбна зборка з --with-http_gzip_static_module, гэта відаць у вывадзе nginx -V. Origin павінен прымаць злучэнні толькі ад Cloudflare, каб ніхто не абыходзіў кэш. Наладу файрвола для гэтага разбірае дапаможнік па абароне Linux VPS.
ETag, Last-Modified і рэвалідацыя
З etag on, які ўключаны па змаўчанні, Nginx аддае для статычных файлаў ETag і Last-Modified і адказвае на ўмоўныя запыты кодам 304. Яго ETag будуецца з часу змянення і памеру файла, а не з хэша змесціва. Таму дэплой, які перазапісвае ўсе файлы, мяняе ўсе ETag, і першая рэвалідацыя пасля рэлізу вяртае поўны адказ 200. Калі рэліз раздаюць некалькі сервераў origin, час файлаў на іх павінен супадаць, напрыклад пры капіраванні праз rsync -a, інакш вынік рэвалідацыі будзе залежаць ад таго, які сервер адказаў.
Пакуль копія свежая, Cloudflare сам адказвае на рэвалідацыі браўзераў. Пасля заканчэння тэрміну ён адпраўляе ў Nginx умоўны запыт, і адказ 304 падаўжае TTL без перадачы цела. Калі Cloudflare мяняе кадзіроўку, ён ператварае моцны ETag у слабы W/"...". Гэта бяспечна, бо If-None-Match выкарыстоўвае слабае параўнанне. Уключайце Respect Strong ETags, толькі калі кліенту сапраўды патрэбныя пабайтавыя валідатары.
Сцісканне: gzip на origin, Brotli і Zstandard на edge
Cloudflare запытвае ў origin accept-encoding: br, gzip і можа перакадаваць атрыманы адказ. Наведвальнікам ён аддае gzip, Brotli або Zstandard у залежнасці ад Accept-Encoding, тарыфу і Compression Rules. Па змаўчанні зоны Free аддаюць перавагу Zstandard, Pro і Business выбіраюць Brotli, а Enterprise выкарыстоўвае gzip. Сціскаюцца толькі адказы 200, 403 і 404, як пазначана ў дакументацыі пра сцісканне.
Для origin за Cloudflare дастаткова gzip. Сціскайце тэкставыя файлы загадзя падчас зборкі для gzip_static або ўключыце сцісканне на ляту праз gzip on. Па змаўчанні gzip_types утрымлівае толькі text/html, таму пералічыце іншыя тэкставыя тыпы і пакіньце gzip_vary on. Brotli і Zstandard не ўваходзяць у набор модуляў nginx.org і патрабуюць старонніх модуляў. Не стаўце Cache-Control: no-transform на тэкставыя адказы, бо тады Cloudflare не сцісне несціснуты адказ.
Cookies, query string і ключ кэша
Ключ кэша Cloudflare па змаўчанні ўтрымлівае схему, хост, шлях і поўны query string, а таксама некалькі загалоўкаў запыту, напрыклад Origin. Cookies у яго не ўваходзяць, таму кэшаваны URL ніколі не павінен залежаць ад cookie. Старонкі для аўтарызаваных карыстальнікаў, кошыкі, API ці бэкенд агента выводзьце праз правіла bypass з private або no-store ці на асобны хост. Як звычайна выглядае такі дынамічны шлях запыту, паказвае дапаможнік па архітэктуры AI-агента для production.
Загаловак адказу Set-Cookie ламае кэшаванне. Пры Eligible for cache Cloudflare захоўвае cookie і не кладзе адказ у кэш, таму кожны запыт атрымлівае MISS. Праверце, што ні модуль, ні балансавальнік нагрузкі не дадаюць cookies да статычных файлаў, або выдаляйце іх праз Cache Response Rule.
Кожны асобны query string стварае асобны запіс, таму спасылкі з ?utm_source= пачынаюць з промаху. Гэта зніжае долю трапленняў, але не карэктнасць. Узровень кэшавання Ignore Query String дзейнічае толькі для статычных пашырэнняў файлаў, а ўласныя ключы па параметрах query string залежаць ад тарыфу. Sort query string даступная на ўсіх тарыфах.
Стратэгіі purge і ачыстка з CI
З красавіка 2025 года ўсе метады purge даступныя на любым тарыфе:
- Single URL выдаляе дакладныя URL, да 100 за запыт (500 на Enterprise), калі зборка ведае, што змянілася.
- Prefix выдаляе ўсё пад шляхам накшталт
example.com/writing/, уключна з варыянтамі з query string. - Tag выдаляе ўсе аб'екты, у адказе якіх быў адпаведны загаловак
Cache-Tag. Cloudflare прыбірае гэты загаловак раней, чым адказ убачыць наведвальнік. Пазначце HTML, фіды і sitemap тэгамhtml, і адзін выклік ачысціць усе сталыя URL, а хэшаваныя асэты застануцца ў кэшы. - Hostname выдаляе ўсё для аднаго хоста.
- Everything ачышчае ўсю зону і накіроўвае ўвесь трафік на origin, пакуль кэш не запоўніцца зноў, таму пакіньце яго на крайні выпадак.
Запыты па hostname, tag, prefix і purge everything дзеляць ліміт акаўнта: 5 запытаў у хвіліну на Free, 5 у секунду на Pro, 10 у секунду на Business і 50 у секунду на Enterprise, паводле дакументацыі пра purge. Выкарыстоўвайце API-токен толькі з правам Cache Purge на адну зону і захоўвайце яго як сакрэт CI:
#!/usr/bin/env bash
set -euo pipefail
: "${CF_API_TOKEN:?}" "${CF_ZONE_ID:?}" "${ORIGIN_HOST:?}"
# 1. Hashed assets first, without --delete, so old pages keep working.
rsync -a dist/_astro/ "deploy@${ORIGIN_HOST}:/srv/example.com/current/_astro/"
# 2. Everything else. Excluded paths are not deleted.
rsync -a --delete --exclude '/_astro/' dist/ "deploy@${ORIGIN_HOST}:/srv/example.com/current/"
# 3. Purge HTML, feeds and sitemaps at the edge.
curl -fsS --max-time 30 -X POST \
"https://api.cloudflare.com/client/v4/zones/${CF_ZONE_ID}/purge_cache" \
-H "Authorization: Bearer ${CF_API_TOKEN}" \
-H "Content-Type: application/json" \
--data '{"tags":["html"]}' </dev/null \
| jq -e '.success == true' >/dev/null
Вялікім сайтам лепш загружаць зборку ў каталог рэлізу і атамарна пераключаць symlink. Паспяховы адказ purge азначае толькі тое, што запыт прыняты, таму завяршайце задачу загрузкай змененай старонкі і праверкай маркера зборкі, напрыклад хэша каміта.
Праверка праз curl -I, cf-cache-status і Age
curl -sSI -H 'Accept-Encoding: zstd, br, gzip' https://example.com/writing/some-post/ \
| grep -iE '^(cache-control|cf-cache-status|age|etag|content-encoding):'
Для кэшаваных запытаў Cloudflare ператварае HEAD у GET, таму curl -I таксама напаўняе кэш. Першы адказ павінен паказаць MISS, другі HIT з Age, гэта значыць колькасцю секунд з моманту кэшавання або рэвалідацыі аб'екта. Кожны статус вызначаны ў дакументацыі пра адказы кэша:
DYNAMIC: запыт не прыдатны для кэшавання, звычайна таму што HTML не пакрыты правілам або ўключаны Development Mode.BYPASS: запыт прыдатны, але адказ не кэшуецца з-заno-store,private,Set-CookieабоVary: *.UPDATING: аддадзена састарэлая копія, пакуль ідзе фонавая рэвалідацыя.EXPIRED: састарэлая копія нанова загружана сінхронна. Калі вы чакаліUPDATING, шукайцеs-maxage,must-revalidateабоno-cache.REVALIDATED: origin пацвердзіў копію адказам 304, пакуль запыт чакаў.STALE: origin не адказаў, і была аддадзена старая копія.
Пасля purge чакайце MISS або EXPIRED пры Tiered Cache і пасля purge everything. Каб праверыць толькі Nginx, звярніцеся да origin з дазволенага хоста камандай curl -sSI --resolve example.com:443:203.0.113.10 https://example.com/, потым дадайце -H 'If-None-Match: "<etag>"' і чакайце 304.
Рэкамендаваныя загалоўкі па тыпах файлаў
| Тып файла | Прыклад | Cache-Control | Палітыка edge | Пасля дэплою |
|---|---|---|---|---|
| JS, CSS, шрыфты, выявы з адбіткам | /_astro/app.3f9c1a.js |
public, max-age=31536000, immutable |
З origin | Нічога |
| HTML-старонкі | /writing/post/ |
no-cache |
max-age=300, stale-while-revalidate=60, stale-if-error=86400 |
Purge тэга html |
| Фіды, sitemap, robots.txt | /rss.xml |
public, max-age=300 |
max-age=3600, stale-if-error=86400 |
Purge тэга html |
| Выявы і іконкі без хэша | /favicon.ico |
public, max-age=86400 |
З origin | Purge URL або новае імя |
| JSON, які мяняецца паміж дэплоямі | /data/stats.json |
public, max-age=60 |
З origin або bypass | Purge URL |
| Старонка 404 | любы адсутны URL | no-store з always |
Не кэшуецца | Нічога |
| API, прэв'ю, адмінка | /api/ |
private, no-store |
Правіла bypass | Нічога |
Значэнні edge з max-age запісваюцца ў Cloudflare-CDN-Cache-Control. Калі вы кэшуеце адказы 404 на edge, пазначайце іх тэгам html, інакш старонка, апублікаваная пазней, застанецца схаванай за закэшаваным 404.
Чэк-ліст дэплою
- Імёны асэтаў утрымліваюць хэшы змесціва, і нішто са сталым URL не пазначана
immutable. - Кожны блок Nginx з
add_headerпадключае загалоўкі бяспекі, аalwaysстаіць толькі на загалоўках бяспекі і адказах з памылкамі. - HTML аддае
no-cacheразам зCloudflare-CDN-Cache-Control, безs-maxageпобач саstale-while-revalidate. gzip_typesахоплівае тэкставыя тыпы, а статычныя адказы не ўтрымліваюцьSet-Cookie.- Browser Cache TTL улічвае існыя загалоўкі, а правіла bypass стаіць апошнім і не абмежавана толькі
GET. - CI загружае асэты раней за HTML, захоўвае старыя хэшаваныя файлы, ачышчае тэг
htmlі падае, каліsuccessне роўны true. - Старонка паказвае
MISS, потымHITзAge, потымUPDATINGпасля заканчэння edge TTL. - Origin прымае трафік толькі ад Cloudflare.