Коли мій сайт на Raspberry Pi вимикався під час блекаутів, Cloudflare показував сторінку 522 – Connection timed out. Я вирішив зробити fallback-сторінку власного дизайну, не витрачаючи ні копійки. У цій статті я детально опишу, як налаштувати безкоштовний Cloudflare Worker.
Що таке Cloudflare Workers
Cloudflare Workers – це безсерверне середовище, у якому можна запускати власний JavaScript-код безпосередньо на edge-серверах Cloudflare. Воно працює як «між прошарок» між користувачем і вашим сайтом, дозволяючи змінювати або замінювати відповіді на запити.
Хоча Cloudflare Workers – дуже зручне та безкоштовне рішення, подібного ефекту можна досягти й іншими способами.
Ось кілька популярних варіантів:
- Netlify Edge Functions
- Vercel Edge Functions
- AWS CloudFront Functions
- Google Cloud Functions
- GitHub Pages + JS Redirection
Оскільки DNS мого сайту вже керується Cloudflare, мені здалося логічним залишатися в одній екосистемі. Отже Cloudflare Workers є ідеальним вибором для невеликих домашніх або персональних проєктів, особливо коли потрібно швидко й безкоштовно забезпечити персональну сторінку помилки під час недоступності сервера.
Як працює Cloudflare Workers
Щоб краще зрозуміти, як саме відбувається взаємодія між користувачем, воркером та веб сервером, я візуалізував цей процес в таку просту блок схему

Тобто варто уявити Cloudflare Worker як посередника між користувачем і сервером на Raspberry Pi. Кожен запит до сайту спочатку проходить через мережу Cloudflare, де Worker перевіряє, чи доступний основний сервер. Якщо все працює, запит просто передається далі, ніби нічого й не відбувалося. Але якщо з’єднання з Raspberry Pi розірвано або сервер не відповідає вчасно, Worker миттєво підхоплює управління і показує власну fallback-сторінку.
Створення Worker
Створення Cloudflare Workers це комплекс дій, а саме:
- Створення шаблонного воркера в 1 клік
- Зміна кода воркера
- Підключення до домену
Зараз я опишу кожен пункт більш детально, адже є певні особливості.
Створення шаблонного воркера в 1 клік
- Увійдіть у Cloudflare Dashboard
- В лівому меню виберіть Compute & AI → Workers & Pages
- Оберіть шаблон Hello World! → Get started.

Таким чином буде створено шаблон “Hello World” script. Я звернув увагу, що область тексту не активна – сіра, і цей тест не можна редагувати. Єдине що я змінив – це назву цього скрипта на blackout, адже на наступному кроці ми цей скрипт будемо редагувати! Тому в цьому випадку необхідно просто погодитися на створення цього воркера.

Цей скрипт відобразиться в списку воркерів

Зміна кода воркера
На наступному кроці необхідно відредагувати щойно створений скрипт, для цього клацаємо на ньому та переходимо на основну сторінку цього воркера, де клацаємо на кнопку “Edit code“.

Відкриється редактор коду, але із можливістю редагування. Оскільки я не знаю JavaScript я звернувся за допомогою до ChatGPT, щоб він мені написав скрипт. Я замінив код скрипту Hello World на свій.

