6.4 KiB
| created | path | project | tags | type | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 2026-06-29 | Sources/Dev | cloudflare-cache-strategy |
|
session-notes |
Outcome
A content contributor (Italy-based, username sheridanbf731) was repeatedly locked out of /wp-admin/ on production. What presented as an "IP block / login" problem turned out to be three stacked, unrelated issues that had to be peeled apart in sequence. All three were resolved; contributor confirmed able to log in, move between front-end and admin, and log out without being blocked.
Root causes, in the order they were found and fixed:
-
Editor role bounced from wp-admin — Ultimate Member's
um_block_wpadmin_by_user_role(hooked toinit@ priority 99) was enforcing a stale "Can access wp-admin? No" value. The per-role setting had been flipped to Yes and the DB + UM debug panel both showed Yes, but the function was reading a stale Redis-cached role object.wp cache flushcleared it and the bounce stopped. (The role setting was correct three steps before the fix actually took — the cache was the blocker.) -
reauth=1session-invalidation loop + Wordfence lockout cascade — the real recurring problem. A Cloudflare cache rule ("Bypass cache for admin") only bypassed cache for/wp-adminand/wp-login.phppaths. Logged-in users browsing front-end pages (homepage, recipes — where UM/role redirects send them) were served cached, logged-out pages. WordPress saw the cookie/cache mismatch, threwreauth=1, and backgroundadmin-ajax.phprequests fired with the confused session state. Wordfence logged those AJAX requests as failed logins → two of them tripped the brute-force lockout. Fixed by addinghttp.cookie contains "wordpress_logged_in_"to the cache-bypass rule, so Cloudflare never serves cache to any logged-in user on any path. -
Two broken conditions in the same Cloudflare rule — the n8n and gitea bypass conditions used
URI Path contains "https://n8n...", which can never match (a URI path never contains a hostname). Corrected syntax ishttp.host eq "n8n.plantbasedsoutherner.com". Travis chose to leave the originals in place for now rather than edit them tonight; the Host-based correction is captured here as a follow-on.
Topics Covered
- Wordfence block-page diagnosis vs. manual block list (the IP was never manually blocked; it was a failed-login lockout)
- Cloudflare IPs in logs: all visitor traffic appears to Wordfence as Cloudflare edge IPs (
172.69.x.x,172.70.x.x), rotating and geolocated to wherever the edge node is (Milan, Tallinn). Wordfence counts failed logins per IP, so a shared Cloudflare IP pools everyone's failures onto one counter, making lockouts fire "faster than the threshold." - Ultimate Member two-layer access control: per-role "Can access wp-admin?" toggle vs. role priority (UM uses the highest-priority role's settings when a user has multiple). Contributor had a single role, so priority wasn't the issue here.
- Redirect-source diagnosis:
x-redirect-by: WordPressresponse header indicates a corewp_redirect()call (theme/snippet/mu-plugin) rather than a self-labeling plugin. wp evalhook-dump technique to list every callback oninit/admin_init/template_redirectwith file+line for closures — this is what surfacedum_block_wpadmin_by_user_roleby name.- Confirmed clean: all WPCode snippets, stock Astra theme + functions.php, mu-plugins (only Elementor Safe Mode), and
grep -rni "setcookie"across themes/mu-plugins (empty — confirms cookie-busting code was replaced by the n8n publish-webhook approach). - The Cloudflare "Verify you are human" challenge on
/wp-login.php(Turnstile/managed challenge,__cf_chl_f_tktoken) is normal, benign bot protection — risk-scored per session, so trusted browsers skip it and fresh/private sessions get it. Not part of the bug.
Key Learnings
- When a role/permission change "doesn't take" but the setting clearly reads correct, flush the object cache (Redis) before assuming the setting is wrong. Object cache + role meta is a classic stale-read trap. This alone would have saved many steps.
- Cache-bypass for logged-in WordPress users must be keyed on the
wordpress_logged_in_cookie, not on URL path. Path-only bypass leaves front-end pages cacheable, which poisons logged-in sessions and producesreauth=1loops. This is the single most important fix from the session. - Wordfence behind Cloudflare must be told how to read real client IPs (Settings → "How does Wordfence get IPs?" → CF-Connecting-IP / X-Forwarded-For). Until set, all per-IP logic (lockouts, blocks, allowlists) operates on shared Cloudflare edge IPs and behaves erratically. Do not allowlist or block
172.69.x.x/172.70.x.xranges — those are Cloudflare, not visitors. This remained un-done at session end and is the top follow-on. - Cloudflare rule field discipline: URI Path is the path only; matching a subdomain requires the Host field (
http.host eq ...). - Stacked symptoms can share one root: the "rotating IPs," "faster than 3 attempts," and "blocked on logout" complaints were all downstream of the same Cloudflare-cache + shared-IP situation.
Follow-ons
- Set Wordfence "How does Wordfence get IPs?" to the CF-Connecting-IP (or X-Forwarded-For) option so Wordfence sees real client IPs. Highest-value remaining cleanup; safe to do any day. Until then, per-IP blocks/allowlists are unreliable.
- Fix the n8n/gitea cache-bypass conditions in the "Bypass cache for admin" rule — switch from the non-functional
URI Path contains "https://..."tohttp.host eq "n8n.plantbasedsoutherner.com"/http.host eq "gitea.plantbasedsoutherner.com". Test on staging first; verify n8n + gitea still load and that cache still bypasses correctly for them. - Rotate the hardcoded n8n webhook secret found in the
publish_postWPCode snippet (HMAC secret was exposed in chat) and move it towp-config.phpor an env var instead of inline in the snippet. - Consider replicating the corrected Cloudflare cache rule on staging and validating the full logged-in cycle there before treating the production fix as final.
- Optional: if the
/wp-login.phphuman-check becomes friction for the three known contributors, tune via a Cloudflare WAF skip rule for known users — low priority, currently working as intended.