Každý server občas spadne – po restartu kvůli aktualizaci, při přetížení nebo když má výpadek provider. Když máte web za Cloudflare proxy (oranžový mráček), návštěvník v tu chvíli místo vašeho webu uvidí tohle:

Stránka je anglicky, technická, bez loga a bez kontaktu. Zákazník neví, jestli firma ještě existuje, a u e-shopu se nedozví, že by mohl objednat telefonem.
Na svém webu jsem to vyřešil jedním malým Cloudflare Workerem. Za normálního provozu nedělá nic a teprve když Cloudflare nedosáhne na server, vrátí stránku v barvách webu, česky a s kontaktem.

Proč ne vestavěné „Error Pages“?
Cloudflare má v zóně sekci Error Pages (Custom Errors), která dělá přesně tohle, jenže jen na placených plánech Pro a vyšších. Na Free plánu tam uvidíte jen tlačítko „Upgrade plan“.

Worker je na Free plánu zdarma, jen má denní limit požadavků. Na ten si dejte pozor, víc o něm v kroku 5.
Jak to funguje
Worker stojí mezi návštěvníkem a serverem. Každý požadavek pošle beze změny dál na server (fetch(request)) a odpověď vrátí. Zasáhne, jen když přijde jedna z chyb, které generuje sám Cloudflare, protože se na server nedostal:
| Kód | Co znamená |
|---|---|
| 520 | server vrátil nesmysl nebo prázdnou odpověď |
| 521 | server odmítá spojení (web server neběží) |
| 522 | spojení vypršelo (server neodpovídá) |
| 523 | server je nedosažitelný |
| 524 | server neodpověděl včas |
| 525, 526 | problém s SSL mezi Cloudflare a serverem |
| 530 | chyba DNS originu |
Důležité je, co Worker nedělá, aby stránku neukázal omylem:
- Neřeší chyby z vašeho serveru (500, 502, 503). Když spadne PHP nebo databáze, má to být vidět. Jinak byste chybu aplikace zamaskovali a nevěděli o ní.
- Zasáhne jen u
GET/HEADsAccept: text/html, tedy když prohlížeč chce stránku. Odeslaný formulář (POST), AJAX,wp-json, obrázky ani API klienti stránku výpadku nedostanou – dostanou původní chybu. Formulář tak nikdy nepředstírá, že se odeslal, a JSON klient nedostane HTML. - Vrací status 503 s
Retry-AfteraCache-Control: no-store. Google tak pozná dočasný výpadek a nic se neuloží do cache.
Krok 1: Vytvořte Worker
V levém menu účtu otevřete Compute → Workers & Pages a klikněte na Create application.

Zvolte Start with Hello World!.

Pojmenujte Worker (třeba fallback) a klikněte na Deploy. Kód z ukázky teď nevadí, za chvíli ho přepíšeme. Worker zatím nikde neběží – dokud mu nepřiřadíte route, žádného webu se nedotkne.

Krok 2: Vložte kód
V detailu Workeru klikněte vpravo nahoře na Edit code, smažte ukázku, vložte kód níž a klikněte na Deploy.

