Fail2ban 自訂 banaction 結合 Cloudflare 的實作筆記

本文記錄了如何利用 Fail2ban 搭配 Cloudflare API 來有效阻擋惡意 IP。文中詳細說明了 Fail2ban 在使用 Cloudflare CDN 時的常見問題,並提供了取得真實 IP、設定自訂 banaction 的完整步驟與實作細節,讓你避開設定上的各種坑,輕鬆提升伺服器安全性。

Fail2ban 自訂 banaction 結合 Cloudflare 的實作筆記

問題背景

在閱讀本文前,有一些基礎概念需要稍微解釋一下,這將幫助你理解為什麼我們需要使用 Fail2ban 結合 Cloudflare API 的方式來阻擋攻擊者的 IP。

[!tip] 問題所在的核心原因
Fail2ban 是一個常用於保護伺服器安全的工具,能透過監測主機的 log 檔案,自動封鎖具有惡意行為的 IP 位址。但在搭配使用 Cloudflare CDN 服務的情況下,這個方式卻會失效。

[!tip] 為什麼一定要透過 Cloudflare API 阻擋 IP
由於攻擊的請求是透過 Cloudflare CDN 進入你的伺服器,因此在伺服器層級封鎖攻擊者的真實 IP 是無效的。
真正有效的做法,是要直接透過 Cloudflare 提供的 API,將攻擊者的 IP 加入 Cloudflare 自身的防火牆規則中,這樣才能在流量進入 CDN 時就直接被阻擋,徹底防止攻擊流量進入到你的主機。

[!tip] 腳本用途的補充說明
本文中提供的 Bash 腳本會定期從 Cloudflare 官方網站取得最新的 Proxy IP 清單。之所以需要定期執行這個動作,是因為 Cloudflare 的 Proxy IP 並非固定不變,Cloudflare 會不定期增加或調整 Proxy IP 範圍。如果沒有定期更新這份 IP 清單,伺服器上的 log 就可能無法正確識別訪客的真實 IP,導致 Fail2ban 或其他 IP 偵測工具無法有效運作。

定期更新 IP 清單的動作確保 Nginx 可以信任最新的 Cloudflare Proxy IP,並透過 HTTP header (CF-Connecting-IP) 正確記錄訪客真實 IP,為後續的封鎖攻擊 IP 做好準備。

這也是為什麼我們必須透過 Cloudflare Firewall API 去進行真正的 IP 封鎖。

解決方案概述

最近,我的伺服器遭遇了斷斷續續的攻擊,機器人透過腳本不斷嘗試存取一些並不存在的 PHP 檔案(例如:wp-login.php、xmlrpc.php 等),試圖找到潛在的漏洞或導致服務過載,給我造成了很大的困擾。

為了阻擋這些惡意請求,我選擇了常見的伺服器安全工具——Fail2ban。它可以透過分析伺服器的 log 檔案,自動偵測並阻擋有惡意行為的 IP,原本認為這是一個快速且有效的解決方式。

然而,當我搞定 Fail2ban 之後,卻發現還有一些意想不到的坑需要處理。因為我前面還有掛一個 Cloudflare 作為 proxy,所有的請求都先經過 Cloudflare,再轉發到我的主機。如果你單純 Ban 掉那些 IP,那恭喜你,你把全部使用者都給 Ban 了,因為 Fail2ban 封鎖的 IP 實際上並非攻擊者的真實 IP,而是 Cloudflare 的 Proxy IP。

為了解決這個問題,我首先試著解決 log 紀錄訪客真實 IP 的問題,撰寫了一個定期更新 Cloudflare IP 清單的腳本,讓 Nginx log 正確記錄真實 IP。原本以為這樣 Fail2ban 就能順利封鎖惡意 IP,但結果卻發現實際封鎖的仍然是 Cloudflare Proxy 的 IP,導致封鎖失效。

思考過後才想到可以用 Cloudflare 提供的 Firewall API,將惡意 IP 直接封鎖在 Cloudflare 那裡,這樣才可以真正的阻擋這些攻擊 IP。

實作步驟

1. 環境準備

我伺服器作業系統是 Ubuntu 19.04 如果你不是這個作業系統,可能有些地方指令會不太一樣,不過概念上是差不多的,可以斟酌參考。

  • Ubuntu 19.04
  • Nginx

2. 設定 Nginx 取得真實 IP

為了讓 Nginx 內的 log 取得真實的 IP 必須在 nginx.config 上下點功夫,以下是我寫的一個 Bash 腳本,會定期從 Cloudflare 抓取最新的 Proxy IP 清單,並自動更新 Nginx 的設定檔,使 Nginx 能正確識別訪客真實 IP:

#!/bin/bash

CF_IPV4_URL="https://www.cloudflare.com/ips-v4"
CF_IPV6_URL="https://www.cloudflare.com/ips-v6"
OUTPUT_FILE="/etc/nginx/conf.d/real_ip_cloudflare.conf"

# 產生檔案,確保每次都完全覆蓋
{
  echo "# Auto-generated Cloudflare IP allow list"
  echo "# Last updated: $(date)"

  curl -s $CF_IPV4_URL | while read ip; do
    echo "set_real_ip_from $ip;"
  done

  curl -s $CF_IPV6_URL | while read ip; do
    echo "set_real_ip_from $ip;"
  done

  echo "real_ip_header CF-Connecting-IP;"
  echo "real_ip_recursive on;"
} > "$OUTPUT_FILE"

# 測試 nginx 設定,如果正確才 reload
if nginx -t; then
  systemctl reload nginx
else
  echo "Nginx config test failed. Not reloading."
fi

Nginx 如果你沒改動過設定,會有 autoload 機制,所以只要放在這路徑之下就可以正確運作了

