When this blog website went offline during blackouts, Cloudflare displayed the 522 – Connection timed out page.
I wanted something better – a simple fallback page with my own design, built at zero cost.
In this article, I’ll explain step-by-step how to configure a free Cloudflare Worker that keeps your website presentable even when your server is unavailable.
What are Cloudflare Workers
Cloudflare Workers are a serverless environment that allows you to run custom JavaScript directly on Cloudflare’s edge servers. They act as a thin programmable layer between your users and your origin server – intercepting, modifying, or replacing responses before they reach the visitor.
Although Cloudflare Workers are an excellent free solution, a similar result can be achieved using other platforms.
Here are a few popular alternatives:
- Netlify Edge Functions
- Vercel Edge Functions
- AWS CloudFront Functions
- Google Cloud Functions
- GitHub Pages + JavaScript Redirection
Since my DNS is already managed by Cloudflare, it made perfect sense to stay within the same ecosystem. Cloudflare Workers became an ideal choice for small personal or home projects, especially when you need a quick, cost-free way to serve a custom error page during server downtime.
How Cloudflare Workers operate
To better visualize the interaction between the user, the Worker, and the origin server, I designed a simple block diagram that explains the process.

Essentially, a Cloudflare Worker acts as a smart proxy between your visitors and your Raspberry Pi server. Every request first passes through Cloudflare’s network, where the Worker checks if the origin is reachable. If the server responds normally, the request is forwarded and the user sees the actual website. But if the Raspberry Pi is offline or too slow to respond, the Worker immediately takes over and serves a pre-defined fallback page.
Creating the Worker
Setting up Cloudflare Workers involves several key steps:
- Creating a template Worker in one click
- Editing the Worker’s code
- Connecting it to your domain
Let’s go through each step in more detail.
Select the Template
- Log in to your Cloudflare Dashboard.
- In the left menu, select Compute & AI → Workers & Pages.
- Choose the Hello World! template and click Get started.

This creates a simple “Hello World” script. At first, the code editor might look greyed-out (read-only). The only change I made initially was renaming the script to blackout – because in the next step, we’ll replace its code entirely.

Once you confirm creation, the new Worker appears in your list of services.

Editing the Worker Code
Next, click on the newly created Worker → open its main page → and press Edit code. This opens the editable code editor.

Since I don’t know JavaScript well, I asked ChatGPT to help me write the logic for my fallback page. Below is the full script I replaced the default “Hello World” with:

Expand to see the code:
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' }
})
}
}
After updating the code, click Deploy – this automatically saves and publishes your Worker to Cloudflare’s edge network.
Connecting the Worker to the Domain
On the Worker’s main page, open the Domains & Routes section – this ensures all website requests are processed by your Worker.

Click + Add and configure the following:
- Zone → select your domain (in my case,
ostrich.kyiv.ua) - Route →
ostrich.kyiv.ua/* - Failure mode → choose Fail open (proceed) so the website remains accessible even if the Worker itself fails.

Click Add route (or Update route if you already have one). The new route will appear in the table of active routes.

At this stage, the configuration is complete – time to test!
Testing the Worker
When my Raspberry Pi server goes offline, visitors now see a clean and friendly fallback page instead of the default Cloudflare 522.
Everything works seamlessly, and the best part – it’s completely free, with no additional servers or plugins required.

Conclusions
Cloudflare Workers turned out to be a simple yet powerful way to stay online even when my Raspberry Pi server goes dark during blackouts. All it takes is a few lines of JavaScript and a bit of curiosity to improve user experience. Since my DNS is already on Cloudflare, integrating Workers felt natural — no extra costs, no complex setup. This project proved that even in times of energy instability, it’s possible to keep a website alive — simply, reliably, and for free.
