Cloudflare Redundancy System
Keep your website online even when your server goes down — automatically.
This project uses Cloudflare Workers to create a transparent redundancy layer in front of your website. When your origin server is reachable, visitors are proxied through normally. When it goes down, a static snapshot is served instead — with zero configuration required from the visitor.
Table of Contents
- How it works
- Prerequisites
- Step 1 — Create the static cache repository
- Step 2 — Back up your websites
- Step 3 — Deploy the cache worker
- Step 4 — Deploy the proxy worker
- Step 5 — Configure your DNS
- Automation
- Reference — save.sh
- Reference — proxy-code.js
- Troubleshooting
How it works
Visitor
│
▼
[Cloudflare proxy Worker] ←── your domain (example.com)
│
├─── Origin reachable? ──YES──► Forward request to https://_.example.com
│ (your real server, A/AAAA record "_")
└─── Origin down? ──NO───► Fetch from website-caches Worker
(pre-built static snapshot on GitHub)
The two Cloudflare Workers involved are:
| Worker | Role |
|---|---|
website-caches |
Serves the pre-built static snapshot of your site from a GitHub repository |
proxy |
Sits in front of your domain; decides whether to forward live or serve cached content |
Prerequisites
- A Cloudflare account with your domain added
- A GitHub account
wgetandgitavailable on the machine that will run the backup script- Basic familiarity with Cloudflare Workers and DNS records
[!IMPORTANT] Your domain must be managed by Cloudflare (i.e. Cloudflare must be your DNS provider) for the worker routing to work.
Step 1 — Create the static cache repository
Fork this repository and name it exactly website-caches:
[!NOTE] The repository name
website-cachesis used later by the proxy worker to build the fallback URL. Do not rename it.
Step 2 — Back up your websites
Open save.sh and add a save_website call for each site you want to back up:
save_website https://example.com
save_website https://another-site.org
Then run the script:
chmod +x save.sh
./save.sh
Or without changing permissions:
sh save.sh
After the run you should see a static/ folder:
With your site mirrored inside it:
[!WARNING] If the
static/folder is empty or your site is missing, the wget mirror failed. Check thatwgetis installed and that the URL insave.shis correct, then re-run the script.
Step 3 — Deploy the cache worker
Go to Cloudflare Workers & Pages and create a new Worker connected to your GitHub repository:
Click Continue with GitHub and select your website-caches repo:
In the Deploy command field, enter:
npx wrangler deploy --assets=./static --compatibility-date 2026-01-01
Click Deploy. Cloudflare will build and publish the static assets automatically. Every time save.sh pushes new content to GitHub, a new deployment will be triggered.
[!TIP] You can check the deployment URL in the Worker settings page — it will look like
https://website-caches.<your-subdomain>.workers.dev. Keep it handy for the next step.
Step 4 — Deploy the proxy worker
Create a new Worker named proxy using the Hello World template:
Click Create, then open Edit code:
Replace the entire content with proxy-code.js from this repository.
[!IMPORTANT] At the top of the file, replace
fox3000foxywith your own Cloudflare Workers subdomain:const FALLBACK_BASE = "https://website-caches.<YOUR-SUBDOMAIN>.workers.dev"
Click Deploy:
Step 5 — Configure your DNS
You need two things in your Cloudflare DNS settings:
- Change your existing
AandAAAArecords — set the subdomain name to_so your origin server is reachable at_.example.cominternally by the proxy:
- Add your root domain as a custom route for the
proxyworker:
[!CAUTION] Do not delete your A/AAAA records — only rename their subdomain to
_. The proxy worker needs to reach_.example.comto check if your origin is alive. Deleting the records makes the live-forwarding path unreachable.
Your redundancy system is now fully operational. Visitors always hit the proxy worker; the worker silently switches between your live server and the static backup as needed.
Automation
To keep the static backup fresh, schedule save.sh with a crontab:
# Run every 30 minutes
*/30 * * * * /path/to/save.sh >> /var/log/website-backup.log 2>&1
[!TIP] Adjust the interval based on how frequently your site changes. A blog updated daily can use a much longer interval (e.g. every 6 hours) to reduce unnecessary commits.
[!CAUTION] Cloudflare's free tier includes limited monthly build minutes. Frequent
save.shruns can exhaust your budget. Find the right balance between backup freshness and build costs — consider running hourly or daily instead of every 30 minutes for production sites.
Reference — save.sh
The script does the following on each run:
| Step | What happens |
|---|---|
save_website <url> |
Mirrors the site with wget into static/<hostname>/ |
--mirror |
Recursively downloads all pages and linked assets |
--convert-links |
Rewrites links to work offline |
--page-requisites |
Downloads CSS, JS, images required to render each page |
git pull |
Syncs any remote changes before committing |
git add * |
Stages all new and modified files |
git commit |
Creates a commit with a DD/MM/YYYY HH:MM timestamp |
git push |
Pushes the snapshot to GitHub, triggering a Cloudflare deployment |
The git author is set to backup-bot so commits are clearly identifiable as automated.
Reference — proxy-code.js
The proxy worker intercepts every HTTP request to your domain and applies the following logic:
Incoming request
│
▼
Parse URL → extract hostname, path, query
│
▼
GET /profile/theme (POST) ?
├─ YES → set theme cookie, return 200
└─ NO ──────────────────────────────────┐
▼
Check origin (HEAD https://_.<host>)
│
┌────────────┴────────────┐
Online Offline
│ │
▼ ▼
Forward request to Fetch from FALLBACK_BASE
https://_.<host><path> /<host><path>
Key behaviours
URL & link rewriting
Every href, src, and action attribute in the HTML response is rewritten to replace _.example.com with example.com, so the visitor never sees internal hostnames. The same rewriting is applied to Location and Refresh response headers and to inline <script> content.
Cookie handling
Set-Cookie headers from the origin are rewritten to use the proxy domain (example.com) instead of _.example.com. SameSite=None; Secure is added automatically to cookies that lack those attributes, which is required for cross-context cookies to work in modern browsers.
Theme support
A POST /profile/theme endpoint sets a theme cookie. On every HTML response, the data-bs-theme attribute of <body> is updated to match — enabling dark/light mode switching that persists across the proxy.
Non-GET requests
POST, PUT, PATCH, DELETE and other non-idempotent requests are fully proxied to the live origin (body included). The static fallback is only served for GET requests when the origin is down.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
Static folder is empty after save.sh |
wget not installed or URL typo |
Install wget; verify the URL resolves |
Proxy returns 502 Proxy error |
Worker threw an exception | Check Worker logs in Cloudflare dashboard |
| Site always shows cached version | Origin health check failing | Ensure _.example.com DNS record exists and is proxied |
| CSS/images broken on cached pages | wget did not download all requisites |
Re-run save.sh; check wget output for errors |
| Cookies not persisting through proxy | SameSite policy blocked |
Ensure the worker domain uses HTTPS (Cloudflare enforces this by default) |
[!NOTE] Worker logs are available in real time under Workers & Pages → your worker → Logs in the Cloudflare dashboard. This is the fastest way to diagnose runtime issues.
If you still have a problem or want to bring a fix, don't hesitate to open an Issue or a Pull Request!