Last active
August 2, 2026 17:21
-
-
Save irazasyed/553a9f579e200690f2c295da43a08002 to your computer and use it in GitHub Desktop.
Restrict the origin's web ports to Cloudflare edge IPs.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| [Unit] | |
| Description=Restrict origin web ports (80/443) to Cloudflare edge IPs | |
| Documentation=file:/usr/local/sbin/cf-origin-lock.sh | |
| # The script fetches Cloudflare's published ranges, so it needs real | |
| # connectivity rather than just a configured interface. It falls back to the | |
| # cached list in /var/lib/cf-origin-lock if the fetch fails. | |
| After=network-online.target | |
| Wants=network-online.target | |
| [Service] | |
| Type=oneshot | |
| ExecStart=/usr/local/sbin/cf-origin-lock.sh | |
| RemainAfterExit=yes | |
| StandardOutput=journal | |
| StandardError=journal | |
| [Install] | |
| WantedBy=multi-user.target |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| # | |
| # Restrict the origin's web ports to Cloudflare edge IPs. | |
| # | |
| # The origin answers on its bare IP, so anyone who knows it can reach the site | |
| # without passing through Cloudflare at all -- WAF, rate limiting and custom | |
| # rules included. That exposure is also what makes useIpInProxyHeader unsafe on | |
| # its own: trusting X-Forwarded-For while the origin is openly reachable lets | |
| # anyone forge a client IP, including one of QUIC.cloud's. | |
| # | |
| # Only 80 and 443 are touched. SSH (22) and CyberPanel (8090) are deliberately | |
| # left alone so a mistake here cannot lock anyone out of the box. Loopback is | |
| # accepted first so the wp-cron job on 127.0.0.1 keeps working, and | |
| # ESTABLISHED/RELATED is accepted so in-flight connections survive re-runs. | |
| # | |
| # Run at boot and weekly by cf-origin-lock.timer: Cloudflare adds ranges from | |
| # time to time, and a stale list would silently drop edge traffic. | |
| set -euo pipefail | |
| CACHE_DIR=/var/lib/cf-origin-lock | |
| mkdir -p "$CACHE_DIR" | |
| # Fetch the current ranges, falling back to the last known good copy. | |
| # | |
| # A failed fetch must never produce a partial ruleset: the chain ends in DROP, | |
| # so an empty list would blackhole every web request. Preferring a stale cache | |
| # over an empty fetch keeps the failure mode "slightly out of date" rather than | |
| # "site offline". | |
| fetch_list() { | |
| local url="$1" cache="$2" min="$3" fresh | |
| fresh=$(curl -fsS --max-time 20 "$url" 2>/dev/null || true) | |
| if [ "$(printf '%s\n' "$fresh" | grep -c .)" -ge "$min" ]; then | |
| printf '%s\n' "$fresh" > "$cache" | |
| printf '%s\n' "$fresh" | |
| return 0 | |
| fi | |
| if [ -s "$cache" ] && [ "$(grep -c . "$cache")" -ge "$min" ]; then | |
| echo "WARN: fetch of $url failed or was short; using cached list" >&2 | |
| cat "$cache" | |
| return 0 | |
| fi | |
| echo "ERROR: no usable list for $url and no cache; refusing to apply" >&2 | |
| return 1 | |
| } | |
| CF4=$(fetch_list https://www.cloudflare.com/ips-v4 "$CACHE_DIR/ips-v4.txt" 10) | |
| CF6=$(fetch_list https://www.cloudflare.com/ips-v6 "$CACHE_DIR/ips-v6.txt" 3) | |
| apply() { | |
| local cmd="$1" list="$2" | |
| # Rebuild the allowlist chain from scratch so removed ranges actually go. | |
| $cmd -N CFWEB 2>/dev/null || $cmd -F CFWEB | |
| local c | |
| for c in $list; do | |
| $cmd -A CFWEB -s "$c" -j ACCEPT | |
| done | |
| $cmd -A CFWEB -j DROP | |
| # -C before adding keeps this idempotent across weekly re-runs. | |
| $cmd -C INPUT -i lo -j ACCEPT 2>/dev/null \ | |
| || $cmd -I INPUT 1 -i lo -j ACCEPT | |
| $cmd -C INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT 2>/dev/null \ | |
| || $cmd -I INPUT 2 -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT | |
| $cmd -C INPUT -p tcp -m multiport --dports 80,443 -m conntrack --ctstate NEW -j CFWEB 2>/dev/null \ | |
| || $cmd -A INPUT -p tcp -m multiport --dports 80,443 -m conntrack --ctstate NEW -j CFWEB | |
| } | |
| apply iptables "$CF4" | |
| apply ip6tables "$CF6" | |
| echo "cf-origin-lock applied: $(printf '%s\n' "$CF4" | grep -c .) v4 + $(printf '%s\n' "$CF6" | grep -c .) v6 ranges" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| [Unit] | |
| Description=Weekly refresh of the Cloudflare origin-lock ranges | |
| [Timer] | |
| # Cloudflare adds and retires ranges occasionally. A stale allowlist silently | |
| # drops edge traffic from any new range, which would look like a partial | |
| # outage, so the list is rebuilt weekly rather than left as a one-off. | |
| OnCalendar=weekly | |
| # Catch up after downtime instead of waiting a full week from the next boot. | |
| Persistent=true | |
| RandomizedDelaySec=1h | |
| [Install] | |
| WantedBy=timers.target |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env bash | |
| # | |
| # Undo cf-origin-lock.sh: stop filtering 80/443 by source and drop the chain. | |
| # | |
| # Leaves the loopback and ESTABLISHED/RELATED accepts in place. They are | |
| # harmless on their own, and removing them from a live box is the one part of | |
| # this that could interrupt an in-flight connection. | |
| set -uo pipefail | |
| for cmd in iptables ip6tables; do | |
| $cmd -D INPUT -p tcp -m multiport --dports 80,443 -m conntrack --ctstate NEW -j CFWEB 2>/dev/null || true | |
| $cmd -F CFWEB 2>/dev/null || true | |
| $cmd -X CFWEB 2>/dev/null || true | |
| done | |
| echo "cf-origin-lock reverted: 80/443 open to all sources again" |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment