Skip to content

Self-host on Cloudflare's free plan: no server or domain needed - #3588

Merged
forgetso merged 2 commits into
mainfrom
feat/self-hosted-cloudflare
Oct 22, 2026
Merged

forgetso merged 2 commits into
mainfrom
feat/self-hosted-cloudflare

Conversation

@forgetso

Copy link
Copy Markdown
Member

What changed

You can now self-host Procaptcha with no server, no domain and no open ports, just a free Cloudflare account and any machine that can run Docker.

How it works: the provider runs in Docker on your machine, connected to Cloudflare through a tunnel that only ever connects outwards. A Worker on your free workers.dev address serves the widget and passes the widget's API calls through that tunnel to the provider. Cloudflare handles HTTPS, so Caddy isn't needed.

New files:

  • docker/cloudflare/worker.ts + wrangler.jsonc: the Worker. It also serves a demo page at /, which shows whether the provider is reachable, renders the CAPTCHA and shows the response token with a copy button. It supports light and dark mode.
  • docker-compose.self-hosted.cloudflare.yml: adds cloudflared to the stack and leaves Caddy out.
  • generateSelfHostedEnv.ts --cloudflare: makes .env load both compose files and adds a CLOUDFLARE_TUNNEL_TOKEN line to fill in.
  • SELF_HOSTING.md: a new section, "No server? Run it on Cloudflare's free plan". The matching docs page is in Self-Hosting: work from a fresh clone, and add a Cloudflare free-plan page docs#123.

One detail that matters: the Worker passes each visitor's real IP address to the provider. Without it, the provider sees every visitor as the tunnel, so per-IP rate limits and rules would apply to everyone at once. I saw this happen with a plain tunnel: every session recorded the tunnel's container address.

Effect

Self-hosting no longer needs a server with a public IP, or a domain. Nothing changes for anyone using the existing guide.

Test coverage

Tested by running it, from a fresh clone of this branch, against real Cloudflare: a tunnel created with wrangler, a VPC service, the Worker on workers.dev, and the published 3.8.38 provider image (no custom build).

  • docker compose up -d started Mongo, Redis, the provider and cloudflared, but not Caddy. The tunnel registered 4 connections.
  • /healthz on the Worker reached the provider.
  • In a real browser, the demo page rendered a puzzle. After switching the key to proof of work, a token was issued, and @prosopo/server verified it (verified: true).
  • The provider recorded the visitor's real IP, not the tunnel's.
  • The Worker type-checks under strict. Lint is clean.

The one step I couldn't do by hand was copying the tunnel token from the Cloudflare dashboard. I fetched the same token through the API.

🤖 Generated with Claude Code

forgetso and others added 2 commits October 22, 2026 14:57
A Worker on workers.dev serves the widget and forwards the provider API through
a Workers VPC binding to a Cloudflare Tunnel, so a provider needs no domain,
public IP or certificate. Adds the Worker template with a demo page, a compose
add-on that runs cloudflared without Caddy, and generateSelfHostedEnv.ts
--cloudflare.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@forgetso
forgetso enabled auto-merge (squash) October 22, 2026 14:14
@forgetso
forgetso disabled auto-merge October 22, 2026 14:33
@forgetso
forgetso merged commit b4235b4 into main Oct 22, 2026
23 checks passed
@forgetso
forgetso deleted the feat/self-hosted-cloudflare branch October 22, 2026 14:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant