networking-cybersecurity • 7 min read (1329 words)

Zero-Trust Origin Server Hardening: Cloudflare WAF, mTLS Authenticated Origin Pulls & Linux nftables

Defense-in-depth guide to preventing direct-to-origin DDoS and WAF bypass attacks using Linux kernel nftables IP sets, mutual TLS (mTLS), and SSH Certificate Authorities.

Multi-layered Zero-Trust origin security architecture with nftables and mTLS
Methodology: Security configuration playbook based on official Cloudflare Authenticated Origin Pulls documentation, Netfilter nftables reference manuals, and OpenSSH certificate authority specifications.

1. Anatomy of an Origin IP Bypass Attack

Deploying a reverse-proxy Content Delivery Network (CDN) and Web Application Firewall (WAF) such as Cloudflare shields your web application from volumetric DDoS floods and OWASP Top-10 exploits—but only when traffic actually traverses the proxy edge.

In practice, automated scanners continuously map IPv4 address space (0.0.0.0/0) on TCP port 443, inspecting TLS Server Name Indication (SNI) certificates and HTTP Host headers, or querying historical DNS databases (SecurityTrails, Censys, Shodan) and SPF/mail records to uncover the real public IP address of your origin server.

If your origin server's firewall accepts inbound TCP connections on ports 80 and 443 from any source IP (0.0.0.0/0), an attacker who discovers your origin IP can:
1. Send volumetric HTTP floods directly to your web server, exhausting worker processes and database connections without ever hitting your CDN rate limiter.
2. Send SQL injection or credential-stuffing payloads directly to the origin, bypassing all WAF rules.

Securing an origin server requires three concentric layers of defense: kernel-level IP filtering (nftables), cryptographic mutual TLS (Authenticated Origin Pulls), and hardened administrative access (SSH Certificates / private tunnels).

2. Layer 1: Kernel-Level Edge IP Allowlisting with nftables

The first defense layer drops all inbound packets to TCP/UDP ports 80 and 443 inside the Linux kernel unless the source IP address belongs to Cloudflare's official published IPv4 and IPv6 ranges. Using nftables interval sets (flags interval) allows the kernel to evaluate CIDR prefixes in $O(\log N)$ time with negligible CPU overhead:

#!/usr/sbin/nft -f
# /etc/nftables.conf — Zero-Trust Origin Firewall Policy

flush ruleset

table inet filter {
set cf_ipv4 {
type ipv4_addr
flags interval
elements = {
173.245.48.0/20, 103.21.244.0/22, 103.22.200.0/22, 103.31.4.0/22,
141.101.64.0/18, 108.162.192.0/18, 190.93.240.0/20, 188.114.96.0/20,
197.234.240.0/22, 198.41.128.0/17, 162.158.0.0/15, 104.16.0.0/13,
104.24.0.0/14, 172.64.0.0/13, 131.0.72.0/22
}
}

set cf_ipv6 {
type ipv6_addr
flags interval
elements = {
2400:cb00::/32, 2606:4700::/32, 2803:f800::/32, 2405:b500::/32,
2405:8100::/32, 2a06:98c0::/29, 2c0f:f248::/32
}
}

chain input {
type filter hook input priority 0; policy drop;

# Allow established/related connections and loopback interface
ct state established,related accept
ct state invalid drop
iif "lo" accept

# Allow essential ICMP / ICMPv6 (Path MTU Discovery & Neighbor Discovery)
ip protocol icmp icmp type { echo-request, destination-unreachable, time-exceeded } limit rate 10/second accept
ip6 nexthdr icmpv6 icmpv6 type { echo-request, destination-unreachable, packet-too-big, time-exceeded, nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert } accept

# Allow SSH only from your Bastion/VPN CIDR (Replace 203.0.113.10/32 with your management IP)
tcp dport 22 ip saddr 203.0.113.10/32 ct state new limit rate 15/minute accept

# Allow HTTP/HTTPS (TCP 80/443) and HTTP/3 (UDP 443) ONLY from Cloudflare Edge IPs
tcp dport { 80, 443 } ip saddr @cf_ipv4 accept
tcp dport { 80, 443 } ip6 saddr @cf_ipv6 accept
udp dport 443 ip saddr @cf_ipv4 accept
udp dport 443 ip6 saddr @cf_ipv6 accept
}

chain forward {
type filter hook forward priority 0; policy drop;
}

chain output {
type filter hook output priority 0; policy accept;
}
}

Enable and persist the ruleset on Debian/Ubuntu with sudo systemctl enable --now nftables.

3. Layer 2: Cryptographic Verification via Mutual TLS (Authenticated Origin Pulls)

