Cloudflare in front, 2026-10-05 to 2026-10-19 (15 days of production)
You put a reverse proxy in front. Here is what it let through.
requests the CDN passed and Guard Core blocked in 15 days
requests the CDN blocked on the same services
On the services that run Guard Core, the guard blocked 2.7x what the CDN blocked. 73% of all blocked requests on those services got past the CDN and were stopped in the app.
We keep hearing the same answer
When we propose application-layer protection, maintainers answer with the same advice. We take it seriously, so we ran it: a CDN and a hardened web server in front, for 15 days, with real traffic. These are their words, with names and projects removed.
“A purpose built security reverse proxy should be run in front of the app.”
“There is no built in brute-force protection ... [the app] is typically behind a proxy like nginx or Caddy”
“rate limiting and IP access rules can be configured in a reverse proxy”
“Our security model delegates authentication and ingress policy to a reverse proxy”
The edge passed it. The guard stopped it.
blocked by Guard Core after the CDN let them through
blocked by the CDN on the same services
Of those 22,293 requests, 21,110 were refused with a 403 (most of them banned clients), 1,173 were detected attacks and 10 hit rate limits. The edge could not make those calls: it does not know which routes your app serves, its rules judge each request on its own without the app's record of what that client already tried, and its block ends at that one request instead of banning the client across every endpoint of the app.
The busiest banned client sent 5,359 requests in 15 days. The CDN forwarded every one.
Where there is no guard, nothing stopped it
requests for .env, .git and cloud credential files reached services in the same deployment that do not run Guard Core, through the CDN
They got a 404 only because those files were not there. One leaked .env is a full compromise, and nothing at the edge stopped the attempts.
A CDN protects your domain name, not your server's IP.
requests reached our server in 15 days without ever touching the CDN
Any server with a public IP receives direct traffic: scanners do not need your domain name. Whatever is listening on that address is exposed to it, however good the edge in front of the domain is.
Why fail2ban cannot help behind a CDN
fail2ban bans the client IP in the firewall. Behind a CDN, every request to your domain reaches the server from a CDN address, so a firewall rule on the client IP never matches. Only the app, which resolves the real client from trusted proxies, can ban it.
IPs banned by the SSH jail in 30 days, where it sees the real peer
IPs banned by the web jails in 30 days, bans that never match the requests they were meant to stop
bans by the bot-search jail: it reads the web server's error log, and the probes' 404s come from the apps, so it never sees them
No noise: we audited every detection
We replayed all 1,149 detections through the production versions. None was on a route the apps serve to their users. 52 are debatable by design: paths a crawler or browser can request on its own, flagged because the app does not serve them. 38 were triggered by a header or the body, which the web server does not log, so their exact trigger cannot be confirmed from the logs; all of them were on paths the apps do not serve.
detections audited
on a route the apps serve
debatable by design
One client per request looks harmless. A campaign does not.
A distributed attack spreads its requests over many addresses so that no single address looks suspicious: each one stays under every per-IP limit at the edge. In the demo below, no address sent more than 4 requests. Guard Core SaaS looks at the clients together. Campaigns score how alike their traffic is across six signals: shared request sequence, timing cadence, IP spread, endpoint spread, automation share and user-agent concentration. Coordinated Attacks flags many addresses sending the same attack to the same endpoint within minutes. Blocking a campaign from the dashboard adds every member IP to the project's IP blacklist. Both are Guard Core SaaS features on the Team, Business and Enterprise tiers; see pricing.
Here is a demo run on our production stack with documentation IP addresses: 40 identities, 4 requests each to the same 4 endpoints in a fixed order, 250 ms apart, with 6 rotating user agents.
requests sent
requests blocked
answered 403 because the client was already banned; 40 answered 400 when detected
The ban comes from the demo app's own rule, not from a default: the demo app bans after two detections. Rate limits never fired.
Outlook, not our data
Our logs do not show AI-driven attacks. These published reports describe where attacks are heading.
“the threat actor was able to use AI to perform 80-90% of the campaign, with human intervention required only sporadically”
Anthropic, 13 November 2025, Disrupting the first reported AI-orchestrated cyber espionage campaign
“The time between disclosure and exploitation has shrunk to days and AI will almost certainly reduce this further.”
UK National Cyber Security Centre, 7 May 2025, Impact of AI on cyber threat from now to 2027
Do not take our word for it. Run it on your logs.
These commands read your web server logs locally and print counts only: how many requests for secret files, admin panels and path traversal got each status code. If the edge stopped them, this prints nothing. They only read: run them on the server, and change the log path if yours live elsewhere.
zcat -f /var/log/nginx/access.log* | awk 'tolower($7) ~ /\.env|\.git\/|\.aws\/|\.ssh\/|wp-login\.php|xmlrpc\.php|phpmyadmin|\.\.\/|%2e%2e/ {print $9}' | sort | uniq -c | sort -rnzcat -f /var/log/caddy/access*.log* | jq -r 'select(.request.uri | ascii_downcase | test("\\.env|\\.git/|\\.aws/|\\.ssh/|wp-login\\.php|xmlrpc\\.php|phpmyadmin|\\.\\./|%2e%2e")) | .status' | sort | uniq -c | sort -rnKeep the front door. Lock the back door too.
A reverse proxy and a CDN lock the front door, and you should keep them: they are the right place for volume, TLS and DDoS protection. Guard Core locks the back door from inside the app, where the routes and the history of each client are known.
How we measured, and what it cannot tell you
- Window: 2026-10-05 to 2026-10-19 (15 days); fail2ban figures cover 30 days.
- Sources: CDN analytics, web server access logs, fail2ban logs and container logs.
- Privacy: client IPs are hashed with a random per-run salt that is never printed, and every output is a count. The page shows aggregates only.
- Limits: 38 detections were triggered by a header or the body, so the logs cannot confirm their exact trigger. This is one deployment over 15 days.