# Country Blocking and the Cloudflare Real-IP Gotcha

> GuardPress country blocking behind Cloudflare sees every visitor as an edge IP unless real-IP detection is on; GuardPress 1.6.30+ auto-detects Cloudflare.

- Canonical: <https://royalplugins.com/support/guardpress/country-blocking-cloudflare-real-ip/>
- HTML version: <https://royalplugins.com/support/guardpress/country-blocking-cloudflare-real-ip/>

---

GuardPress **Geographic Access Rules** let you block all traffic from selected countries with a checkbox and a multi-select. The thing nobody warns you about: behind Cloudflare (or any reverse proxy), every request reaches your server from the proxy’s IP — not the visitor’s. Without real-IP detection, country blocking would resolve every visitor to the same country and either block 0% or 100% of traffic. GuardPress 1.6.30+ auto-detects Cloudflare and handles this correctly out of the box; this page explains how it works, how to verify it, and what to do when the auto-detection isn’t enough.

### The one-line summary

Enable **GuardPress → IP Management → Geographic Access Rules**, pick the countries you want to block, save. If your site is behind Cloudflare, GuardPress 1.6.30 and later already trusts the `CF-Connecting-IP` header automatically when the connection arrives from a published Cloudflare edge range — no extra config required.

## Quick Setup

The whole configuration lives on a single admin page:

#### Open IP Management

In WP Admin, go to **GuardPress → IP Management**. Scroll past the Whitelist and Blacklist sections to the **Geographic Access Rules** card.