Розгорніть щоб побачити код:
worker-fallback.js
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request))
})
const ORIGIN_HOST = 'https://ostrich.kyiv.ua'
const ORIGIN_TIMEOUT_MS = 10000
const FALLBACK_HTML = `
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>Site temporarily unavailable — Сайт тимчасово недоступний</title>
<style>
body {
font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
margin: 0;
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
background: #f8f9fb;
color: #111;
text-align: center;
}
.card {
max-width: 680px;
background: white;
border-radius: 16px;
padding: 30px;
box-shadow: 0 6px 25px rgba(0,0,0,.08);
}
h1 { font-size: 1.5rem; margin-bottom: .5rem; }
p { margin: .6rem 0; line-height: 1.5; }
.divider {
border-top: 1px solid #ddd;
margin: 1.4rem 0;
}
.time {
font-size: 0.9rem;
color: #666;
margin-top: 10px;
}
</style>
</head>
<body>
<div class="card">
<h1>Site temporarily unavailable</h1>
<p>Hello! If you are reading this message, please know that due to ongoing massive russian attacks on Ukraine's energy sector, my server is currently without power. I hope electricity will be restored soon, so please visit this page again later. <br>Support Ukraine! 💙💛</p>
<div class="divider"></div>
<h1>Сайт тимчасово недоступний</h1>
<p>Привіт! Якщо ти читаєш це повідомлення, знай, що через постійні масовані атаки росії на енергетичну інфраструктуру України мій сервер зараз без електрики. Я сподіваюся, що найближчим часом живлення буде відновлено, тому запрошую відвідати цю сторінку пізніше. І найголовніше — підтримуй Україну! 💙💛</p>
<p class="time">Last checked: <span id="ts"></span></p>
</div>
<script>
document.getElementById('ts').textContent = new Date().toLocaleString('uk-UA');
</script>
</body>
</html>
`
async function handleRequest(request) {
// будуємо URL до origin (зберігаємо шлях і query)
const url = new URL(request.url)
const originUrl = ORIGIN_HOST.replace(/\/$/, '') + url.pathname + (url.search || '')
// Обгортка для тайм-ауту
const controller = new AbortController()
const id = setTimeout(() => controller.abort(), ORIGIN_TIMEOUT_MS)
try {
// Пересилаємо отримані headers та method, телом при потребі
const resp = await fetch(originUrl, {
method: request.method,
headers: request.headers,
body: request.method === 'GET' || request.method === 'HEAD' ? null : request.body,
redirect: 'manual',
signal: controller.signal
})
clearTimeout(id)
// Якщо origin повернув помилку 5xx або 4xx - можна показати fallback або все ж віддати origin
if (resp.status >= 500 || resp.status === 524 || resp.status === 520) {
// замість помилки origin повертаємо fallback
return new Response(FALLBACK_HTML, {
status: 200,
headers: { 'Content-Type': 'text/html; charset=utf-8' }
})
}
// В іншому випадку проксируємо відповідь від origin (включаючи заголовки)
const responseHeaders = new Headers(resp.headers)
// Можна додати cache control для статичних ресурсів, якщо потрібно
return new Response(resp.body, {
status: resp.status,
statusText: resp.statusText,
headers: responseHeaders
})
} catch (err) {
// тайм-аут або помилка мережі -> показати fallback
clearTimeout(id)
return new Response(FALLBACK_HTML, {
status: 200,
headers: { 'Content-Type': 'text/html; charset=utf-8' }
})
}
}
Для оновлення коду необхідно натиснути на кнопку “Deploy”. В такому випадку дані автоматично збережуться і оновляться на Edge сервері.
Підключення до домену
На головній сторінці створеного воркера, переходимо в розділ “Domains & Routes”, щоб усі запити до сайту проходитили через Worker.

У вікні налаштувань Domains & Routes клацаємо на кнопку +Add, і у вікні що відкрилося праворуч вибираємо Route – адже нам треба побудувати маршрут від воркера до сайта. Вводимо наступні налаштування в цьому вікні:
- Zone – із списка вибираємо сайт, в моєму випадку ostrich.kyiv.ua
- Route – прописуємо ostrich.kyiv.ua/*
- Failure mode – вибираємо Fail open (proceed), щоб наш сайт продовжував працювати не дивлячись на те, чи працює сам воркер чи ні, щоб ми від нього не очікували ніякої помилки.

Клацаємо Add route або Update route – якщо таке було створено раніше. Новий маршрут відобразиться в табличці маршрутів.

На цьому етапі налаштування та конфігурації завершилися. Залишилося перевірити результат роботи!
Перевірка роботи Cloudflare Workers
Тепер, коли мій Raspberry Pi вимикається, користувачі бачать чисту, охайну сторінку з поясненням, а не типовий Cloudflare 522.
І все це – безкоштовно, без додаткових серверів чи плагінів.

Висновки
Cloudflare Workers стали для мене простим і водночас потужним рішенням, яке дозволяє залишатися на зв’язку навіть тоді, коли мій сервер на Raspberry Pi тимчасово недоступний через блекаут. Усе, що потрібно – трохи коду та власна ідея, як зробити досвід користувача кращим. Я залишився в екосистемі Cloudflare, бо DNS уже тут, а робота з Workers інтегрується природно, без зайвих витрат і складних налаштувань. Це рішення довело, що навіть у часи енергетичної нестабільності, через росіян, можна підтримувати присутність свого сайту в мережі – просто, надійно і безкоштовно.