主要是這三個設定

  • set_real_ip_from 指定允許 Nginx 信任 Cloudflare 的 Proxy IP。
  • real_ip_header CF-Connecting-IP 指定真正訪客的 IP 是由 Cloudflare 提供的這個 header (CF-Connecting-IP) 所傳遞。
  • real_ip_recursive on 表示當 Nginx 在多層 proxy 時會往前遞迴找真正的訪客 IP。

這個時候可以檢查一下 nginx 的 access.log 確認一下 IP 是否是真實 IP 了
CleanShot 2025-05-29 at 23.47.39.png

3. 配置 Fail2ban

安裝過程我就不講了,網路上教學一大堆,肯定比我寫得詳細
Fail2ban 的運作主要圍繞以下三個核心概念進行設定:

  • action:發現惡意行為後要執行的動作
  • filter:定義要在 log 中搜尋什麼模式
  • jail:把 filter + action 組合起來的設定單元

3.1 設定 Filter

filter.d/nginx-php-attacks.conf

[Definition]
failregex = ^<HOST> - - \[.*\] "(GET|POST).*\.php.*HTTP.*" 404
ignoreregex =

這邊我設定只要是 GET or POST 訪問 .php 檔案,status code 404 我就算一次紀錄

3.2 設定 Jail

jail.d/nginx-php-attacks.local

[nginx-php-attacks]
enabled = true
port    = http,https
filter  = nginx-php-attacks
logpath = /var/log/nginx/access.log
maxretry = 5
findtime = 600
bantime = 86400
action = cloudflare[name=nginx-php-attacks], iptables-multiport[name=nginx-php-attacks, port="http,https"], ip6tables-multiport[name=nginx-php-attacks, port="http,https"]

使用 nginx-php-attacks 這個 filter 去監聽 logpath /var/log/nginx/access.log 只 600 秒內給你 5 次機會,你超過了就讓你坐牢 24 小時

其實只需要 cloudflare[name=nginx-php-attacks] 就可以了,只是我多了 iptables 也一起去 ban IP

3.3 設定 Action

接下來就是比較複雜的 action 設定了,最多坑都在這邊
Fail2ban 官方提供的 Cloudflare action 已經過時 github source 另外一個坑是內建的 cloudflare 的 API 已經棄用,建議用新的 API 執行 action:

action.d/cloudflare.conf

[Definition]
actionban = curl -4 -s -o /dev/null -X POST <_cf_api_prms> \
            -d '{"mode":"block","configuration":{"target":"<cftarget>","value":"<ip>"},"notes":"Fail2Ban <name>"}' \
            <_cf_api_url>

actionunban = id=$(curl -4 -s -X GET <_cf_api_prms> \
               "<_cf_api_url>?mode=block&configuration_target=<cftarget>&configuration_value=<ip>&page=1&per_page=1" \
               | jq -r '.result[0].id')
            if [ -z "$id" ]; then echo "<name>: id for <ip> cannot be found"; exit 0; fi;
            curl -4 -s -o /dev/null -X DELETE <_cf_api_prms> "<_cf_api_url>/$id"

_cf_api_url = https://api.cloudflare.com/client/v4/zones/<cfzone>/firewall/access_rules/rules
_cf_api_prms = -H 'Authorization: Bearer <cftoken>' -H 'Content-Type: application/json'

[Init]
cfzone = YOUR_ZONE
cftoken = YOUT_ZONE_TOKEN

我用了 Cloudflare 比較安全的 Zone 方式來使用 Firewall API

執行 ban 的時候我會打 API 執行 block IP unban 的時候會打 API 刪除 block 設定

用了 jq 這個套件來處理 response 如果沒有這個套件要記得裝一下 which jq 可以確認
另外要注意的是我用了 curl -4 意思是我用 IPv4 發出請求,不然 token 會因為 IP 不符擋下來

4. 設定 Cloudflare API

因為要透過 API 設定 Firewall,所以我們必須設定一個 API Token 確保安全 User API Tokens 點下新增,設定可以參考
- Zone:Read, Firewall Services:Edit

CleanShot 2025-05-30 at 01.07.45.png
必須要設定主機 IP 這也是為什麼我上面有提到 curl 要用 IPv4 去發送的原因
也別忘了 Zone ID 在主頁右下角
CleanShot 2025-05-30 at 01.11.35.png

如此一來我們就可以把 Zone ID 跟 Token 貼回剛剛的 action.d/cloudflare.conf

5. 驗證與測試

記得只要改動設定檔都要重啟 service fail2ban restart
可以使用指令

fail2ban-client status
fail2ban-client status nginx-php-attacks

確認我們的 nginx-php-attacks 有正確的設定了,也可以測試一下

fail2ban-client set nginx-php-attacks banip 168.61.83.86

如果有成功你會看到

|- Currently banned: 1
`- Banned IP list: 168.61.83.86 ...

CleanShot 2025-05-30 at 01.04.05.png

記得 unban 也要測試下

fail2ban-client set nginx-php-attacks unbanip 168.61.83.86

總結

完成以上步驟後,整個流程就順利串接起來了,現在 Fail2ban 可以透過 Cloudflare API 有效地管理封鎖(ban)與解除封鎖(unban)的 IP。這個解決方案的主要優點是:

  1. 能夠正確識別並封鎖攻擊者的真實 IP
  2. 在 CDN 層級就阻擋惡意流量
  3. 自動化處理封鎖和解鎖流程
  4. 定期更新 Cloudflare IP 清單,確保系統持續有效運作

需要注意的是:

  1. 要定期檢查 Fail2ban 的運作狀態
  2. 確保 Cloudflare API Token 的安全性
  3. 監控系統日誌,及時發現異常情況