Origin hardening: what must be in place
Every Cloudflare feature you disable was doing something your origin wasn't. Now the origin has to carry its own weight. This checklist maps each Cloudflare feature to the origin configuration that replaces it — with links to the guides that walk through each one in detail.
Before you flip any Cloudflare switches, work through this list. If you disable a Cloudflare feature and don't know which origin setting replaces it, stop. Find the setting. Configure it. Test it. Then proceed.
1. HTTPS enforcement
What Cloudflare would otherwise do: Always Use HTTPS, Automatic HTTPS Rewrites.
Let's Encrypt via certbot. No self-signed certs on production hostnames. Cloudflare in Full (Strict) mode validates the origin cert — if renewal fails, the site goes down with a 526 error. Covered in the Let's Encrypt guide.
A catch-all
server block on port 80 that 301-redirects to https://$host$request_uri. No plain-HTTP content ever reaches an application. Included in both the WordPress and Bludit installation guides.
http:// URLs in contentAutomatic HTTPS Rewrites masked these; now they'll appear as mixed content warnings. Run
wp search-replace 'http://example.com' 'https://example.com' --dry-run to check, then without the flag to fix. More in the WP-CLI guide.
WordPress:
WP_HOME and WP_SITEURL in wp-config.php or the database. Bludit: site URL in the admin panel. If your CMS thinks it lives at http://, every generated link will be wrong.
2. HSTS (HTTP Strict Transport Security)
What Cloudflare would otherwise do: Add its own HSTS header.
The origin must send its own Strict-Transport-Security header on every HTTPS response. If you use Let's Encrypt's Certbot, the options-ssl-nginx.conf snippet typically includes HSTS — verify it's present and not duplicated. One authority, one header.
There's a subtle nginx gotcha here: add_header in a parent block is replaced — not merged — by any add_header in a child location block. If you have a location block that sets its own headers for caching or CORS, repeat the HSTS line there. Otherwise those responses go out without it. The Securing Nginx and PHP guide covers this in the header hardening section.
# Verify HSTS is live on every domain
for domain in $(ls /etc/nginx/sites-enabled/); do
curl -sI --resolve "$domain:443:127.0.0.1" "https://$domain" 2>/dev/null \
| grep -i strict-transport || echo "$domain: MISSING"
done
Start with a short max-age (86400 — one day) while you verify everything works. Once you're confident every domain serves the header correctly, bump it to 63072000 (two years). Browsers that receive a long max-age will refuse to connect over plain HTTP for the full duration — there's no undo button.
3. TLS protocol floor
What Cloudflare would otherwise do: Terminate TLS and enforce a minimum protocol version.
Verify your nginx SSL configuration includes ssl_protocols TLSv1.2 TLSv1.3; — TLS 1.0 and 1.1 must be disabled. If you used Certbot, this is already set in options-ssl-nginx.conf. If you configured SSL manually, check it.
Certificate auto-renewal is critical. Cloudflare in Full (Strict) mode validates your origin cert on every connection. If renewal fails silently and the cert expires, Cloudflare returns a 526 error to every visitor. Certbot installs a systemd timer by default, but it's worth verifying:
# Check cert expiry across all domains
for dir in /etc/letsencrypt/live/*/; do
domain=$(basename "$dir")
echo -n "$domain: "
openssl x509 -in "$dir/fullchain.pem" -noout -enddate 2>/dev/null | cut -d= -f2
done
4. Bot mitigation and security headers
What Cloudflare would otherwise do: Browser Integrity Check, some WAF protections, bot scoring.
Cloudflare's Browser Integrity Check is a blunt instrument — it blocks requests based on a reputation database you can't audit or tune. Your origin-level rules are precise, auditable, and work regardless of whether traffic passes through Cloudflare.
limit_req_zone on PHP endpoints, login pages, and search. Prevents brute-force attacks and resource exhaustion. Covered in Preventing Search Overload and the limit_req guide.
Watches access and error logs for suspicious patterns. A catch-all honeypot server block catches bots that connect with bogus hostnames. The full setup — jails, AbuseIPDB reporting, crowd-sourced blacklists — is in the fail2ban guide.
Deny access to
.env, .git, wp-config.php, xmlrpc.php, hidden files, backup files, and known vulnerability probes. The drop.conf and wp_security.conf snippets in the WordPress guide, the bludit_security.conf rules in the Bludit installation guide, and Securing Nginx and PHP cover the common patterns.
server_tokens off; in nginx. expose_php = Off in php.ini. Bots can't exploit version-specific vulnerabilities if they can't determine what versions you're running.
5. PHP and application security
What Cloudflare would otherwise do: Nothing directly. But a transparent proxy won't block a compromised plugin's outbound connections, won't stop a session fixation attack, and won't prevent code execution through a file upload vulnerability. That's all on you.
session.cookie_secure = 1, session.cookie_httponly = 1, session.cookie_samesite = "Lax", session.use_strict_mode = 1. These prevent session theft over HTTP, JavaScript access to session cookies, and cross-site request forgery.
disable_functions = exec, passthru, shell_exec, system, proc_open, popen in the FPM pool config. A compromised plugin that can't call exec() is limited in what damage it can do.
open_basedir restrictionLock each site's PHP process to its document root plus
/tmp and /var/www/tmp. Covered in the Securing Nginx and PHP guide — this is the filesystem half of multi-site isolation.
define('DISABLE_WP_CRON', true); in wp-config.php. Run cron tasks via system crontab instead — prevents a PHP process from spawning on every page load. Covered in the WordPress guide.
6. Real IP restoration
Without real IP restoration, every request appears to come from a Cloudflare IP. Rate limiting, fail2ban, and access logs all become useless — you can't ban an IP if all you see is Cloudflare. The full setup — set_real_ip_from ranges, CF-Connecting-IP header, auto-update cron job, and the critical $binary_remote_addr vs $realip_remote_addr distinction for rate-limiting keys — is in the Cloudflare Real IP guide.
7. Performance baseline
What Cloudflare would otherwise do: Rocket Loader, Auto Minify, Polish, Mirage.
Cloudflare's content optimization features were offloading work from your origin. Without them, the origin must serve well-compressed, well-cached assets. If you weren't doing this before, your origin will feel slower after the switch — not because Cloudflare was making you fast, but because you weren't pulling your own weight.
gzip on; with appropriate gzip_types. Compresses text-based assets at the origin before they reach Cloudflare. Included in the static caching configuration of both CMS installation guides.
expires max; with Cache-Control: public, immutable for versioned assets and images. must-revalidate, proxy-revalidate for CSS/JS. Cloudflare respects these headers — set them correctly at the origin and the edge cache does the right thing automatically.
Match buffer sizes to your workload. Fewer, larger buffers outperform many small ones for typical WordPress page responses. Covered in the WordPress guide and Bludit installation guide, Step 6.
OPCache enabled with file cache for warm restarts.
pm.max_children appropriate for your RAM — idle workers sitting on memory that could be disk cache are a performance drag. Covered in the Securing Nginx and PHP guide.
Pre-flight validation
Before touching any Cloudflare settings, run this from the VPS. All four checks should return clean results:
# 1. All certs valid?
for dir in /etc/letsencrypt/live/*/; do
openssl x509 -in "$dir/fullchain.pem" -noout -enddate 2>/dev/null
done
# 2. HSTS header present on every domain?
for domain in $(ls /etc/nginx/sites-enabled/); do
curl -sI --resolve "$domain:443:127.0.0.1" "https://$domain" 2>/dev/null \
| grep -i strict-transport || echo "$domain: MISSING"
done
# 3. HTTP → HTTPS redirect working?
curl -sI --resolve "example.com:80:127.0.0.1" "http://example.com" \
| grep -i location
# 4. PHP session settings live?
php-fpm8.5 -i | grep -E 'session\.(use_strict_mode|cookie_httponly|cookie_samesite|cookie_secure)'
If anything returns red, fix it at the origin first. Cloudflare can't compensate for a broken origin — in Full (Strict) mode, it won't even try.