# Cloudflare Setup for XYZ Age Verification

The XYZ Age Verification plugin requires Cloudflare to detect visitor locations. This guide walks through setting up Cloudflare if your site isn't already proxied through it.

## Why Cloudflare Is Required

The plugin uses HTTP headers that Cloudflare adds to requests when configured:

- **CF-IPCountry** — the visitor's country code (e.g., `US`, `DE`, `GB`)
- **CF-Region-Code** — the visitor's state or province code (e.g., `TX`, `CA`, `BY`)
- **Verified-Bot** — whether the request is from a verified bot (e.g., Googlebot, Bingbot)

The location headers are how the plugin knows which region a visitor is from and whether they need to be age-verified. The bot header allows verified search engine crawlers to bypass the age gate, preserving your site's SEO. Without Cloudflare, the plugin has no way to determine visitor location and the age gate will not activate.

## Cloudflare Free Plan Is Sufficient

You do not need a paid Cloudflare plan. The free plan includes:

- DNS proxying
- Managed Transforms for visitor location headers
- Transform Rules for verified bot detection
- HTTPS with universal SSL certificates
- Basic DDoS protection
- CDN caching

All features required by the plugin are available on all Cloudflare plans, including the free tier.

## Setting Up Cloudflare

### Step 1: Create a Cloudflare Account

