Cloudflare – 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 Cloudflare – 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
Cloudflare: real experience repelling a DDoS attack from russia https://ostrich.kyiv.ua/en/2025/05/25/cloudflare-real-experience-repelling-a-ddos-attack-from-russia/ https://ostrich.kyiv.ua/en/2025/05/25/cloudflare-real-experience-repelling-a-ddos-attack-from-russia/#respond Sun, 25 May 2025 13:23:47 +0000 https://ostrich.kyiv.ua/?p=1219 Introduction

When I bought a domain and decided to deploy a public site on my Raspberry Pi (this blog you are reading now) I knew that a DDoS attack would happen, for sure, but it was only a matter of time. That time has come. In this post, I’ll share my experience of how I fended off a DDoS attack with Cloudflare on a free plan. I will provide here the graphs and results of the work on the fact of the completion of the attack, but they will be quite informative for visual perception.

Preventive measures

I continue to study modern network capabilities, new technologies, understand settings and services. I currently have several web services installed on my server, but from a security point of view, I have restricted access from the Internet to only the local network. Such measures cannot apply to a public web resource – this blog. After writing about the 5th article, I thought about protection against DDoS attacks, and the first thing that came to mind was the Cloudflare service.

Cloudflare – is a powerful platform for ensuring security, optimizing performance and increasing the stability of web resources. Thanks to a global network of data centers, Cloudflare acts as an intermediate link between the site and the visitor: it caches content, blocks unwanted traffic and provides analytics.

At the entry level, I chose the free plan, which has some limitations unlike the commercial one, but it is quite enough for basic site protection needs. To start using Cloudflare, you need to follow a few simple steps:

  • Sign up for Cloudflare
  • In the admin panel of the domain registrar it is necessary to register the DNS records of the Cloudflare servers
  • In the admin panel of Cloudflare add DNS records of your physical server

After making the changes, the reindexing of DNS records will begin, which can take up to 72 hours, but in my case it happened in less than a day. After that, I breathed a sigh of relief and started using the site fully relying on Cloudflare.

DDoS attack detection

I use Zabbix to monitor the resources of my local network. I usually monitor mail server activity, Raspberry Pi temperatures, and other metrics. Visually, I placed several widgets on the dashboard so that anomalies in the operation of services can be clearly and quickly identified

Normally the temperature is around 60 – 65°C, but I noticed an over temperature of almost 10 degrees, up to 70 – 75°C. At this time, I looked at the CPU load, and it was at the level of 50%, I immediately understood that something was wrong.

I looked at the server metrics and almost all of the graph values ​​indicated an emergency situation

Visual charts only show the big picture. Now my goal was to identify the root of the evil. To do this, I connected to the server and started viewing the status of processes and services. Unfortunately, I did not take screenshots during the diagnostics, but I copied the text from the console. I will try to reproduce the course of events based on the history of the teams.

CPU resource

With the first command, I identified the first 15 processes with high CPU consumption.

ps -eo pid,ppid,cmd,%mem,%cpu --sort=-%cpu | head -n 15

I got the following output:

ps -eo pid,ppid,cmd,%mem,%cpu --sort=-%cpu | head -n 15
    PID    PPID CMD                         %MEM %CPU
2366770 1943100 /usr/sbin/apache2 -k start   1.1 12.4
2373538 1943100 /usr/sbin/apache2 -k start   1.2 12.2
2374705 1943100 /usr/sbin/apache2 -k start   1.2 11.8
2374940 1943100 /usr/sbin/apache2 -k start   1.2 11.3
2376091 1943100 /usr/sbin/apache2 -k start   1.0 11.1
2370641 1943100 /usr/sbin/apache2 -k start   1.2 10.4
2355557 1943100 /usr/sbin/apache2 -k start   1.2 10.4
    709       1 /opt/networkoptix/mediaserv  5.7  9.6
2375784 1943100 /usr/sbin/apache2 -k start   1.0  9.3
2373539 1943100 /usr/sbin/apache2 -k start   1.2  7.8
2375416 1943100 /usr/sbin/apache2 -k start   1.2  7.1
2376564 1943100 /usr/sbin/apache2 -k start   0.8  6.9
2372672 1943100 /usr/sbin/apache2 -k start   1.2  6.9

Of course, the values ​​were 10-20% CPU per process, which in sum gave quite a large total load.

Traffic source

The second team had to determine which IPs generate a lot of traffic. This command displays the first 10 IP addresses based on the number of requests.

awk '{print $1}' /var/log/apache2/access.log | sort | uniq -c | sort -nr | head -n 10

I got the following output:

 118852 127.0.0.1
   7169 162.158.239.23
   7168 162.158.238.232
   6304 162.158.238.233
   5810 162.158.239.24
   5068 162.158.238.106
   4848 162.158.238.107
   3871 162.158.238.120
   3843 162.158.238.121
   2875 104.23.217.65

This information reflects local requests (127.0.0.1) – more than 118 thousand requests, that is, someone from the server actively requests the site. The pool of IP addresses 162.158.x.x and 104.23.x.x is owned by Cloudflare, as a proxy, so the real traffic is hidden behind Cloudflare and certain rules of my router are not working.

In addition, geo-filtering of traffic is already configured on my router, which excludes any traffic from russia and belarus.

Virtual host involvement

The next step is to determine which site or virtual host is loading the server. To do this, we will analyze the first 100 lines of the log, however, for improved statistics, this number can be increased. Since I detected this attack at an early stage, 100 lines is enough.

