Web – Ostrich blog https://ostrich.kyiv.ua Mon, 10 Nov 2025 08:19:28 +0000 en-US hourly 1 https://wordpress.org/?v=6.8.3 https://ostrich.kyiv.ua/wp-content/uploads/2024/02/ostrich-150x150.png Web – Ostrich blog https://ostrich.kyiv.ua 32 32 How to create a custom Fallback page using Cloudflare Workers https://ostrich.kyiv.ua/en/2025/10/26/how-to-create-a-custom-fallback-page-using-cloudflare-workers/ https://ostrich.kyiv.ua/en/2025/10/26/how-to-create-a-custom-fallback-page-using-cloudflare-workers/#respond Sun, 26 Oct 2025 09:38:14 +0000 https://ostrich.kyiv.ua/?p=1790

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:

  1. Creating a template Worker in one click
  2. Editing the Worker’s code
  3. Connecting it to your domain

Let’s go through each step in more detail.

Select the Template

  1. Log in to your Cloudflare Dashboard.
  2. In the left menu, select Compute & AI → Workers & Pages.
  3. 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)
  • Routeostrich.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.

]]>
https://ostrich.kyiv.ua/en/2025/10/26/how-to-create-a-custom-fallback-page-using-cloudflare-workers/feed/ 0
DNSSEC: How Domain Name Security Works https://ostrich.kyiv.ua/en/2025/09/25/dnssec-how-domain-name-security-works/ https://ostrich.kyiv.ua/en/2025/09/25/dnssec-how-domain-name-security-works/#respond Thu, 25 Sep 2025 09:32:34 +0000 https://ostrich.kyiv.ua/?p=1712

The classic Domain Name System (DNS) is one of the key components of how the Internet works. However, in its basic form DNS does not have a mechanism to verify the authenticity of data. To solve this problem, DNSSEC (Domain Name System Security Extensions) was developed — a set of extensions to DNS that add integrity and authenticity checks.

What is DNSSEC

DNSSEC is a set of cryptographic extensions to DNS that allow you to:

  • Verify the authenticity of records (source validation);
  • Protect against forged or tampered responses (integrity protection);
  • Build a trusted chain from the DNS root to a specific domain.

The principle of operation is based on digital signatures. Each DNS record is signed with a private key. The client (resolver) receives not only the value but also a cryptographic signature, which can be verified using the public key stored in the parent zone.

How DNSSEC Works

Visually, and very simply, on my real domain ostrich.kyiv.ua can build the following diagram:

  • Key creation. A key pair is generated for the domain:
    • KSK (Key Signing Key) — signs the keys;
    • ZSK (Zone Signing Key) — signs the records in the zone.
  • Zone signing. All records (A, MX, TXT, etc.) are signed using the ZSK.
  • Public key delegation. The hash of the public key (DS record) is published in the parent zone (for example, in .ua for the .com.ua domain).
  • Chain of trust. When a resolver receives a response, it verifies the signature, and then checks whether the key is trusted through the chain from the DNS root.

As a result, the user can be confident that the received DNS data is genuine and has not been tampered with.

Example of Use

Suppose a user visits my website. Without DNSSEC, a hacker could spoof a DNS response and redirect them to a phishing site. With DNSSEC, the browser (via the resolver) will only receive signed records, and if the signature does not match, the response will be rejected. Thus, the user will only reach the legitimate server.

DNSSEC Settings

The registrar of my domain name is a local provider that recently added DNSSEC to their services. I decided to use this opportunity and configure it. Since the registrar is not Cloudflare but my local provider, the configuration must be applied on the registrar’s side. However, the data itself is obtained from Cloudflare because my DNS records are hosted there.

Enabling the feature is quite simple. In the Cloudflare panel, go to DNS → Settings → DNSSEC → Enable.

A window will display all the necessary information for activation in your domain registrar’s control panel, with a warning:
“To enable DNSSEC you will need to add this DS record to your registrar. Most registrars will ask for only a few of the fields below. We have instructions for common registrars.”

My registrar requires filling in only 4 fields:

  • Key tag
  • Algorithm
  • Digest type
  • Digest

Literally within a few minutes, the DNSSEC status changes to “Success! ostrich.kyiv.ua is protected with DNSSEC.”

Verification

To check whether the changes have actually been applied, it is enough to run the following command in the terminal, which queries Cloudflare directly:

dig +dnssec ostrich.kyiv.ua @1.1.1.1

The expected result is an additional line in the output with an RRSIG record:

ostrich.kyiv.ua.        300     IN      RRSIG   A 13 3 300 20250926095050 20250924075050 34505 ostrich.kyiv.ua. IQE6axVd6YMeHnyXC2zW9ELt9P+6ZNzuhPbWQ4BqRnAtAGkQtIA7ETiE k/079aSTNqHk+fnnKidHU3Jp5pdORQ==

This record consists of the following parameters:

  • A — The signature covers A (IPv4) records.
  • 13 — Signature algorithm. 13 = ECDSA Curve P-256 with SHA-256 (a modern algorithm, used by Cloudflare by default).
  • 3 — Number of labels in the domain name (for ostrich.kyiv.ua → 3: ostrich, kyiv, ua).
  • 300 — TTL in seconds with which the record was signed (the maximum caching time of this signature).
  • 20250926095050 — Signature expiration time (UTC, format YYYYMMDDHHMMSS). Here → September 26, 2025, 09:50:50 UTC.
  • 20250924075050 — Signature inception time (UTC). Here → September 24, 2025, 07:50:50 UTC. (The signature is valid only within this time interval.)
  • 34505 — The DNSKEY key tag used for signing. The resolver looks for the DNSKEY with this tag to validate the signature.
  • ostrich.kyiv.ua. — The domain name of the signer.
  • IQE6axVd6YM… — The actual cryptographic signature (base64). Used together with DNSKEY to verify authenticity.

Thus, just a few minutes after applying the changes, additional DNS protection can be obtained.

There is also the dnssec-analyzer resource, which visually shows whether domain validation is working correctly.

Conclusion

DNSSEC is an important step toward improving Internet security. It does not replace HTTPS or VPN, but it makes DNS names more trustworthy.
For website owners, enabling DNSSEC is a way to demonstrate care for visitors’ security and readiness for future standards (such as DANE).
If your DNS provider supports DNSSEC (for example, Cloudflare), it is strongly recommended to enable it.

]]>
https://ostrich.kyiv.ua/en/2025/09/25/dnssec-how-domain-name-security-works/feed/ 0