1. Go to [cloudflare.com](https://www.cloudflare.com) and sign up.
2. Click **Add a Site** and enter your domain name.
3. Select the **Free** plan.

### Step 2: Update Your DNS Records

Cloudflare will scan your existing DNS records. Review them and make sure:

- Your **A record** pointing to your server IP is present
- The **proxy status** (orange cloud icon) is **Proxied**, not DNS-only

If the cloud icon is gray ("DNS only"), click it to toggle to orange ("Proxied"). The proxy must be enabled for Cloudflare to add the geo headers.

### Step 3: Change Your Nameservers

Cloudflare will provide you with two nameservers. Update your domain registrar to use these nameservers. This usually takes effect within a few hours but can take up to 48 hours.

### Step 4: Configure SSL/TLS

In your Cloudflare dashboard:

1. Go to **SSL/TLS > Overview**.
2. Set the mode to **Full (strict)** if your server has a valid SSL certificate, or **Full** if it has a self-signed certificate.
3. Do NOT use "Flexible" — this can cause redirect loops with WordPress.

### Step 5: Enable Visitor Location Headers

In your Cloudflare dashboard:

1. Go to **Rules → Settings**.
2. Select the **Managed Transforms** tab.
3. Enable **Add visitor location headers**.

This sends `cf-ipcountry` and `cf-region-code` headers to your origin server, which the plugin uses for geo-based age gate decisions.

### Step 6: Create the Verified Bot Header Rule

This step allows verified search engine bots (Googlebot, Bingbot, etc.) to bypass the age gate so they can crawl your site normally. Without this rule, bots will be redirected to the age gate and your SEO will be negatively affected.

There are two ways to configure the Value field of this rule. The **basic configuration** is what the plugin defaults to and is what most operators will use. The **strong configuration** uses a per-site secret token and is recommended for sites with stricter compliance posture (operators who want a defensible answer to "is there any documented bypass path?" in a regulatory review). Both configurations work identically from the visitor's perspective; the difference is only in what an attacker who reaches the origin directly would need to know.

#### Basic configuration

1. Go to **Rules → Overview**.
2. In the **Request Header Transform Rules** section, click **Create Rule**.
3. Give the rule a name (e.g., `add verified bot header`).
4. Select **Custom filter expression** and click **Edit expression** (the `cf.client.bot` field is not available in the Expression Builder presets).
5. Enter the expression: `(cf.client.bot)`
6. Under **Then**, set the value-type dropdown (the leftmost selector on the "Modify request header" row) to **Set dynamic**. This tells Cloudflare to evaluate the Value field as an expression rather than treat it as a literal string.
7. Set the header:
   - **Header name**: `verified-bot`
   - **Value**: `cf.client.bot` (this expression evaluates to the literal string `true`)
8. Set **Place at**: First.
9. Click **Save**.

The plugin's Verified-Bot Token setting defaults to `true` to match this configuration. No further action required.

#### Strong configuration (recommended for compliance-sensitive sites)

This variant replaces the literal `true` value with a per-site secret. An attacker who reaches your origin server directly (e.g., bypassing Cloudflare via your origin IP) cannot use the bot-bypass without knowing this secret.

1. In the WordPress admin, go to **Settings → XYZ Age Verification**.
2. Next to the **Verified-Bot Token** field, click **Generate**. Copy the resulting hex string.
3. Save the plugin settings.
4. In the Cloudflare dashboard, follow steps 1-5 of the Basic configuration above.
5. Under **Then**, set the value-type dropdown (the leftmost selector on the "Modify request header" row) to **Set static**. **This is the critical difference from the Basic configuration**, which uses **Set dynamic**. The strong configuration must use **Set static** because the Value is a literal token string, not an expression for Cloudflare to evaluate. If you leave the dropdown at **Set dynamic**, Cloudflare will refuse to save the rule and return a "Filter parsing error" pointing at your token (e.g. `'4253cc65...' is not a valid value for expression ... unknown identifier`). This is a deliberate guard rail — you'll see the problem immediately rather than after verified bots start getting gated.
6. Set the header:
   - **Header name**: `verified-bot`
   - **Value**: paste the hex string you generated in step 2.
7. Set **Place at**: First.
8. Click **Save**.

The two values must match exactly. If you rotate the token in the plugin, you must also update the Cloudflare rule's Value field; otherwise verified bots will be redirected to the age gate.

For a walkthrough with screenshots, see [Setting the Cloudflare Bot Header](https://support.xyzinc.com/blog/17-setting-the-cloudflare-bot-header).

### Step 7: Verify the Headers

After DNS propagation completes, verify the headers are working:

```
curl -sI https://yoursite.com | grep -i cf-
```

You should see headers like:
```
cf-cache-status: DYNAMIC
cf-ray: abc123-ORD
```

To verify geo headers specifically, check from within WordPress. The plugin's setup checklist will show whether it can detect Cloudflare headers.

## Restricting Origin Access to Cloudflare (Recommended)

Cloudflare can only enforce age verification (and any other security policy) on requests that actually go through it. If an attacker discovers your origin server's IP address, they can connect to it directly and replay any request as if it had come through Cloudflare — including the bot-bypass header and any region-spoofing payload they choose. The standard technique uses `curl --resolve` to send a TCP connection to the origin IP while presenting the correct SNI, Host header, and TLS hostname to the server, so the request looks indistinguishable from a legitimate one except for its source IP. Most web servers happily serve their configured vhost when this happens.

Origin IPs are easier to find than operators usually expect. Common leakage vectors include historical DNS records archived by services like SecurityTrails and DNSlytics; unproxied subdomains (`mail.`, `cpanel.`, `direct.`, `dev.`) revealed by certificate transparency logs; SMTP `Received:` headers in outbound email from the WordPress server; SPF/MX records; and internet-wide scans by Censys and Shodan that index every public HTTPS certificate's SAN list. A site that has been on Cloudflare for years with disciplined subdomain hygiene is harder to find; a site freshly migrated is often trivial.

The most accessible defense is to configure your origin (or its upstream firewall) to only accept HTTP/HTTPS connections from Cloudflare's published IP ranges. The list is at [cloudflare.com/ips](https://www.cloudflare.com/ips/) and changes occasionally — set a calendar reminder to review it quarterly, or use a managed integration if your provider offers one.

How you implement the allow-list depends on where your site is hosted:

- **Shared hosting (cPanel, Plesk, etc.):** look for an "IP Deny Manager" or "IP Access" tool. Some shared hosts will not let you restrict by IP because the server IP is shared with other customers. If yours doesn't, fall back to the `.htaccess` option below.
- **VPS or dedicated server (AWS, DigitalOcean, Linode, Hetzner, etc.):** use your cloud provider's firewall (AWS Security Groups, DigitalOcean Cloud Firewalls, `ufw` on Ubuntu) to allow ports 80 and 443 only from Cloudflare's IP ranges. Block everything else. This is the cleanest implementation because it stops connections before they reach the web server.
- **`.htaccess` (Apache, any host):** add Cloudflare's ranges as `Require ip` directives. Enforced inside Apache, so it's a touch less robust than a network-layer firewall but easy to deploy on shared hosting.
- **nginx:** use `allow` and `deny` directives in your server block.

If you cannot restrict origin access at all (some shared hosts make this impossible), the strong configuration of the Verified Bot Header Rule above is the next-best defense — even a direct-to-origin attacker cannot use the documented bot-bypass path without knowing the per-site token, which is stored only in your WordPress database and your Cloudflare account.

### Stronger alternative: Authenticated Origin Pulls (advanced)

For operators comfortable managing TLS certificates on their server, Cloudflare's **Authenticated Origin Pulls** is a stronger defense than IP allow-listing. Instead of trusting the source IP of incoming connections, your origin requires every TLS handshake to present a client certificate issued by Cloudflare. Forging this is not practical, so attackers cannot reach the origin even if they know the IP and even if Cloudflare's IP ranges change.

Why this is more involved than IP allow-listing:

- You need shell access to the origin and the ability to edit your web server's TLS configuration.
- You must download Cloudflare's Authenticated Origin Pulls CA certificate, install it on the origin, and add directives that require client certificate verification on every TLS handshake (`ssl_client_certificate` + `ssl_verify_client on` in nginx; `SSLCACertificateFile` + `SSLVerifyClient require` in Apache).
- A misconfiguration produces TLS handshake errors that look like generic outages, which can be hard to diagnose if you're new to client-cert authentication.

The Cloudflare documentation has step-by-step instructions and the default CA download — search Cloudflare's docs for "Authenticated Origin Pulls." This is available on the Cloudflare Free plan when using their default (shared) CA. Per-hostname certificates are a paid feature but not required for the protection benefit.

Authenticated Origin Pulls and IP allow-listing are complementary, not alternatives. The strongest configuration is both — IP allow-listing stops obvious noise at the network layer, and Authenticated Origin Pulls protects against future IP-list changes and any IP-discovery technique an attacker comes up with. But either one alone is a meaningful improvement over an exposed origin, and IP allow-listing is the realistic starting point for most operators.

## Common Issues

### "DNS only" Instead of "Proxied"

If your DNS record shows a gray cloud, Cloudflare is only providing DNS resolution — not proxying traffic. The geo headers will not be present. Toggle the record to "Proxied" (orange cloud).

### Cloudflare Behind a CDN or Load Balancer

If you have another CDN or proxy in front of Cloudflare, the geo headers may be stripped. Cloudflare should be the outermost proxy for the headers to reach your WordPress server.

### Subdomain Not Proxied

Each subdomain needs its own DNS record with proxy enabled. If your WordPress site is on a subdomain (e.g., `blog.yoursite.com`), make sure that specific subdomain has a proxied A or CNAME record.

### IP Geolocation Accuracy

Cloudflare determines location based on the IP address of the visitor. In rare cases (VPNs, corporate proxies), the detected location may not match the visitor's actual location. This is a known limitation of IP-based geolocation and applies to all geo-detection systems.

## WordPress-Specific Cloudflare Settings

### Page Rules (Optional)

If you want to exclude specific paths from Cloudflare's cache but still keep proxying active, use Page Rules:

- `yoursite.com/wp-admin/*` — Cache Level: Bypass
- `yoursite.com/age-gate/*` — Cache Level: Bypass

### Cloudflare Plugin for WordPress

The [Cloudflare WordPress plugin](https://wordpress.org/plugins/cloudflare/) is optional but can help with:

- Automatic cache purging when you update content
- Restoring the real visitor IP address in WordPress logs
- One-click optimizations

It is not required for the age verification plugin to work.

## Next Steps

Once Cloudflare is active and proxying your site, return to the [Installation Guide](01-installation-guide.md) to complete the plugin setup.