sudo tail -n 100 /var/log/apache2/access.log | awk '{print $7}' | sort | uniq -c | sort -nr | head -20

I got the following output:

29 /wp-login.php
22 /wp-admin/images/
7 /zabbix/zabbix.php?action=widget.item.view
4 /zabbix/zabbix.php?action=widget.svggraph.view
1 /wp-cron.php?doing_wp_cron=1748089832.8370869159698486328125
...

If you ignore requests to Zabbix, then the main requests go to WordPress. To summarize, the result of the diagnosis is as follows:

  • apache2 – consumes more than all the resources of the processor
  • local IP and Cloudflare IP – has the most requests
  • requests to WordPress pages

At the first stage, these diagnostic data turned out to be sufficient to determine further actions.

Cloudflare Security Actions

I didn’t expect Cloudflare to be such a powerful tool, even in the free version. Many features are disabled by default and must be enabled manually for certain attacks, and it is recommended to deactivate these settings after the attack is over. The main mode is I’m under attack mode.

I’m under attack mode

I’m Under Attack Mode is a special Cloudflare feature designed for protection of websites from DDoS attacks, in particular at the level HTTP flood (e.g., bulk GET/POST attacks). The basic principle of operation of this mode is as follows:

  • Before each visit to the site an intermediate verification page is displayed, which lasts a few seconds.
  • Cloudflare does JavaScript challenge — that is, forces the browser to “prove” that it is not a bot.
  • If the check is successful, the user gets to the site.
  • Bots and simple scripts that cannot execute JS – automatically are sifted out.

This mode can be enabled in the Security -> Settings section, there is a switch I’m under attack mode, it must be activated, and the work of repelling the attack will begin immediately.

In the new interface, this item was moved to the All settings tab at the bottom, and named Security Level. When you click on Edit, you can enable or disable I’m under attack mode.

After activating this mode, a message will appear on the dashboard:

Under Attack Mode is active
Under Attack mode is used when a website is under a DDoS attack. All visitors will be shown an interstitial page for a short duration.

In this mode, there is a small delay with the check, which looks like this:

In the Security -> Events section, you can see records about the initiator of the requests, rather complete and organized information is displayed. In the new interface, the Events section has been moved to the Analytics menu and the Events tab has been created, where the information is structured in the same way.

Custom and IP access rules

Given that the DDoS attack occurred from a single IP address, rules can be applied to it.

  • IP access rules – to block IP addresses individually or using a subnet mask
  • Custom rules – more flexible setting of rules

First, I decided to create a rule that would block the attacking server by IP address, for this in the new interface you need to go to the Security -> Security rules -> IP access rules section and click on the +Create button. To create a rule, it is enough to fill in only 4 fields:

  • IP, IP range, country name, or ASN – 194.67.196.50
  • Action – Block
  • Zone – This website
  • Notes – Fucking russian hacker

From the very beginning, I wanted to block all of russia by geolocation, but in the free tariff plan, at the level it is not possible to do so, after applying this rule I see an error: Sorry, block by country is only available on the Enterprise plan.

It turns out that there is a geolocation filter in the free version, but it can be configured in the Custom rules section.

  • Rule name (required) – Block_country_404
  • Field – Country
  • Operator – equals
  • Value – russian federation
  • Then take action – Block
  • Place at – First

This way the rule will look like this

Reports

In the Security -> Overview section, you can see a graph of the ratio of restricted, Cloudflare-handled and server-handled traffic to the total number of requests. When Cloudflare is active, the value of the traffic limit (Mitigated) will increase, and the number of cached requests will also increase, because Cloudflare in this mode works without direct traffic and has a number of restrictions to prevent hacking, such as password selection by brute force.

Many useful graphs are displayed on the dashboard – Overview. I took several screenshots, at the beginning of the attack and at the end of it. The graph shows the last 24 hours of activity, so I’ve overlayed these two graphs to show you the big picture of activity. I marked the range of Cloudflare’s service from start to finish with a red rectangle.

The graphs are quite informative and setting up Cloudflare is easy. According to the schedule, the attack lasted 17 hours from the beginning, and thanks to my vigilance, at the beginning of the attack, at the 6th hour, I applied Cloudflare protection, and already in 11 hours the attack was stopped, I think because of its irrationality for the attacker.

After the attack was over, I turned off the I’m under attack mode and continue to monitor server activity to be ready to prevent repeated attacks at any time!

Conclusions

The experience of fighting a DDoS attack on my own server has shown that even with minimal resources – a Raspberry Pi, my own blog, and a free Cloudflare plan – you can effectively protect yourself from threats if you have a basic understanding of how the network, servers, and monitoring systems work.

I was convinced that:

  • Preventive actions are important. Installing Cloudflare in advance allowed me to quickly enable protective mechanisms.
  • Monitoring tools such as Zabbix help to quickly detect anomalies and localize the source of the problem.
  • Cloudflare has proven to be extremely effective even in its free version, in particular thanks to the I’m Under Attack mode, which allows you to filter out malicious traffic before it even reaches the server.
  • Openness to learning and log analysis is the key to a quick response. The ps, awk commands, and access.log analysis helped not only identify the source of the load, but also draw conclusions about the type of attack.

This case confirmed that reliable protection is not always expensive infrastructure, but primarily attentiveness, readiness to respond, and the ability to work with available tools.

My blog remained online, and this is the best indicator of the effectiveness of the solutions applied.

]]>
https://ostrich.kyiv.ua/en/2025/05/25/cloudflare-real-experience-repelling-a-ddos-attack-from-russia/feed/ 0