![GuardPress admin submenu in the WordPress sidebar showing Dashboard, Firewall, Malware Scanner, Outdated Software, Database, File Monitor, Audit Log, Backups, IP Management (highlighted), Migration, Settings, and License entries](https://royalplugins.com/support/guardpress/images/guardpress-ip-management-nav.png)

#### Enable geographic rules

Tick the **Enable geographic access rules** checkbox. The country selector reveals itself below.

![Geographic Access Rules card with the Enable geographic access rules checkbox unticked. Below it, a How IP Management Works info panel explains Whitelist, Blacklist, CIDR notation, Whitelist-first precedence, that country detection uses IP geolocation cached for 7 days, and that IPs locked out 3 or more times are automatically banned permanently](https://royalplugins.com/support/guardpress/images/guardpress-geo-rules-empty.png)

#### Add the countries you want to block

Pick a country from the dropdown and click **Add**. Repeat for every country to block. Each selection appears as a removable pill below the dropdown.

![Geographic Access Rules card with the Enable geographic access rules checkbox ticked. A country dropdown (showing Andorra as the first alphabetical option) sits beside a blue Add button. Below them: No countries restricted yet message, and a Save Geographic Rules button](https://royalplugins.com/support/guardpress/images/guardpress-geo-rules-active.png)

#### Save the rules

Click **Save Geographic Rules**. The change takes effect on the next request; no plugin restart needed. An audit log entry is written (**GuardPress → Audit Log**, action `geo_rules_updated`) so you have a record of when the policy changed and how many countries are on the list.

Blocklist only, not allowlist

The current implementation is a single *blocklist*: any country on the list is blocked, everyone else is allowed. There is no opposite mode (“only allow these countries”). If you need allow-list-style behaviour, the closest workaround is to add every country except your allowed set to the blocklist — tedious but works; the list supports the full ISO 3166-1 alpha-2 country space.

## How GuardPress Looks Up Country From IP

GuardPress doesn’t bundle a GeoIP database (it would bloat the plugin zip and go stale quickly). Instead it asks **ip-api.com** for the country code for each new visitor IP:

- **Endpoint:** `https://ip-api.com/json/<ip>?fields=countryCode`
- **HTTP timeout:** 5 seconds (if ip-api is slow or unreachable, GuardPress fails open — the request is allowed through and the result is cached as `XX` / unknown for a week)
- **Cache:** the resolved country code is cached per IP for 7 days in a WordPress transient (`gp_geo_<md5(ip)>`). Same visitor returning later doesn’t re-trigger the lookup until the cache expires
- **Private / reserved IPs** are skipped — `192.168.x.x`, `10.x.x.x`, `127.0.0.1`, etc. cannot resolve to a meaningful country and are returned as `XX`

Whatever country code ip-api returns gets compared against the blocklist you saved. If it matches, GuardPress emits a 403 Access Denied page (or a JSON / XML-RPC equivalent if the request is API-shaped — see [Firewall overview](https://royalplugins.com/support/guardpress/#firewall) for the per-context block response format).

## The Cloudflare Gotcha

Here’s the part that bites people. When a visitor in (say) Vietnam loads your site:

- **Without Cloudflare in front:** the request arrives at your server with `REMOTE_ADDR` set to the visitor’s IP in Vietnam. ip-api returns `VN`. If Vietnam is on the blocklist, the visitor is blocked. Correct.
- **With Cloudflare in front:** the visitor connects to a Cloudflare edge node (in Singapore, San Francisco, wherever’s closest), and Cloudflare opens a NEW connection to your origin server. From your server’s perspective, `REMOTE_ADDR` is a Cloudflare edge IP (`104.16.x.x`, `172.64.x.x`, etc.) — not the Vietnamese visitor. ip-api returns `US` (or wherever the edge node lives), which probably isn’t on your blocklist, so the visitor passes the geo check. Country blocking does nothing.

The opposite failure is just as bad: if you happen to put US on the blocklist and your site is behind Cloudflare with US-region edge nodes, you may end up blocking every visitor — because every connection from Cloudflare to your origin looks like it’s coming from the US.

Cloudflare publishes a header, `CF-Connecting-IP`, which carries the real visitor IP through the proxy. The fix is for GuardPress to read THAT header instead of `REMOTE_ADDR` when the request actually came through Cloudflare. The hard part is doing it safely: any plugin that blindly trusts a forwarded header gets spoofed instantly by anyone sending the request with a fake header.

## How GuardPress Resolves the Real IP

GuardPress uses a three-layer trust check before honouring any forwarded header. The layers are evaluated in order, and the first match wins:

1. **Loopback** — if `REMOTE_ADDR` is `127.0.0.1` or `::1`, the request came from the local machine (typically a reverse proxy on the same host: CloudPanel, nginx-fronted Apache, Docker bridge networks, LiteSpeed proxy). External attackers can’t spoof loopback because the TCP/IP stack rejects packets with a loopback source IP on non-loopback interfaces. Forwarded headers are trusted.
2. **Known CDN ranges (Cloudflare)** — if `REMOTE_ADDR` matches one of the 22 baked-in Cloudflare IPv4 + IPv6 CIDRs, the connection is provably from Cloudflare’s edge network. `CF-Connecting-IP` is trusted. **This is the layer that fixes the gotcha automatically**, added in 1.6.30.
3. **Admin allowlist** — if you’ve manually pasted IPs / CIDRs into **GuardPress → Settings → Network / Proxies → Trusted Proxies**, any connection from one of those addresses gets its forwarded header honoured. Use this for non-Cloudflare reverse proxies (Sucuri, custom load balancers, etc.).

When at least one layer matches, GuardPress walks the following headers in order and uses the first that contains a valid IP:

1. `HTTP_CF_CONNECTING_IP` (Cloudflare’s real-visitor header)
2. `HTTP_X_FORWARDED_FOR` (standard reverse-proxy header — if comma-separated, the leftmost IP is used as the originating client)
3. `HTTP_X_REAL_IP` (nginx convention)
4. `HTTP_CLIENT_IP` (legacy)

If `REMOTE_ADDR` doesn’t match any of the three trust layers, GuardPress falls back to `REMOTE_ADDR` verbatim. This is the safe default for direct-attached hosts with no reverse proxy — it stops attackers from setting `CF-Connecting-IP` in a direct request and forging their IP.

Why this is safe

`REMOTE_ADDR` is set by the TCP layer when the connection is established. It is the IP address that completed the TCP three-way handshake with your server. It cannot be spoofed at this layer — if an attacker faked the source IP, the SYN-ACK response would go to the faked address, not them. So a `REMOTE_ADDR` match against a published Cloudflare edge range is cryptographic-grade evidence that the connection actually came through Cloudflare, which is what justifies trusting Cloudflare’s header without further admin configuration.

## Verifying the Auto-Detection Worked

Before relying on country blocking in production, confirm GuardPress is seeing real visitor IPs and not Cloudflare edge IPs.

### The easy check: trigger a block and look at the audit log

Add a country you can simulate (or your own country if you can VPN somewhere else) to the blocklist. Hit the site from a matching IP. Then go to **GuardPress → Audit Log** and look for entries with `action = firewall_block`. The log entry includes the IP that was blocked:

- If the logged IP is the visitor’s real IP — auto-detection is working.
- If the logged IP is a Cloudflare edge IP (`104.16.x.x`, `172.64.x.x`, `162.158.x.x`, `173.245.x.x`, etc.) — auto-detection isn’t firing. Skip ahead to the “manual configuration” section below.

### The forensic check: dump REMOTE\_ADDR + headers from a test request

Drop a temporary one-liner into your theme’s `functions.php` (or into a must-use plugin if you have one):

```
add_action( 'init', function() {
    if ( ! current_user_can( 'manage_options' ) ) return;
    if ( ! isset( $_GET['gp_ip_check'] ) ) return;
    echo '<pre>';
    echo 'REMOTE_ADDR: ' . esc_html( $_SERVER['REMOTE_ADDR'] ?? '(none)' ) . "\n";
    echo 'CF-Connecting-IP: ' . esc_html( $_SERVER['HTTP_CF_CONNECTING_IP'] ?? '(none)' ) . "\n";
    echo 'X-Forwarded-For: ' . esc_html( $_SERVER['HTTP_X_FORWARDED_FOR'] ?? '(none)' ) . "\n";
    echo 'GuardPress detected: ' . esc_html( GuardPress_Logger::get_client_ip() ) . "\n";
    echo '</pre>';
    exit;
} );
```

Visit `https://your-site.com/?gp_ip_check=1` while logged in as admin. The output tells you exactly what GuardPress is seeing. If `REMOTE_ADDR` is a Cloudflare range and `CF-Connecting-IP` is set and GuardPress detected = the `CF-Connecting-IP` value, auto-detection is healthy. Remove the snippet when you’re done.

Don’t leave the snippet in production

It exposes the visitor’s IP only to logged-in admins, so it’s not a leak, but it’s still test code in a production site. Delete it once you’ve confirmed.

## Manual Configuration (When Auto-Detection Isn’t Enough)

Auto-detection covers Cloudflare and same-host reverse proxies. If you’re behind a different proxy — Sucuri, StackPath, a custom load balancer, an enterprise WAF in front of Cloudflare — you need to tell GuardPress which addresses to trust.

### Add CIDRs to Trusted Proxies

Go to **GuardPress → Settings** and scroll to the **Network / Proxies** section. The **Trusted Proxies** textarea accepts one IP or CIDR per line. Examples:

- `10.0.0.5` — a single internal load balancer
- `192.168.1.0/24` — an entire internal subnet
- `127.0.0.1` — a reverse proxy on the same host (also handled by auto-detection, but explicit is fine)
- Whatever CIDRs your CDN publishes — pull from your CDN’s docs / IP list page

Save. GuardPress will now honour `X-Forwarded-For` / `CF-Connecting-IP` / etc. on any request originating from those addresses, in addition to the built-in Cloudflare ranges.

### Extending or overriding the built-in Cloudflare ranges

The 22 baked-in CIDRs are snapshotted from cloudflare.com/ips-v4 + /ips-v6. Cloudflare’s edge ranges change rarely (years between additions) but if a new range appears before the next GuardPress release, you have two options:

- **Quick fix:** paste the new Cloudflare CIDR into Trusted Proxies. It joins the existing built-in list.
- **Code fix:** filter `guardpress_known_proxy_ranges` from a must-use plugin to add or replace entries. The filter receives an associative array (`'cloudflare' => [ ...cidrs ]`); add your own provider key with your own CIDRs and they’re trusted as built-ins.

## What a Blocked Visitor Sees, and How to Unblock

A visitor matching a blocked country gets a plain **Access Denied** HTML page with a short error code (the first 8 characters of an MD5 of the block reason — useful for support, doesn’t leak the rule details). The HTTP status is 403; the page advises contacting the site administrator. There is no captcha, no “prove you’re a human” flow — geo blocking is a hard block.

To let a specific person through despite their country being blocked, add their IP to the whitelist:

1. **GuardPress → IP Management** → Whitelist section
2. Add the visitor’s real IP (or CIDR for a range)
3. Save

Whitelist is checked BEFORE the geographic rule, so whitelisted IPs always bypass country blocking (and every other firewall check). The same precedence applies to legitimate search-engine bots — see the next section.

## Search Engine Bots Are Exempt

GuardPress short-circuits the firewall — including geo blocking — for legitimate search-engine and platform crawlers. This prevents geo rules from accidentally de-indexing your site by blocking the Googlebot or Bingbot crawl. The major crawlers are recognised by user agent AND verified via reverse-DNS lookup against documented hostname suffixes:

- **Google** family (Googlebot, Google-InspectionTool, GoogleOther, Google-Extended, AdsBot-Google, Mediapartners-Google, Feedfetcher-Google, Google-Read-Aloud) — verified against `.googlebot.com`, `.google.com`, `.googleusercontent.com`
- **Bing** family (bingbot, msnbot, BingPreview, adidxbot) — verified against `.search.msn.com`
- **Yahoo Slurp** — verified against `.crawl.yahoo.net`
- **Yandex** family — verified against `.yandex.com` / `.yandex.ru` / `.yandex.net`
- **Baidu** — verified against `.baidu.com` / `.baidu.jp`
- **DuckDuckGo** — verified against `.duckduckgo.com`
- **Apple Applebot** — verified against `.applebot.apple.com` / `.apple.com`

Social and platform crawlers (Facebook, Twitter, LinkedIn, Pinterest, Slack, Telegram, WhatsApp, Discord) plus archive bots (archive.org, ia\_archiver) and the major uptime monitors (UptimeRobot, StatusCake, Jetpack monitors) are trusted by user agent only — their infrastructure is too heterogeneous for DNS verification to work reliably.

This list is filterable via the `guardpress_verified_bots` filter (DNS-verified bots) and `guardpress_whitelisted_bots` filter (UA-trust bots) if you need to add or remove entries.

## Common Pitfalls

### VPN and proxy users from blocked countries get through

Country blocking is IP-based. A determined visitor in a blocked country can route through a commercial VPN whose exit nodes are in an allowed country, and they’ll resolve to the allowed country. There’s no general defence against this at the geo-block layer — if you need stronger gating, combine country blocking with login-protection (CAPTCHA, 2FA), rate limiting, and content-level access controls. Geo blocking is a noise filter, not an air gap.

### Mobile carriers with IPs that rotate across countries

Some mobile carriers (especially in border regions, or low-cost MVNOs) assign IPs from country pools that don’t match the user’s physical location. A user in Belgium may resolve to France one session and Germany the next. If you block one of those, you’ll get intermittent customer complaints from real visitors. Tune the blocklist conservatively — block countries that produce sustained malicious traffic on your specific site, not countries you have a general impression about.

### Page caches serving stale blocked-page responses

If your site has server-side caching (ForgeCache, Cloudflare full-page cache, host page cache), the 403 Access Denied page COULD be cached and served to subsequent visitors from non-blocked countries. GuardPress sends `nocache_headers()` with the block, which signals downstream caches not to store the response — but a cache that ignores cache-control headers (some aggressive Cloudflare Cache Rules, Varnish with hard overrides) can still misbehave. If you see country-block responses showing up for visitors who shouldn’t be blocked, audit your page cache’s honor-cache-headers setting first.

### The visitor is whitelisted but still gets blocked

The whitelist matches against the IP GuardPress detects, not the raw `REMOTE_ADDR`. If real-IP detection isn’t configured behind your proxy, you’ll be whitelisting the visitor’s real IP while GuardPress is seeing the proxy IP — and the whitelist never matches. Run the diagnostic snippet from the “Verifying” section above to confirm the real IP is being detected.

### ip-api.com is rate limiting your server

ip-api.com’s free tier is generous (~45 requests per minute per source IP) but high-traffic sites with low cache hit rates can occasionally hit it. When that happens, lookups fail and ip-api returns nothing — GuardPress caches the result as `XX` (unknown) for a week, and the unknown visitor falls through the geo check (fail-open). You can see this in the audit log if firewall blocks suddenly stop appearing for known-blocked countries. The 7-day cache TTL means the impact is bounded, but if it’s persistent, consider upgrading to ip-api’s commercial tier or splitting traffic across multiple egress IPs.

## FAQ

### Does GuardPress send my visitors’ IPs to a third party?

Yes, to ip-api.com, for the country lookup. The visitor’s IP is sent in the URL path; ip-api returns just the country code. The result is cached for 7 days per IP so the same visitor doesn’t trigger repeated lookups. If you can’t accept the third-party callout (strict data-residency or GDPR posture), don’t enable geographic rules — the rest of the firewall doesn’t need GeoIP.

### Can I block a region or continent instead of individual countries?

Not directly. The selector is country-level. To block a region, add every country in the region to the blocklist individually — you can do this in one save.

### What happens during the ip-api lookup if it’s slow?

The lookup has a 5-second HTTP timeout. If ip-api doesn’t respond within 5 seconds, GuardPress fails open — the visitor is allowed through and the country is cached as `XX` (unknown) for a week. The page load isn’t held up beyond 5 seconds. Worst case during an ip-api outage is that geo blocking briefly stops working; the site keeps serving normally.

### How do I add a custom CDN’s ranges if GuardPress doesn’t know about them?

Two options. Quick: paste their published IP ranges into **GuardPress → Settings → Trusted Proxies**. Permanent / code-driven: use the `guardpress_known_proxy_ranges` filter from a must-use plugin to register a new provider key with its CIDRs. Both work; the textarea is friendlier for non-developers and survives plugin updates.

### I’m getting flooded with “Geographic restriction: XX” entries in my audit log

That means a lot of automated traffic from blocked countries is hitting your site, which is exactly what country blocking is for — the alert noise IS the protection working. If the audit log volume is more than you want, tune email alert throttling at **GuardPress → Settings → Email Alerts → Alert Throttling** (introduced in 1.6.29) so you get a summary instead of one email per block. The audit log itself is pruned daily.

## Still Stuck? Email Priority Support

If country blocking isn’t blocking the visitors you expect, or it’s blocking visitors you don’t want to block:

Email **[support@royalplugins.com](mailto:support@royalplugins.com)** with the diagnostic info below. Priority email support is included with your GuardPress Pro license — typical response time is within 24 hours.

#### Information to include in your email

- **GuardPress version** from **WP Admin → Plugins** (1.6.30 or higher is recommended for Cloudflare auto-detection)
- **WordPress version** from **WP Admin → Updates**
- **Are you behind Cloudflare / Sucuri / StackPath / a custom load balancer?** Name the proxy and whether it’s proxying ALL traffic or only some paths
- **Country codes on your blocklist** (e.g. `CN, RU, KP`)
- **Output of the diagnostic snippet** from the “Verifying” section above, captured from a request you expected to be blocked
- **A sample audit log entry** from **GuardPress → Audit Log** showing a recent firewall block (action `firewall_block`) — we need to see what IP GuardPress logged
- **If unblocking a customer:** their real IP (you can ask them to visit `https://api.ipify.org` to get it) and the country code we should expect for that IP

Related GuardPress topics

If you’re tightening up firewall and access controls more broadly:

- [**Cloudflare Turnstile Setup**](https://royalplugins.com/support/guardpress/cloudflare-turnstile-setup/) — the other “GuardPress + Cloudflare” setup, this time on the login form
- [**IP Management overview**](https://royalplugins.com/support/guardpress/#ip-management) — whitelist, blacklist, geo rules, CIDR notation
- [**Quieting Security Alert Email Flood**](https://royalplugins.com/support/guardpress/quieting-security-alert-emails/) — if firewall blocks are flooding your inbox

[Back to GuardPress Support](https://royalplugins.com/support/guardpress/)
[GuardPress Plugin Page](https://royalplugins.com/guardpress/)