Why is IP allowlisting alone insufficient for strict Zero-Trust security? Because on any multi-tenant CDN, another user could theoretically create a zone on the same CDN and point an A record to your origin IP address—causing their requests to arrive from legitimate CDN edge IPs!

To prevent cross-tenant origin spoofing:
1. Enable Full (Strict) SSL/TLS encryption in your CDN dashboard and install an Origin CA certificate bound strictly to your domain (*.example.com).
2. Enable Per-Zone or Per-Hostname Authenticated Origin Pulls (mTLS) using your own uploaded client CA certificate, and configure Nginx (or Caddy/HAProxy) to verify the client certificate on every TLS handshake:

server {
    listen 443 ssl;
    http2 on;
    server_name example.com www.example.com;

# Origin Server Certificate (Issued by Origin CA or Let's Encrypt DNS-01)
ssl_certificate /etc/ssl/origin/example.com.pem;
ssl_certificate_key /etc/ssl/origin/example.com.key;
ssl_protocols TLSv1.2 TLSv1.3;

# Require Mutual TLS (mTLS) Client Certificate from Authorized Proxy Edge
ssl_client_certificate /etc/ssl/origin/authenticated-origin-pull-ca.pem;
ssl_verify_client on;

# Restore Real Visitor IP Only After Verifying Source
real_ip_header CF-Connecting-IP;

location / {
proxy_pass http://127.0.0.1:4321;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}

# Reject default/IP-based SNI scanners during the TLS handshake (Nginx 1.19.4+)
server {
listen 443 ssl default_server;
ssl_reject_handshake on;
}

Notice the default_server block with ssl_reject_handshake on;: when an automated scanner probes https://<YOUR_ORIGIN_IP> without sending your exact server_name in the TLS SNI extension, Nginx aborts the TLS handshake immediately without revealing your domain certificate!

4. Layer 3: Eliminating Public Origin Exposure Entirely with Outbound Tunnels

If your application does not require raw UDP ingress or custom non-HTTP TCP ports, you can eliminate inbound firewall openings (80/443) altogether:
- Bind your web server strictly to 127.0.0.1:4321 (loopback).
- Run a lightweight outbound-only encrypted daemon (cloudflared tunnel or WireGuard mesh) that establishes persistent outbound connections from your server to the nearest edge POPs.
- Set your nftables input chain policy to drop all inbound public traffic except your private WireGuard/SSH management interface.

5. Automating Weekly Cloudflare IP Set Updates & Monitoring Dropped Packets

Because Cloudflare occasionally adds new Anycast edge subnets to cloudflare.com/ips-v4 and cloudflare.com/ips-v6, automate safe atomic reloading of your nftables IP sets via a weekly systemd timer that validates syntax (nft -c) before applying:

#!/usr/bin/env bash
# /usr/local/sbin/update-cloudflare-nftables.sh
set -euo pipefail

IPV4_LIST=$(curl -fsSL https://www.cloudflare.com/ips-v4 | paste -sd, -)
IPV6_LIST=$(curl -fsSL https://www.cloudflare.com/ips-v6 | paste -sd, -)

if [[ -z "$IPV4_LIST" || -z "$IPV6_LIST" ]]; then
echo "Error: Empty IP list fetched; aborting nftables update." >&2
exit 1
fi

nft -f - <<EOF
flush set inet filter cf_ipv4
add element inet filter cf_ipv4 { $IPV4_LIST }
flush set inet filter cf_ipv6
add element inet filter cf_ipv6 { $IPV6_LIST }
EOF
echo "Successfully refreshed Cloudflare IPv4/IPv6 sets in nftables."

6. End-to-End Origin Security Verification Protocol

After applying all three layers (nftables edge IP allowlisting + Authenticated Origin Pulls mTLS + strict TLS 1.3), run the following external verification checks from a machine outside your trusted network to confirm your origin IP is invisible to direct scanners:

| Verification Test Command | Expected Result on Hardened Origin | What It Proves |
| :--- | :--- | :--- |
| curl -vk --connect-to ::ORIGIN_IP https://yourdomain.com --max-time 5 | Connection Timed Out (TCP SYN dropped) | Layer 1 (nftables) drops direct non-Cloudflare TCP packets silently in kernel space. |
| Request via custom attacker Cloudflare zone pointing to ORIGIN_IP | HTTP 400 Bad Request (No required SSL certificate) | Layer 2 (Authenticated Origin Pulls mTLS) blocks attackers trying to proxy through their own Cloudflare account. |
| ss -tulpn on the origin Linux server | Only 22 (VPN/SSH) and 443 (or 127.0.0.1 for Tunnel) listening | Zero database (5432, 6379) or internal management ports exposed on 0.0.0.0. |

Official References