Caddy and allowing traffic only from Cloudflare tutorial

Here is a short recipe for wiring your Caddy so that all HTTP/HTTPS traffic outside Cloudflare is blocked.

  • The main use case is to ensure that no one performs Denial of Service attack by going around Cloudflare, or any other security reverse proxy provider you might be using.
  • Similar functionality can be achieved with real_ip and other plugins. This recipe focuses on simplicity, as it works without plugin installations.
  • Cloudflare IP ranges are not static: however they barely ever change, so updating them can be a manual job.
  • Note that someone can still use Cloudflare Workers as a third party and make requests within Cloudflare IP range; however using this to large scale denial of service is very difficult.

First we create a shell script that prints out us a Cloudflare IPv4 / IPv6 line for Caddyfile

cloudflare-ip-list.sh:

#!/bin/sh
#
# Prints out the latest Cloudflare IP list.
#
# Note: Third party Cloudflare workers can still access your site even if you 
# block traffic from this range.
#

for i in `curl -s https://www.cloudflare.com/ips-v4`; do echo -n "$i "; done
for i in `curl -s https://www.cloudflare.com/ips-v6`; do echo -n "$i "; done
echo

Then we prepare Caddyfile that uses the given IP range list and snippets. The example if for setting up multiple subdomains that repeat the rule:

#
# Caddy configuration example that blocks non-Cloudflare traffic
#
{
    admin off
    email no-reply@example.com

    log {
        output file /var/log/caddy/access.log
        format json
    }
}

#
# Caddy snippet to be used with subdomains to block all traffic
# that is not originated from Cloudflare.
#
# This is designed to block harmful knocking and IP scanning bot traffic.
#
# # Generate remote_ip list with cloudflare-ip-list.sh
#
# More about snippet features here
# - https://caddyserver.com/docs/caddyfile/concepts#snippets
# - https://caddy.community/t/caddy-v2-reusable-snippets/6744
#
(cloudflare-only) {
  # Update IPs here from the shell script output
  @blocked not remote_ip 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 2400:cb00::/32 2606:4700::/32 2803:f800::/32 2405:b500::/32 2405:8100::/32 2a06:98c0::/29 2c0f:f248::/32 
  respond @blocked "<h1>HTTP/HTTPS access through Cloudflare only</h1>" 403
}


# Specify a subdomain that can be only accessed through
# CloudFlare reverse proxy.
#
# To manually test using direct IP to see you are blocked:
#
#    curl --header "Host: mysite.example.com" http://1.2.3.4
#
http://mysite.example.com {    
    handle {
        import cloudflare-only

        # Your website handling logic goes here
        reverse_proxy 127.0.0.1:8080
    }
}

:80 {
    respond "<h1>HTTP request did not specify site domain</h1>" 403
}

:443 {
    respond "<h1>HTTP request did not specify site domain</h1>" 403
}

Then you can test that direct IP access is blocked with curl:

curl --header "Host: mysite.example.com" http://1.2.3.4
<h1>HTTP/HTTPS access through Cloudflare only</h1>
2 Likes