diff --git a/docs/install/cloudflare.md b/docs/install/cloudflare.md
index 6ddab5a93d9f..406b19ab25ba 100644
--- a/docs/install/cloudflare.md
+++ b/docs/install/cloudflare.md
@@ -27,6 +27,23 @@ The template lives in [`scripts/cloudflare`](https://github.com/openclaw/opencla
The Worker forwards every HTTP and WebSocket request to one stable Durable Object name. That Durable Object owns one Container instance and is the single-writer fence around the Litestream replica. The Container exposes OpenClaw on port `8080`; `/startupz` is its traffic-readiness check.
+```mermaid
+flowchart TD
+ client[Channels, browsers, API clients]
+ worker[Cloudflare Worker]
+ durable[Durable Object, one stable name]
+ container[Container running the OpenClaw Gateway on 8080]
+ litestream[Litestream sidecar process]
+ r2[(R2 bucket of SQLite replicas)]
+
+ client --> worker
+ worker --> durable
+ durable --> container
+ container --> litestream
+ litestream -- continuous WAL streaming --> r2
+ r2 -- restore on boot --> container
+```
+
Litestream watches both SQLite roots:
- `/home/node/.openclaw/state/*.sqlite`
@@ -34,6 +51,8 @@ Litestream watches both SQLite roots:
At boot, the entrypoint uses R2's S3 `ListObjectsV2` API as the restore manifest, rejects paths outside those roots, restores each discovered database, and only then starts the Gateway.
+Measured on this template against a real R2 bucket: about 2.4 seconds from write to replica, and about 9 seconds to restore both databases into a fresh Container that reached a healthy Gateway roughly 13 seconds after start. Treat these as order-of-magnitude expectations, not guarantees.
+
## Deploy
@@ -156,6 +175,59 @@ At boot, the entrypoint uses R2's S3 `ListObjectsV2` API as the restore manifest
+## Verify the deployment
+
+Run these checks after the first bootstrap, before you depend on this deployment.
+
+Confirm the Gateway admits traffic. `/startupz` reports startup completion and ignores channel health, so it stays green when one channel account is broken:
+
+```bash
+curl -sS https://.workers.dev/startupz
+curl -sS -H "Authorization: Bearer $OPENCLAW_GATEWAY_TOKEN" \
+ https://.workers.dev/readyz
+```
+
+Confirm replication is actually reaching R2. Objects should appear under `replicas/` within seconds of activity:
+
+```bash
+npx wrangler r2 object get openclaw-backups/replicas --remote 2>/dev/null || true
+npx wrangler r2 bucket list
+```
+
+Rehearse recovery before you need it. An untested restore path is not a backup:
+
+1. Send one message so the Gateway writes a session row.
+2. Wait about ten seconds for replication.
+3. Delete the Container instance, or redeploy to force replacement.
+4. Reopen the Worker URL and confirm the conversation still exists.
+
+If step 4 loses data, stop and fix replication before connecting production channels.
+
+## Cost and sizing
+
+Containers require the Workers Paid plan. Memory and disk bill on the resources **provisioned** for the instance type for as long as the Container is awake; CPU bills on active use only.
+
+The default `standard-2` instance provisions 1 vCPU, 6 GiB memory, and 12 GB disk. Running it always-on for a full month is therefore dominated by provisioned memory rather than by how busy the agent is. At the published rates, that is roughly 40 to 50 US dollars per month including the plan fee, mostly memory, before egress.
+
+This matters for the lifecycle decision below:
+
+- **Socket channels keep the Container awake**, so they pay the always-on rate. A small always-on virtual machine is often cheaper. Choose Cloudflare here for its operational model, colocation with other Cloudflare services, or the R2 durability path, not to save money.
+- **Webhook-only installations sleep**, and a sleeping Container bills nothing. That is where this target is genuinely inexpensive.
+
+Verify current rates on [Cloudflare's Containers pricing page](https://developers.cloudflare.com/containers/pricing/) before committing; these figures are estimates from the published rate card and change independently of OpenClaw.
+
+## Observability
+
+Stream Worker and Container logs while reproducing an issue:
+
+```bash
+npx wrangler tail
+npx wrangler containers list
+npx wrangler containers instances --json
+```
+
+Gateway logs stay inside the Container. Reach them over the temporary SSH session described in the bootstrap step, or forward them to your own collector. The Container filesystem is ephemeral, so treat in-Container logs as debugging output rather than as a durable record.
+
## Choose the lifecycle mode
`OPENCLAW_WEBHOOK_ONLY` defaults to `false`, which keeps the Container running through idle periods. Keep this default for channels that maintain sockets or long-lived processes, including:
@@ -191,6 +263,26 @@ npm run deploy
Test updates and rollbacks against a separate R2 bucket first. Preserve current state before activating older bytes.
+## Troubleshooting
+
+**Worker returns 5xx and the Container never becomes ready** -- Cloudflare only runs `linux/amd64` images pulled from a public registry. Rebuild with `--platform linux/amd64`, confirm the derived Docker Hub repository is public, and confirm `containers[].image` uses the pushed digest rather than a moving tag.
+
+**Deployment succeeds but every request times out** -- The Container helper waits for `GET /startupz`. Check that the Gateway inside the Container listens on port `8080` and that no bootstrap step changed the port.
+
+**Litestream logs authentication or signature errors** -- Litestream needs R2 S3 API credentials, which are not the same as a Cloudflare API token. Create an R2 API token and use its access key ID and secret access key, and confirm `LITESTREAM_ENDPOINT` contains your account ID.
+
+**First boot logs no databases to restore** -- Expected on an empty bucket. The entrypoint treats an empty replica listing as a fresh installation and starts the Gateway normally.
+
+**`/readyz` returns 503 while `/startupz` returns 200** -- Working as designed. Startup finished, and a configured channel account is unhealthy. Inspect channel status rather than restarting the Container; see [Health checks](/gateway/health#http-probes).
+
+**`wrangler containers ssh` is rejected** -- SSH ships disabled. Add `"ssh": { "enabled": true }` to the container entry, redeploy, then connect.
+
+**Configuration disappeared after a sleep or a redeploy** -- Litestream restores SQLite databases only. `openclaw.json`, credential files, installed plugin files, and workspaces live on the ephemeral disk. Reapply your bootstrap runbook, or keep the installation always-on and take [full archives](/install/backups#full-archives).
+
+**Channel sessions break after a restore** -- Restoring older bytes rolls back ratcheting credentials. Relink the affected channel and review pending approvals; see [Limits and recovery](#limits-and-recovery).
+
+**WebSocket connections close on large payloads** -- Cloudflare closes received WebSocket messages larger than 32 MiB. Reduce attachment sizes or transfer them out of band.
+
## Related
- [Backups](/install/backups)
diff --git a/scripts/cloudflare/README.md b/scripts/cloudflare/README.md
index a1dd643fe6d7..4d4557820486 100644
--- a/scripts/cloudflare/README.md
+++ b/scripts/cloudflare/README.md
@@ -125,10 +125,23 @@ node openclaw.mjs doctor --json
Keep the exact bootstrap recipe in a private, reproducible runbook. Litestream does not replicate `openclaw.json`, credential files, installed plugin files, or workspaces.
+## 5. Verify before relying on it
+
+```bash
+curl -sS https://.workers.dev/startupz
+npx wrangler tail
+```
+
+Then rehearse recovery, because an untested restore path is not a backup: send one message, wait about ten seconds for replication, force a Container replacement, and confirm the conversation survives. Fix replication before connecting production channels if it does not.
+
+Measured on this template against a real R2 bucket: about 2.4 s write-to-replica, about 9 s to restore both databases, and a healthy Gateway about 13 s after a fresh start.
+
## Scale-to-zero policy
The template defaults `OPENCLAW_WEBHOOK_ONLY` to `false`. This keeps the Container alive across idle periods for Discord, Slack Socket Mode, WhatsApp, and every other channel that maintains a socket or polling process.
+Cost follows directly from that choice. Memory and disk bill on provisioned instance resources for as long as the Container is awake, so an always-on `standard-2` is dominated by its 6 GiB of provisioned memory rather than by agent activity -- roughly 40 to 50 US dollars per month at published rates, where a small always-on VM is often cheaper. A sleeping webhook-only Container bills nothing. Check [current rates](https://developers.cloudflare.com/containers/pricing/) before committing.
+
Set `OPENCLAW_WEBHOOK_ONLY` to `true` only when every enabled channel receives traffic through HTTP webhooks. The Container then stops after ten idle minutes and cold-starts on the next request. Because its disk is fresh after sleep, enable this only when an external process can reapply the declarative bootstrap above; Litestream alone restores SQLite, not the config files needed to activate channels.
## Operational constraints
@@ -154,6 +167,18 @@ npm run deploy
Treat rollbacks like restores: stop traffic where possible, preserve the current state first, and review credentials, approvals, and delivery state before activating older database bytes.
+## Troubleshooting
+
+- **Container never becomes ready:** the image must be `linux/amd64` and pulled from a public registry, referenced by digest rather than a moving tag.
+- **Requests time out after a successful deploy:** the Container helper waits for `GET /startupz` on port `8080`; confirm the Gateway still binds that port.
+- **Litestream authentication or signature errors:** Litestream needs R2 _S3 API_ credentials, not a Cloudflare API token, and `LITESTREAM_ENDPOINT` must contain the account ID.
+- **First boot reports no databases to restore:** expected on an empty bucket; the entrypoint treats that as a fresh installation.
+- **`/readyz` is 503 while `/startupz` is 200:** by design. Startup finished and a channel account is unhealthy; inspect channel status instead of restarting.
+- **`wrangler containers ssh` rejected:** SSH ships disabled; add `"ssh": { "enabled": true }`, redeploy, then connect.
+- **Config missing after sleep or redeploy:** Litestream restores SQLite only. Reapply the bootstrap runbook or stay always-on and take full archives.
+
+Full operator guide: .
+
## Files
- `wrangler.jsonc`: Worker, Durable Object, Container application, and R2 binding