Tohle je zjednodušená verze pro jeden web. Upravte SITE (název, barvy, e-mail):
// Errors Cloudflare generates itself when it cannot reach the origin.
const ORIGIN_DOWN = new Set([520, 521, 522, 523, 524, 525, 526, 530]);
const SITE = {
name: "Můj web",
bg: "#ffffff",
text: "#1a1a1a",
accent: "#1e40af",
contact: "[email protected]",
};
export default {
async fetch(request) {
let response = null;
try {
response = await fetch(request);
} catch (e) {
// Network error on the way to the origin - treat it as down.
}
if (response && !ORIGIN_DOWN.has(response.status)) return response;
const wantsHtml = ["GET", "HEAD"].includes(request.method)
&& (request.headers.get("accept") || "").includes("text/html");
if (!wantsHtml) {
return response ?? new Response("Service Unavailable", {
status: 503,
headers: { "cache-control": "no-store" },
});
}
return new Response(request.method === "HEAD" ? null : page(SITE), {
status: 503,
headers: {
"content-type": "text/html; charset=utf-8",
"cache-control": "no-store",
"retry-after": "300",
"x-fallback": "1",
},
});
},
};
function page(s) {
return `<!doctype html>
<html lang="cs"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="robots" content="noindex">
<title>${s.name} - za chvíli jsme zpět</title>
<style>
body { margin: 0; min-height: 100vh; display: grid; place-items: center;
background: ${s.bg}; color: ${s.text};
font: 18px/1.6 system-ui, sans-serif; padding: 24px; box-sizing: border-box; }
main { max-width: 34rem; }
h1 { color: ${s.accent}; line-height: 1.2; }
a { color: ${s.accent}; }
</style></head>
<body><main>
<h1>Za chvíli jsme zpět</h1>
<p>Web je dočasně nedostupný kvůli údržbě nebo technickému problému.
Už na tom pracujeme, zkuste to prosím za pár minut.</p>
<p>V naléhavém případě nám napište na
<a href="mailto:${s.contact}">${s.contact}</a>.</p>
</main></body></html>`;
}
Pár poznámek ke kódu:
- Všechno musí být uvnitř Workeru. Logo, CSS ani obrázky se z mrtvého serveru nenačtou. Logo vložte jako data URI (
data:image/svg+xml;base64,...), fonty jde tahat z Google Fonts. - Texty si přizpůsobte. Kromě e-mailu tam můžete přidat telefon, odkaz na sociální sítě nebo cokoli dalšího, co se zákazníkovi při výpadku hodí.
- Náhled v editoru vpravo posílá požadavky z IP adres Cloudflare. Pokud máte v zóně pravidla, která blokují IP adresy, uvidíte tam
Error 1106 Access denied. To nevadí, testujte na skutečné adrese (krok 4).
Krok 3: Vypněte adresu workers.dev
Každý nový Worker dostane veřejnou adresu <jméno>.<účet>.workers.dev. Pro tenhle účel ji nepotřebujete a na ní by fetch(request) stejně nedával smysl. V detailu Workeru otevřete záložku Domains a v části Worker URL vypněte přepínače u Production i Preview.

Na stejné záložce pak uvidíte i route, které Worker obsluhuje.
Krok 4: Bezpečný test bez vypínání serveru
Shazovat kvůli testu ostrý web asi nechcete. Stačí v zóně založit testovací subdoménu, která vede na adresu, kde nikdy nic neběží, a Cloudflare na ní pak vždy vrátí 522.
V zóně otevřete DNS → Records → Add record a vyplňte:
- Type: A
- Name:
fallback-test - IPv4 address:
192.0.2.1(dokumentační adresa z RFC 5737, nikam nevede) - Proxy status: Proxied (oranžový mráček – bez něj Worker neběží)


Krok 5: Přiřaďte Worker k webu (route) a nastavte fail open
V zóně otevřete Workers Routes a klikněte na Add route.
- Route: nejdřív
fallback-test.vasedomena.cz/*, po otestovánívasedomena.cz/*awww.vasedomena.cz/*(každá jako samostatná route). - Worker: váš Worker.
- Request limit failure mode: rozbalte a zvolte Fail open (proceed).

Tohle poslední nastavení je v celém návodu nejdůležitější. Free plán má limit 100 000 požadavků na Workery denně, a to za celý účet, ne za doménu. Výchozí volba je Fail closed (block): když limit vyčerpáte, Cloudflare začne požadavky na vaše weby blokovat. Pojistka proti výpadku by tak sama způsobila výpadek. S Fail open jdou požadavky po vyčerpání limitu rovnou na server, jako by Worker neexistoval.

Route pište na konkrétní hostnamy, ne *.vasedomena.cz/*. Hvězdička na začátku by zachytila i vývojové a interní subdomény, kde Worker nechcete.

Krok 6: Ověřte, že to funguje (a že to neruší)
Testovací host by měl vrátit stránku výpadku:
curl -sI -H "accept: text/html" https://fallback-test.vasedomena.cz/
# HTTP/2 503, x-fallback: 1, retry-after: 300, cache-control: no-store
API požadavek na stejném hostu má dostat původní chybu, ne HTML:
curl -sI -H "accept: application/json" https://fallback-test.vasedomena.cz/
# HTTP/2 522
A ostrý web se má po přidání route chovat přesně jako předtím. Projděte úvodní stránku, pár podstránek, administraci, odeslání formuláře. Hlavička x-fallback se na běžícím webu nesmí objevit nikdy.
Stránku si otevřete i v prohlížeči a na mobilu. Takhle vypadá stránka z ukázkového kódu výše:

Pozor na limit: počítá se každý požadavek, ne jen výpadky
Worker běží stále, ne jen při výpadku
Worker neví předem, že server spadl – zjistí to až z jeho odpovědi. Proto běží na každém požadavku, který route projde, i když web normálně funguje.
A návštěva jedné stránky není jeden požadavek: prohlížeč si zvlášť stáhne HTML, každý CSS a JS soubor, každý obrázek, fonty, AJAX volání. Na WordPressu to bývá 30-80 požadavků na jedno zobrazení. Nepomůže ani cache Cloudflare – Worker na route běží před ní.
Kolik to dělá reálně, uvidíte v detailu Workeru na záložce Metrics (Invocations) – i rozpad podle hostnamu:

Můj web hilgert.cz dělá zhruba 2 500-3 000 volání denně, takže se do limitu vejde s velkou rezervou. Pokud ale máte v účtu další Workery nebo chcete Worker přidat i na další weby, volání se sčítají. Web s větším provozem (e-shop, zpravodajský web) se přes 100 tisíc dostane snadno. Pak jsou dvě rozumné cesty:
- Workers Paid za 5 USD měsíčně – 10 milionů požadavků měsíčně v ceně.
- Zůstat na Free s fail open a smířit se s tím, že po vyčerpání limitu se při výpadku ukáže zase obyčejná chyba Cloudflare.
Optimalizace: vynechte statické soubory
Stránku výpadku potřebuje jen HTML. Obrázky, CSS a JS z wp-content a wp-includes Worker jen pošle dál beze změny, ale do limitu se počítají. Můžete je z Workeru vyjmout: v Workers Routes přidejte další route se stejným hostnamem a konkrétní cestou a jako Worker zvolte None:
www.vasedomena.cz/wp-content/*→ Worker: Nonewww.vasedomena.cz/wp-includes/*→ Worker: None
Cloudflare u každého požadavku použije nejkonkrétnější route, takže statika půjde mimo Worker a všechno ostatní dál přes něj. Při výpadku se pak u obrázků ukáže obyčejná chyba Cloudflare, ale tu stejně nikdo nevidí – prohlížeč je potřebuje jen pro stránku, která se nenačetla.
Kolik to ušetří, záleží na webu – na mých webech to bylo 10-70 % volání. Méně ušetří malý web, kde nejspíš většinu provozu tvoří roboti stahující jen HTML a vracející se návštěvníci mají statiku v cache prohlížeče. Víc e-shop se skutečnými zákazníky a spoustou produktových fotek. Změřte si to na svém webu v Metrics před změnou a den po ní.
Co to neřeší
- Když server neodpovídá vůbec (522), Cloudflare čeká na spojení zhruba 20 sekund, než Worker dostane chybu (naměřil jsem 19,7 s). Zkrátit to bezpečně nejde – Worker nepozná pomalou stránku od mrtvého serveru.
- Worker běží jen na záznamech s oranžovým mráčkem. Weby, které proxy nemají (třeba kvůli 100s limitu Cloudflare na dlouhé požadavky), stránku výpadku neukážou. Dlouhé crony je lepší pouštět z příkazové řádky na serveru a dlouhá API dát na subdoménu bez proxy – hlavní web pak může za proxy zůstat.
Komentáře
Zatím žádné komentáře. Buďte první.
Komentáře schvaluji ručně, zobrazí se po schválení.