Originally published on kuryzhev.cloud
A team turns on managed WAF rules and bot challenges, watches the attack traffic drop in the dashboard, and then finds the same junk still hitting the application logs. The cause is usually mundane: the origin server still answers on a public IP, so anyone who learns the address can skip the edge. Cloudflare origin hardening exists to close that gap, and there are two main ways to do it: restrict by network address, or require proof of identity from the connecting client.
Both approaches pair with the same two supporting pieces: Full (strict) SSL between Cloudflare and the origin, and edge rules for bot checks. This article compares the approaches by class so the choice does not depend on a specific plan tier or product revision. Verify plan-specific features in the Cloudflare SSL modes documentation before you commit.
When this choice matters
Edge protections only work on traffic that passes through the edge. A WAF custom rule, a managed challenge, or a rate limit does nothing against a request sent straight to the origin IP. Origin addresses leak in several ways:
- Old DNS records.
- Certificate transparency logs that reveal hostnames, some of which resolve to unproxied IPs.
- Mail headers.
- Forgotten subdomains that are not proxied.
- Internet-wide scan data.
The choice matters most in these situations:
- You rely on Cloudflare bot checks or rate limiting to protect login, checkout, or API endpoints.
- The origin has an expensive code path, such as search or report generation, that a direct flood could exhaust.
- You recently moved a site behind Cloudflare, so the old origin IP may already be known.
- Compliance or customer contracts require that traffic is inspected before it reaches the application.
It matters less for a static site served from object storage, where there is no origin to attack. It also matters less if the origin already sits in a private network with no public listener. Rotating the origin IP after the move is cheap and worth doing, but on its own it is obscurity, not a control.
Option A: network-layer lockdown with Full (strict) SSL
The first class of approach is to make the origin firewall accept port 443 only from Cloudflare's published address ranges. Pair it with Full (strict) mode, which makes Cloudflare validate the origin certificate. The certificate must be unexpired, match the hostname, and be issued by a public CA or by Cloudflare Origin CA. The ranges are listed on the Cloudflare IP addresses page.
The following script rebuilds host firewall rules from the published lists. It uses ufw as an example; adapt it to nftables or a cloud security group. Note that it resets all existing ufw rules, so add back any other services the host needs before running it.
#!/usr/bin/env bash
set -euo pipefail
ADMIN_CIDR="192.0.2.0/24" # placeholder: your management network for SSH
tmp=$(mktemp)
trap 'rm -f "$tmp"' EXIT
# The published files may lack a trailing newline, so add one between them
{ curl -fsSL https://www.cloudflare.com/ips-v4; echo; \
curl -fsSL https://www.cloudflare.com/ips-v6; echo; } > "$tmp"
# Refuse to continue if the list is empty or contains anything but CIDRs
grep -Eq '^[0-9a-fA-F:.]+/[0-9]+$' "$tmp" || { echo "empty IP list"; exit 1; }
if grep -Evq '^([0-9a-fA-F:.]+/[0-9]+)?$' "$tmp"; then
echo "unexpected content in IP list"; exit 1
fi
# reset deletes ALL existing rules (including stale ranges) and disables ufw
ufw --force reset
ufw default deny incoming
ufw default allow outgoing
ufw allow from "$ADMIN_CIDR" to any port 22 proto tcp # keep SSH access
while read -r cidr; do
[ -z "$cidr" ] && continue
ufw allow from "$cidr" to any port 443 proto tcp # HTTPS only from Cloudflare
done < "$tmp"
ufw --force enable # requires IPV6=yes in /etc/default/ufw for v6 rules
Pros
- Works with any web server, load balancer, or cloud firewall, with no TLS changes beyond Full (strict).
- Drops scanner traffic before it reaches the TLS handshake, which keeps logs quiet.
- Easy to audit: the rule set is a list of CIDRs.
Cons
- It proves the packet came from Cloudflare, not from your zone. Any Cloudflare customer can proxy to your origin IP from those same ranges, bypassing your WAF rules.
- The ranges change occasionally, so the list needs scheduled refreshes. Watch out for a stale list after a range is added; it can cause intermittent 521 or 522 errors that look like an application fault.
- Forgetting IPv6 leaves a second door open on dual-stack hosts.
Option B: identity-based origin hardening with Authenticated Origin Pulls
The second class asks Cloudflare to present a client certificate when it connects, and the origin rejects any connection without one. This is mutual TLS, documented as Authenticated Origin Pulls. Because the check is cryptographic, it does not depend on source addresses staying stable.
Here is an nginx server block that enforces it. The CA file comes from the Cloudflare documentation, or from your own CA if you use a custom certificate. Enable Authenticated Origin Pulls in Cloudflare first. If the origin starts requiring a client certificate before Cloudflare sends one, every request fails.
server {
listen 443 ssl;
server_name app.example.com;
ssl_certificate /etc/ssl/origin/app.pem; # Origin CA or public CA cert
ssl_certificate_key /etc/ssl/origin/app.key;
# CA that signs the client cert Cloudflare presents
ssl_client_certificate /etc/ssl/origin/origin-pull-ca.pem;
ssl_verify_client on; # nginx answers 400 if no valid client cert is sent
location / {
proxy_pass http://127.0.0.1:8080;
}
}
Apply the same verification to every server block that listens on 443, including the default_server. A request with an unknown Host or SNI value lands on the default block. If that block skips verification, it bypasses the check.
Pros
- A direct request to the origin is rejected with HTTP 400 before it reaches the application, even if the attacker knows the IP.
- No list of ranges to maintain.
- With a zone-level or per-hostname custom certificate, other Cloudflare tenants cannot route through to you.
Cons
- Requires TLS configuration on every origin, and not every load balancer or managed platform supports client certificate verification. Check yours.
- Watch out for the shared default certificate. The documented default uses a certificate common to all Cloudflare customers, which gives much weaker isolation than a zone-specific one.
- Certificate expiry is a new failure mode. A lapsed client cert on the Cloudflare side or a lapsed CA file at the origin takes the site down.
A related variant is Cloudflare Tunnel, where a connector on the origin makes outbound connections and no inbound port is open. It removes the public listener entirely, at the cost of running and monitoring the connector. See the Tunnel documentation for current setup.
Decision matrix
Bot checks do not change the matrix, since both options protect the path that the edge rules depend on. Those rules live at the edge: Bot Fight Mode or its higher-tier equivalents, plus WAF custom rules that issue a managed challenge on sensitive paths. Which bot features you get depends on plan, so verify the current tiers in the Cloudflare docs.
| Situation | Allowlist + strict | Authenticated pulls + strict | Tunnel |
|---|---|---|---|
| Legacy origin, no TLS control | Fits | Often impossible | Fits if a connector can run |
| Concern: other Cloudflare tenants | Does not address | Addresses (custom cert) | Addresses |
| Operational burden | Range refresh job | Certificate lifecycle | Connector uptime |
| Public inbound port needed | Yes | Yes | No |
| Rollout effort | Lowest | Higher; needs staged testing | Moderate; new component to run |
Use this checklist before changing anything in production:
[ ] Origin cert valid, hostname matches, not expiring within 30 days
[ ] Test Full (strict) on a staging hostname first (a bad cert returns 526)
[ ] Firewall rules cover IPv4 AND IPv6, and SSH/admin access is preserved
[ ] Health checks and monitoring probes allowed or routed via Cloudflare
[ ] Authenticated Origin Pulls enabled in Cloudflare BEFORE origin enforces it
[ ] WAF custom rule (managed challenge) staged: Log action if your plan has it,
otherwise scoped to a test hostname first
[ ] Direct-to-origin request from outside Cloudflare now fails
[ ] Alert on cert expiry and on 52x error rate
Evidence-based recommendation for Cloudflare origin hardening
The documented behavior points to a layered default:
- Full (strict) SSL always.
- A firewall restriction to Cloudflare ranges as the baseline.
- Authenticated Origin Pulls with a zone-specific certificate wherever the origin can verify client certificates.
The allowlist is cheap and blocks opportunistic scanning from non-Cloudflare addresses. Mutual TLS closes the remaining hole that an allowlist cannot: another tenant on the same ranges.
Choose the allowlist alone when the origin is a legacy system you cannot reconfigure, the threat is commodity scanning, and you accept the cross-tenant gap. Choose mutual TLS when you run your own reverse proxy such as nginx and the application is a likely target. Choose Tunnel when you can run a connector and want no inbound exposure at all.
Whichever you pick, confirm it works. From a host outside Cloudflare, request the origin IP directly with the correct Host and SNI, for example curl -v --resolve app.example.com:443:203.0.113.10 https://app.example.com/. With a firewall restriction, expect a timeout or refused connection. With Authenticated Origin Pulls in nginx, expect an HTTP 400 complaining about a missing client certificate.
Stage the bot and WAF rules before enforcing them, because challenges can break API clients and webhooks that cannot solve them. Use the Log action where your plan offers it, or scope rules to a test hostname first. For more infrastructure comparisons, browse kuryzhev.cloud. Exact UI labels and plan limits change, so treat the Cloudflare documentation as the source of truth.
Top comments (0)