You Replaced Your Security Gate With a Different Gate and Called It Progress

Implementation

Security

LittleBig.Co

What if switching from Cloudflare Access to Tailscale for WordPress login protection just moves the perimeter instead of improving it?

Full transparency: Links marked with (*) are affiliate links. Yes, we might earn a commission if you buy. No, that doesn’t mean we’re shilling garbage. We recommend what we’d actually use ourselves. Read our full policy.

Cloudflare Access protected your WordPress login endpoints. Tailscale protects your SSH. Swapping one for the other doesn’t solve the problem — it reshapes it. The real question is whether you need a gate at all, or whether layering existing tools eliminates the need for a dedicated auth proxy entirely.

The Assumption Everyone Makes

When Cloudflare Access gets replaced, you need an equivalent. Something has to sit in front of wp-login.php and decide who gets through. Tailscale handles SSH beautifully — enable the client, connect through the tunnel, done. So it should handle web endpoints the same way. Set up Split DNS, route traffic through the tailnet, problem solved.

Except it doesn’t work like that.

What Actually Happens

SSH works because you connect directly to the server’s Tailscale IP on port 22. The traffic flows through the tunnel by design. Web traffic is different. Your browser resolves the public domain name, hits the public IP, passes through Cloudflare, and arrives at NGINX. Tailscale is never involved. Enable the Tailscale client, check the NGINX access log, and you’ll still see your ISP’s IP address staring back at you.

The instinct is to fix this with DNS. Override resolution so the domain points to the Tailscale IP when the client is active. But if you do that in your DNS server (AdGuard Home, Pi-hole, whatever you use as your resolver), the override applies permanently — Tailscale on or off. Your sites become unreachable without the VPN running, which breaks everything from casual browsing to uptime monitoring.

Tailscale’s Split DNS feature solves the conditional routing problem, but it requires a nameserver per domain, not A record overrides. So now you need your DNS server listening on a Tailscale IP, Split DNS pointing domains to that nameserver, and DNS rewrites inside the resolver pointing back to the server’s Tailscale IP. Three moving parts to achieve what Cloudflare Access did with a toggle.

Why This Matters More Than You Think

The deeper problem isn’t the DNS plumbing. It’s the assumption that you need a replacement for Cloudflare Access at all.

Ask two questions first. Who actually needs to reach wp-login.php? And where does the real threat surface live?

If only you log in — which is true for most WordPress sites managed by a single operator — the answer is brutally simple. An NGINX location block that allows Tailscale IPs and denies everything else:

location = /wp-login.php {
    allow 100.64.0.0/10;
    deny all;

    include snippets/fastcgi-php.conf;
    fastcgi_pass unix:/run/php/php8.4-fpm.sock;
    include fastcgi_params;
}

The 100.64.0.0/10 range covers all Tailscale CGNAT addresses. Tailscale on: you connect, NGINX sees your Tailscale IP, access granted. Tailscale off: you hit the public IP, NGINX returns 403. No Split DNS. No auth proxy. No new infrastructure.

For sites where developers or designers need public login access, the tool you already have in front of every request handles it: Cloudflare. A WAF custom rule on wp-login.php with a managed challenge stops automated attacks at the edge. Your server never processes the request. FluentAuth inside WordPress catches anything sophisticated enough to solve the challenge. Two layers, both already deployed, zero additional complexity.

The Questions You Should Be Asking Instead

  • Which of my sites actually need public login access, and which can lock down to Tailscale-only?
  • Am I introducing an auth proxy (Authentik, Authelia) because I need centralized identity management, or because I’m reflexively replacing the gate I removed?
  • If I’m proxying through Cloudflare anyway, why am I solving bot traffic at the application layer instead of the edge?
  • Is the distributed brute-force pattern I’m seeing actually bypassing Cloudflare, or did I forget to re-enable a WAF rule after testing?

That last question matters more than it sounds. Disabling a Cloudflare rule for testing and forgetting to re-enable it means your server has been unprotected while you’ve been engineering a replacement. The fix isn’t a new architecture — it’s toggling a switch back on.

What This Looks Like in Practice

Twenty WordPress sites. Some are personal, some serve clients, a few need external collaborators logging in.

Tailscale-only sites get the NGINX allow/deny block. Attack surface for wp-login.php: zero. No WAF rules needed, no bot challenges, no brute-force attempts reaching PHP. The endpoint simply doesn’t exist for anyone outside the tailnet.

Public-login sites keep their Cloudflare WAF rule: path contains wp-login → managed challenge. Automated bots die at the edge. Legitimate users solve an invisible challenge and proceed. FluentAuth watches for credential-based attacks that survive the challenge. CrowdSec community blocklists pre-block known bad actors before they even connect.

No Authentik deployment. No Split DNS configuration. No new single point of failure masquerading as a security improvement.

Where Most People Get Stuck

“But Authentik gives me SSO and centralized user management.” It does. And that’s valuable when you’re managing identity across multiple services — client portals, internal dashboards, tools without built-in auth. If you have that use case, deploy Authentik for that use case. Don’t deploy it to protect WordPress login endpoints that simpler tools already cover.

“Managed challenges are too easy for bots to solve.” Some bots can solve managed challenges. If you’re seeing evidence of that in your FluentAuth logs after confirming the Cloudflare rule is active, escalate to an interactive challenge or a straight block on wp-login.php for non-allowlisted IPs. Test with data, not assumptions.

“What about attacks hitting my origin IP directly, bypassing Cloudflare?” If your firewall restricts ports 80 and 443 to Cloudflare’s IP ranges, this isn’t possible. Every HTTP request must transit Cloudflare. If FluentAuth is logging blocked IPs that don’t appear in Cloudflare’s Security Events, the most likely explanation isn’t a bypass — it’s that your WAF rule wasn’t active when those attempts occurred.


Strategic Opposition Principle: Before building a replacement for what you removed, verify that the tools you already have don’t solve the problem without adding new complexity.

Related Field Notes: Your All-in-One Security Plugin is a Single Point of Failure · Your Page Builder Is Getting You Banned From Your Own API