{
"$type": "site.standard.document",
"bskyPostRef": {
"cid": "bafyreif6t3xpwr5iuw5tjzfmbd6iwfppashsvbpdj7dsgvkhu46ksouuj4",
"uri": "at://did:plc:hacddtpu6c35jrehzwl2eq2v/app.bsky.feed.post/3moarkjqaain2"
},
"coverImage": {
"$type": "blob",
"ref": {
"$link": "bafkreidlwb3s66jlxrbtnkru6cxz3c64fwuuoohkgpvjzjd45budcftfw4"
},
"mimeType": "image/png",
"size": 198795
},
"description": " 1. What is it?\n\nAns: When you expose a local port to the internet using cloudflare tunnel you are binding that domain to the local port directly!\nFor example, I setup a writefreely instance in my windows PC.\nLocal Port: localport:1159(any port you assigned can be used) , Domain: https://domain.tld/\n\n\n 2. How to setup those?\n a. Cloudflare tunnel: Setup is easy. You will get exact commands and instruction from cloudflare tunnel setup page.\n b. Writefreely:\n\nwritefreely config start\n\nwritef",
"path": "/cloudflare-tunnel-with-custom-status-page-on-the-same-domai/",
"publishedAt": "2026-06-14T12:30:40.000Z",
"site": "https://sayed.blog",
"tags": [
"Example Repository to deploy",
"https://cf-status.sayed.app",
"@astrojs"
],
"textContent": " 1. What is it?\n\n\n\nAns: When you expose a local port to the internet using cloudflare tunnel you are binding that domain to the local port directly!\nFor example, I setup a writefreely instance in my windows PC.\nLocal Port: localport:1159(any port you assigned can be used) , Domain: https://domain.tld/\n\n\n 1. How to setup those?\na. Cloudflare tunnel: Setup is easy. You will get exact commands and instruction from cloudflare tunnel setup page.\nb. Writefreely:\n\n\n\n\n writefreely config start\n\n\n writefreely keys generate\n\n\n writefreely\n\nNow the main things. This post is not about how to setup or run writefreely instance or cloudflare tunnel. Its about how to serve the tunnel site and status page of that tunnel site in the** _same domain_**!\n\nWhen user visit: https://domain.tld it check the tunnel domain here: https://internal.domain.tld(another sub-domain), Then, if the tunnel domain will be serving http status under 400 its relayed to the root domain. If not, shows custom page I built with Astro(you can just just a simple html here) here. When the local server at 1159 port not running:\n\nWhen the Cloudflared Agent is not connected the proxy worker sees this 530 status but if you manually check at https://internal.domain.tld it will show status 1033:\n\n 1. What about SEO?\nI also added logic to automatically change the server's HTTP response status to match the exact error code (e.g., 521, 502) or fallback to 503 Service Unavailable. When search engines detect a 5xx server error code, they know the downtime is temporary and will deliberately pause indexing and come back later to check on the real site.\n\n\n\nHere id the main middleware which handles everything!\n\n\n import { defineMiddleware } from \"astro:middleware\";\n import { env } from \"cloudflare:workers\";\n\n export const onRequest = defineMiddleware(async (context, next) => {\n // Get the origin host from environment variable\n const originHost = (env as Record<string, string>)?.ORIGIN_HOST;\n\n // If no ORIGIN_HOST configured, just render the Astro page (error page mode via ?code=)\n if (!originHost) {\n return next();\n }\n\n // Build origin URL — same path/query, different host\n const originUrl = new URL(context.request.url);\n originUrl.protocol = \"https:\";\n originUrl.hostname = originHost;\n originUrl.port = \"\";\n\n try {\n // Proxy the request to the origin server (through the tunnel)\n const originResponse = await fetch(\n new Request(originUrl.toString(), {\n method: context.request.method,\n headers: context.request.headers,\n body:\n context.request.method !== \"GET\" && context.request.method !== \"HEAD\"\n ? context.request.body\n : undefined,\n redirect: \"manual\",\n })\n );\n\n // If origin responds successfully, pass the response through\n if (originResponse.status < 400) {\n return new Response(originResponse.body, {\n status: originResponse.status,\n statusText: originResponse.statusText,\n headers: originResponse.headers,\n });\n }\n\n // Origin returned an error — store the code and render the error page\n const errorCode = originResponse.status;\n const errorUrl = new URL(context.request.url);\n // Rewrite to index page with error code\n errorUrl.pathname = \"/\";\n errorUrl.searchParams.set(\"code\", errorCode.toString());\n\n // Rewrite the request internally\n context.locals.errorCode = errorCode;\n return next();\n } catch (e) {\n // Fetch completely failed — tunnel/PC is down\n context.locals.errorCode = 521;\n return next();\n }\n });\n\n\nHere is my wrangler.jsonc\nThis ORIGIN_HOST is the key!\n\n\n {\n \t\"compatibility_date\": \"2026-06-14\",\n \t\"compatibility_flags\": [\n \t\t\"global_fetch_strictly_public\"\n \t],\n \t\"name\": \"error\",\n \t\"main\": \"@astrojs/cloudflare/entrypoints/server\",\n \t\"assets\": {\n \t\t\"directory\": \"./dist\",\n \t\t\"binding\": \"ASSETS\"\n \t},\n \t\"observability\": {\n \t\t\"enabled\": true\n \t},\n \t// Set this to your tunnel hostname to enable proxy mode.\n \t// e.g. \"tunnel.domain.tld\" or \"<uuid>.cfargotunnel.com\"\n \t// Leave empty/remove to use standalone mode with ?code= query params.\n \t\"vars\": {\n \t\t\"ORIGIN_HOST\": \"internal.domain.tld\"\n \t}\n }\n\nExample Repository to deploy\n\nExample Statuspage: https://cf-status.sayed.app\n\n 1. Miscellaneous\n\n\n\nYou can serve the Writefreely local installation on the Fediverse!\nHere is the proof(😑):\n\nThanks for reading! Shoot if you have any questions!",
"title": "Cloudflare Tunnel with Custom Status page on the same domain!",
"updatedAt": "2026-06-14T13:54:32.776Z"
}