• Aug 15, 2026
  • 5 min read

Debugging Notes from a Week on Cloudflare (and Docker)

Over one intense week I moved three sites to a shared forms Worker, added redirects, a cron Worker, a homelab mirror in Docker and an email pipeline. Most of the time went into the work itself. A surprising amount went into small traps that each cost between ten minutes and an hour.

This is the list I wish I had read first. Every item follows the same shape: what I saw, why it happened, and what fixed it.

Cloudflare Pages

1. A new secret does nothing

  • Symptom: I added a secret to a Pages project, but the Function still behaved as if it were missing.
  • Cause: Pages bakes environment variables and secrets into a deployment. Existing deployments keep the old values.
  • Fix: redeploy after changing secrets. Treat “secret changed” as “deploy needed”.

2. wrangler pages deployment tail refuses to start

  • Symptom: the tail command asks for something I didn’t give it.
  • Cause: for Pages, tailing works per deployment, not per project.
  • Fix: list deployments first, copy the deployment ID, then tail that one.

3. www returns a 522

  • Symptom: the apex worked, www. returned Cloudflare error 522 (connection timed out).
  • Cause: the DNS record existed, but the hostname was never attached to the Pages project as a custom domain. Cloudflare had nowhere to send it.
  • Fix: add www under the project’s Custom domains, then let a redirect rule pick the canonical host. The full setup is in apex vs www and pages.dev redirects.

Redirects

4. A redirect rule that never matches

  • Symptom: a zone Redirect Rule saved fine and never fired.
  • Cause: a leading space pasted into the URL field. The rule was valid; it simply described a URL nobody requests.
  • Fix: retype the field instead of pasting, and test with curl -sI https://example.com/ right after saving.

5. Bulk redirects “don’t work”

  • Symptom: a new Bulk Redirect for a *.pages.dev hostname still served the site for a while.
  • Cause: propagation. Bulk Redirects take time to reach every edge.
  • Fix: wait a few minutes before debugging. I now keep a small script that checks every expected status code and Location header, and I run it again after the change has settled.

Workers

6. A warning about a header value

  • Symptom: the Worker logged a warning when sending an ntfy alert.
  • Cause: I put an em dash in the Title header. HTTP header values should stay ASCII.
  • Fix: New enquiry - site.com instead of New enquiry — site.com, plus a test asserting every header value matches /^[\x20-\x7e]*$/.

7. 403 from my own site, only from Python

  • Symptom: a Python script calling one of my endpoints got 403 Forbidden. curl and the browser got the expected response.
  • Cause: Cloudflare rejects the default Python-urllib user agent before the Worker runs (most likely Browser Integrity Check; I didn’t pin down which feature). It happened on all my zones.
  • Fix: send a normal User-Agent from scripts. Mail clients and real browsers were never affected, but any test script must be.

8. A test that fails only when the whole suite runs

  • Symptom: a confirm-link test failed with “subscription not found”, but passed alone.
  • Cause: the previous test didn’t await its ctx.waitUntil() promises. A late email send from that test landed in the next test’s mocks, which then read the wrong confirmation link.
  • Fix: drain the pending promises in beforeEach:
beforeEach(async () => {
  await Promise.allSettled(pending ?? []);
  pending = [];
});

9. Type checking fights itself

  • Symptom: tsc errors about duplicate or conflicting global types.
  • Cause: Node’s types (for node:test) and @cloudflare/workers-types both define globals such as Request and fetch.
  • Fix: type-check only src/ (Worker code). The tests run under Node with --experimental-strip-types and don’t go through tsc.

wrangler and local tooling

10. 401 with a token that “works”

  • Symptom: wrangler commands worked, but the API returned 401 when I reused wrangler’s login token in a script.
  • Cause: I read the token from ~/.config/.wrangler/config/default.toml, a copy that had expired months earlier. The live login was in ~/.wrangler/config/default.toml.
  • Fix: for anything long-running, create a scoped API token (for example Workers KV Storage: Read) and never reuse the OAuth login.

11. The wheel that wasn’t there

  • Symptom: a freshly built Python wheel didn’t show up in git status.
  • Cause: uv build --out-dir <dir> writes a .gitignore containing * into the output directory.
  • Fix: delete that .gitignore when the wheel is meant to be committed (my Pyodide app pins one).

12. Auditing code that isn’t the code

  • Symptom: my audit of a repo listed problems that had already been fixed.
  • Cause: the local clone was weeks behind GitHub.
  • Fix: git fetch, then read origin/<branch> instead of the working copy. Never reset a clone that has uncommitted work; it may be someone’s (often my own) work in progress.

Docker and Python

13. The container can’t write its database

  • Symptom: unable to open database file on first start.
  • Cause: the bind-mounted ./data didn’t exist, so Docker created it, owned by root. The container runs as uid 1000.
  • Fix: mkdir -p data before docker compose up, and say so in the README.

14. Empty logs from a working service

  • Symptom: docker compose logs showed nothing, although the service was doing its job.
  • Cause: Python block-buffers stdout when it isn’t a terminal. My loop printed a line and slept for 15 minutes; the line stayed in the buffer. Errors still appeared because stderr isn’t buffered the same way.
  • Fix: ENV PYTHONUNBUFFERED=1 in the Dockerfile. I proved it with two identical containers, one with the variable and one without.

The pattern behind the list

CategoryItemsCommon thread
Configuration timing1, 5The change is saved but not yet active
Configuration content3, 4, 6It looks right in the dashboard, but isn’t
Environment7, 10, 13The code is fine; the context around it isn’t
Observability2, 12, 14I was looking at the wrong thing
Tests8, 9Shared state between test runs or type systems

Takeaways

  • After any change at the edge, verify from outside with curl -sI before assuming the change failed or succeeded.
  • When code is right and behaviour is wrong, check what is actually running: the deployment, the clone, the token, the user ID.
  • Keep a gotchas file per project. Each line here took minutes to find and seconds to read.