Skip to content

Cloudflare Free Plan Maintenance Page: Custom Error Pages Unavailable, Use a Worker Instead

Mar 13, 2026 1 min
TL;DR Cloudflare Custom Error Pages require a paid plan. On the Free Plan, use a Worker with inline HTML to intercept 5xx responses instead.
Table of Contents
  1. TL;DR
  2. Context
  3. The Problem
  4. The Solution
  5. Testing
  6. Why This Happens
  7. What I Learned
  8. Changelog
  9. References

🌏 中文版

TL;DR

Cloudflare's Custom Error Pages feature (which shows a custom page when nginx goes down) was restructured into Rules in 2025 and is now called Custom Errors (covering Custom Error Rules and Error Pages) — and it's only available on paid plans. The Free Plan workaround: deploy a Cloudflare Worker as a proxy, inline the maintenance page HTML directly in the Worker, and return it whenever the origin responds with a 5xx.

Context

I was in the middle of splitting nginx out of the daodao-server repo into a new daodao-infra repo. During the switchover window (under a minute), I wanted a Cloudflare maintenance page as a fallback to prevent users from seeing raw errors during the gap.

The Problem

I couldn't find Custom Error Pages anywhere in the Cloudflare Dashboard. The docs said it should be under Rules → Custom Pages, but all I could see under Rules was: Overview, Snippets, Cloud Connector, Trace, Page Rules, and Settings. No Custom Pages.

After digging around, I found that as of April 2025, the feature was renamed to Error Pages and moved under Rules — but it's only available on Pro plans and above. On the Free Plan, the option simply doesn't appear.

The Solution

Use a Cloudflare Worker as a proxy with the maintenance page HTML inlined directly in the script.

const MAINTENANCE_HTML = `...`;  // full HTML inlined here

export default {
  async fetch(request) {
    // Preview path — return the maintenance page directly without proxying
    if (new URL(request.url).pathname === '/maintenance.html') {
      return new Response(MAINTENANCE_HTML, {
        status: 200,
        headers: { 'Content-Type': 'text/html; charset=utf-8' },
      });
    }

    try {
      const response = await fetch(request);
      if (response.status >= 500) {
        return new Response(MAINTENANCE_HTML, {
          status: response.status,
          headers: { 'Content-Type': 'text/html; charset=utf-8' },
        });
      }
      return response;
    } catch {
      // Origin is completely unreachable
      return new Response(MAINTENANCE_HTML, {
        status: 503,
        headers: { 'Content-Type': 'text/html; charset=utf-8' },
      });
    }
  },
};

After deploying, go to Website → daodao.so → Worker Routes → Add Route:

  • Route: daodao.so/*
  • Worker: select the Worker you just deployed
  • Failure mode: Fail open (continue)

Choose "fail open": if the Worker itself has an issue, requests fall through directly to nginx, so a Worker failure won't affect normal traffic.

Testing

Preview the maintenance page

After deploying the Worker, visit it directly at:

https://<worker-name>.<account>.workers.dev/maintenance.html

The /maintenance.html path returns the maintenance page directly (no proxying), so you can verify the layout looks correct before binding a domain.

Test 5xx triggering (after binding the domain)

Stop nginx on the VPS:

docker stop nginx

Visit https://daodao.so — you should see the maintenance page instead of a raw error. Restore nginx when done:

docker start nginx

Why This Happens

In April 2025, Cloudflare restructured Custom Pages into the more powerful Custom Errors (with conditional logic support) and took it GA, but gated the feature behind paid plans. The Free Plan dashboard shows no trace of it, which is why it took a while to realize the feature simply wasn't available.

That part has since improved: the official Custom Errors docs now carry an explicit availability table, and the Free column reads 0 rules and no Error Pages. To check whether a plan has the feature, read that table rather than hunting through the dashboard.

What I Learned

Cloudflare Workers are incredibly versatile as a lightweight proxy layer — not just for edge functions. Whenever a platform feature is locked behind a paid tier, Workers can almost always replicate it in code. The Free Plan includes a daily request allowance in the hundred-thousand range (current figure in Workers Limits), which is more than enough for most sites.

Changelog

  • 2026-08-19: Fact-checked against primary sources and refreshed; perishable details handed back to official docs. Added to the "Cloudflare Edge Stack" series.

References