多數人以為,後台跳出「IP 已封鎖」的通知,這次的暴力破解攻擊就已經真的被擋下來了。實際上在那則通知出現以前,這次請求早就把整套流程跑完一輪,連線先被 Web 伺服器收下,PHP 開始執行,WordPress 核心跟著載入,外掛的登入失敗事件被觸發,資料庫多寫一筆計數,最後才輪到外掛決定要不要擋。畫面顯示「已封鎖」的那一刻,伺服器早就把這次嘗試的成本吃下去了。
Fail2ban 走的是完全不同的路線。它不是 WordPress 外掛,而是安裝在主機作業系統上的一個背景程式,持續盯著伺服器的紀錄檔,只要同一個來源反覆出現失敗嘗試,就直接改防火牆規則,把對方擋在連線建立之前。這個差異聽起來像技術細節,實際上決定了同一波暴力破解攻擊,會不會真的把主機資源拖垮。

Fail2ban 是什麼?在防火牆層攔截失敗登入的背景程式
Fail2ban 的角色定位很單純,它是主機作業系統上執行的一個背景程式,不是安裝在 WordPress 後台的外掛,也不會出現在外掛清單裡。它做的事情可以拆成 3 個步驟,先讀取伺服器的紀錄檔,例如 SSH 或 Web 伺服器留下的存取紀錄;再用正規表示式比對,找出符合「這是一次失敗嘗試」特徵的紀錄;一旦同一個來源在設定的時間內反覆命中,就直接去改系統的防火牆規則,把這個來源擋在門外。
這個角色定位不是巧合,而是 Fail2ban 原生的設計方向。它從一開始就是為整台主機的多種服務打造,SSH 登入、郵件伺服器、Web 伺服器都在它原生涵蓋的範圍內,開箱即用就能讀取多種常見的紀錄檔格式,Fail2ban 官方 GitHub 專案的說明也直接點出,它預設就能讀 sshd 與 Apache 這類標準紀錄檔,要涵蓋其他服務也只要新增對應的設定即可。WordPress 只是它可以延伸監控的其中一種服務,不是它唯一的用途,更不是為 WordPress 量身打造的工具。它從一開始就站在比 WordPress 更底層的位置,這正是它比外掛型防護更徹底的關鍵。
讀取紀錄、比對規則、觸發封鎖,三個環節組成一次攔截
上一節說 Fail2ban 讀日誌、比對特徵、改防火牆規則,這 3 個動作實際上分別對應到 3 種設定檔,各自負責一個環節。filter 負責用正規表示式在紀錄檔裡找出「這是一次失敗嘗試」的特徵;action 負責定義找到之後要執行的動作,通常是呼叫 iptables 或 nftables 加一條規則,有時也會順便觸發一封通知信;jail 則是把某個 filter 跟某個 action、還有要看哪個紀錄檔,全部綁在一起,變成一條可以啟用的規則。三者分開設定,是為了讓同一套偵測邏輯可以重複套用到不同服務,不用每加一種服務就重寫一次判斷規則。

Fail2ban 的設定檔案分成 4 種,fail2ban.conf 是全域設定,例如記錄層級;filter.d/ 底下的 *.conf 定義比對失敗特徵的正規表示式;action.d/ 底下的 *.conf 定義封鎖或解封時要執行的指令;jail.conf 與 jail.local 負責把 filter 跟 action 組合起來,綁定要監看的紀錄檔路徑與參數。這裡有一個常被忽略的升級慣例,套件內建的 *.conf 檔案會在下次更新時被覆蓋,自訂的規則要寫進對應的 *.local 檔案,或是現代做法直接拆到獨立的 filter.d/、jail.d/ 目錄下的新檔案,這樣套件升級才不會把自訂設定一起蓋掉。
一次封鎖真正觸發時,改動的不是某個資料庫欄位,而是系統的防火牆規則本身。用 iptables -L -nv 查看,會多出一條像 f2b-wordpress-auths 這樣、以對應 filter 或 jail 命名的規則鏈,對命中的來源 IP 執行拒絕或丟棄連線。這條規則跟 WordPress 完全無關,就算 WordPress 當下沒有在跑,這條防火牆規則照樣生效,這正是攔截點在哪裡這個核心差異的第一個具體證據。
bantime、findtime、maxretry 三個參數的搭配邏輯
決定「多嚴格算攻擊、鎖多久」靠 3 個參數搭配。findtime 定義觀察窗的長度,也就是在多少秒內去統計失敗次數;maxretry 定義這個觀察窗內失敗次數的上限,超過這個次數才會觸發;bantime 定義觸發之後要鎖多久,單位是秒,設成 -1 代表永久封鎖,這是社群教學裡常見的寫法。這 3 個參數不是各自獨立,而是彼此牽動,同一個網站,保護真人登入表單跟保護容易被自動化掃描的路徑,合理的搭配完全不同。
第一組示範是 findtime 設 60、maxretry 設 3、bantime 設 3600,意思是 60 秒內失敗超過 3 次就鎖 1 小時。這種偏嚴格的組合適合用在真人登入頁面,次數門檻拉低,是為了避免鎖太鬆讓自動化程式有機可乘,但鎖的時間不用太長,因為多數合法使用者頂多手滑打錯一兩次密碼。第二組示範是 findtime 設 60、maxretry 設 30、bantime 設 -1,意思是 60 秒內命中規則 30 次就永久封鎖。這種寬鬆次數搭配永久封鎖的組合,通常用在針對持續性掃描或機器人特徵的規則,例如對 wp-content、wp-includes 底下亂發請求的行為,用意不是抓一次手滑,而是一旦確定對方是自動化掃描,就直接永久擋掉,不需要留任何緩衝。

這 3 個參數搭配之外,還有一個實務上一定要先設定的參數叫 ignoreip,用來設定白名單,把自己辦公室或常用的來源 IP 排除在規則之外。忘記設定這一項,管理員自己就有可能因為多次登入失敗被鎖在自己的網站外面,這是導入 Fail2ban 之前最值得先確認好的一步。
封鎖點在防火牆前端或在 WordPress 程式碼裡頭
Fail2ban 之所以比外掛型登入防護更徹底,關鍵不在於誰的偵測邏輯比較聰明,也不是誰的規則比較多,而在於攔截動作實際發生在請求處理流程的哪一個位置。Fail2ban 的封鎖發生在連線建立的當下,由防火牆直接拒絕,WordPress 跟 PHP 進程根本沒有機會被啟動。
外掛型防護的判斷邏輯寫在 PHP 裡,代表整套流程要先跑完才輪到它決定擋不擋。連線先被 Web 伺服器收下,PHP 開始執行,WordPress 核心接著載入,外掛掛在登入失敗事件上的偵測函式才被觸發,再去資料庫或選項表查這個 IP 或帳號目前累積了幾次失敗,比對完才決定這一次要不要擋。這一整串處理,在外掛最後決定「擋」的那一刻其實都已經發生完畢,伺服器該花的運算、該寫的資料庫紀錄一件都沒有少。
WP fail2ban 這款專門把 WordPress 事件橋接給 Fail2ban 使用的官方工具,對這個現象有一句講得很直白的技術論述,一次攻擊不會因為 WordPress 顯示「已封鎖」就變得比較便宜,只要請求真的走到 PHP、開了一次資料庫連線、把計數器加一、再算出一個回應畫面,攻擊者這一次嘗試依然讓伺服器確實忙了一輪。反過來,防火牆層的封鎖才是真正的封鎖,沒有 PHP 迴圈要跑,沒有資料庫計數器要更新,也沒有一個「已封鎖」頁面還在偷偷消耗伺服器資源。這款外掛官方甚至直接點出,多數 WordPress 安全外掛的防護邏輯是在 PHP 內部作戰,逐一檢查每個請求、在資料庫裡維護計數器,想從 PHP 這一層去落實封鎖,而這一層,對這份工作來說已經站得太高了。
這個差異在小流量的情境下不太明顯,一天被試個幾十次,兩種做法的效能差距感覺不出來。但暴力破解攻擊的常態是分散又大量,一旦攻擊量拉高到每分鐘幾百次甚至上千次,外掛型防護每一次失敗都要寫一筆資料庫紀錄,資料庫的負載會隨攻擊量線性上升,這正是「WordPress 說擋下來了」不等於「這次攻擊沒有成本」的具體體現,伺服器的資料庫效能是被攻擊者用一次次失敗嘗試,實際消耗掉的。
外掛型防護的判斷點,仍落在 WordPress 執行之後
前面談的攔截點位置換算成具體外掛,會更容易看出兩者實際的差異所在。這裡挑 Loginizer 跟 Limit Login Attempts Reloaded 兩款全球通用的登入防護外掛來對照,單純因為安裝量夠大、預設行為有代表性,不是要比較誰做得比較好。
Loginizer 官方的說明是,嘗試暴力破解的來源 IP,在 3 次失敗登入之後會被封鎖 15 分鐘,多次被封鎖之後,封鎖時間會升級到 24 小時,這是預設設定,管理員可以在後台的 Loginizer 選單底下的 Brute force 頁面自行調整。這段描述本身就對應到前面的論述,3 次失敗、封鎖 15 分鐘,這個「鎖」是外掛在登入流程裡攔下表單提交、回傳一個錯誤訊息,不是防火牆層直接拒絕連線。
Limit Login Attempts Reloaded 的官方描述提到一個常被忽略的事實,WordPress 預設允許無限次登入嘗試,這其實是外掛存在的理由。它補上的限制方式是依 IP 位址和使用者名稱去限制登入次數,並且提供可調整的封鎖時間長度與重試次數上限。跟 Loginizer 一樣,這整套計數與判斷邏輯同樣是在應用層,以 IP 或帳號做累計再決定要不要擋,並不涉及系統層的防火牆規則。
這兩款外掛的計數與判斷邏輯,跑的都是 PHP 執行環境裡面的程式碼,這正是前面論述的具體案例,也是它們無論功能做得多完整,攔截點的位置都改變不了的原因。功能豐富與攔截位置是兩件事,一款外掛可以把介面做得很細緻、規則設定做得很彈性,但只要判斷仍然發生在 WordPress 載入之後,前面那整段處理成本就一定會發生。
登入頁防得住,xmlrpc 與 REST 這些缺口防不住
前面談的是同一個攻擊路徑,也就是登入表單裡,外掛跟 Fail2ban 攔截點位置的差異,但還有一個更根本的落差,多數外掛把偵測邏輯寫死在登入表單相關的事件上,只要攻擊走的不是 wp-login.php 這條路,這類外掛天生就看不到,更談不上擋。
最典型的例子是 xmlrpc.php 這條路徑。早期版本的 WordPress 曾經有個放大缺口,system.multicall 可以在一個 HTTP 請求裡打包一整批帳號密碼組合、一次性測試完;WordPress 核心在 4.4 版把這個缺口補上,multicall 只要遇到第一次驗證失敗就整批中止,現在的版本已經沒辦法再靠這招批量試密碼。但 xmlrpc.php 仍然是一條獨立於登入表單之外的驗證入口,走的是完全不同的程式邏輯,不會觸發登入表單的事件,攻擊者照樣可以逐筆對它發送驗證請求,只盯著登入表單的外掛一樣偵測不到。REST API 底下的 wp-json 端點也可能被拿來反覆嘗試身分驗證,同樣的道理,只要偵測邏輯沒有覆蓋到這條路徑,外掛就是瞎的。
這裡常見一個錯誤直覺,認為直接關掉 xmlrpc.php 就一勞永逸。實際上部分功能仍然依賴它跟外部服務溝通,例如 Jetpack 這類整合服務需要透過 xmlrpc.php 傳遞資料,直接停用可能連帶把正常功能一起關掉。因為 Fail2ban 看的是伺服器層級的存取紀錄,不管請求走哪一條程式路徑,只要它出現在紀錄檔裡,理論上就寫得出比對規則去涵蓋,不受限於某一個特定的 PHP 事件掛鉤點。
WP fail2ban 這款橋接外掛官方頁面列出的功能清單,剛好可以拿來當這個落差的具體對照。它內建阻擋使用者名稱列舉,也就是透過作者網址、REST 或 sitemap 這類管道去猜測有效帳號名稱的行為,官方說明直接點出,使用者名稱列舉往往是密碼猜測型暴力破解最常見的前置動作,擋住它等於在攻擊真正開始之前就先把整條攻擊鏈切斷。它也處理留言與 pingback 相關的濫用偵測,這些都是典型外掛型防護的偵測範圍之外,卻能透過主機層、或搭配這類橋接工具去涵蓋的攻擊面。
以 IP 判定的攔截照樣擋不住分散來源的嘗試
前面把 Fail2ban 講得像是比外掛徹底許多的解法,但它不是萬用的解方,還有一個限制不能不提。Fail2ban 封鎖邏輯的本質,是以單一 IP 為單位,在一段時間窗內累積夠多次失敗就鎖,這個設計本身有一個天花板。
只要攻擊者把嘗試分散到夠多不同的來源 IP,例如靠分散式殭屍網路或是輪替代理伺服器,單一 IP 永遠不會累積到觸發門檻,Fail2ban 的規則就形同沒有作用。國際資安標準組織 OWASP 在其身分驗證指南裡明確建議,失敗登入的計數應該跟帳號本身綁在一起,而不是只綁來源 IP,理由正是攻擊者可以用大量不同的來源 IP 繞過單純以 IP 為單位的封鎖。同一份文件也說明,常見的鎖定機制有兩種做法,一種是固定時間鎖定,另一種是指數遞增鎖定,鎖定時間從很短的一段開始,例如 1 秒,每失敗一次就翻倍。Fail2ban 的 findtime、maxretry、bantime 這套邏輯,本質上就是以 IP 為單位的固定時間鎖定,天生就吃到 OWASP 點出的這個限制。
另一個常見的環境限制,發生在站台架在 CDN 或反向代理後面的情況。伺服器紀錄檔記到的可能是代理伺服器的 IP,而不是訪客真正的來源 IP,這時候 Fail2ban 判斷出來的「同一個 IP」,其實是一大群完全不同的訪客共用的位址,鎖下去反而會誤傷正常使用者。這個問題不是 Fail2ban 獨有,Limit Login Attempts Reloaded 官方的說明也提到,如果站台用了 CloudFlare、Sucuri 或 Nginx 這類代理服務,而伺服器沒有正確設定還原真實來源 IP,所有使用者包括正常訪客跟攻擊者都會被記成同一個 IP,鎖住一個帳號等於把所有人一起鎖在外面。這是以 IP 為單位判斷的共通毛病,只是換了實作層,不是只有外掛會踩到、Fail2ban 就能全身而退。
回到 Fail2ban 官方自己的講法,它能做到的是降低不正確驗證嘗試的速率,沒辦法把弱密碼本身帶來的風險歸零,真正要保護服務,還是得搭配雙重驗證或公開金鑰這類驗證機制。這句話點出這整個攔截機制的定位,它是一道有效的緩衝,把猜測密碼這件事變得更慢、更貴,但帳號認證強度本身夠不夠,才是決定風險上限的根本因素。
主機控制權,才是要不要裝 Fail2ban 的分水嶺
前面拆了這麼多技術細節,最後回到最實際的問題,什麼情況才需要裝 Fail2ban。判準其實只有一個,對這台主機有沒有根層權限,能不能碰得到系統的防火牆設定與 Fail2ban 的設定目錄。如果答案是肯定的,Fail2ban 才會是一個選項;如果答案是否定的,外掛型防護不是比較差的選擇,而是唯一還能動的那一層。

兩者也不是二選一的對立關係,可以搭配著用,外掛只負責把 WordPress 特定的事件,轉寫成系統看得懂的紀錄格式,真正的封鎖動作交給主機上的 Fail2ban 去執行,但前提仍然是這台主機要有 Fail2ban 服務可以用。WP fail2ban 這類橋接外掛做的正是這件事,把 WordPress 內部發生的登入失敗、使用者名稱列舉這類事件,轉成 Fail2ban 讀得懂的紀錄格式,再由 Fail2ban 負責真正的攔截。這個判準落在 2 種常見情境裡,各自會遇到不一樣的實際狀況。
自架 VPS 能直接靠根層權限改防火牆規則
如果站台架在自己管理的 VPS、獨立主機,或是家裡自架的環境,這台主機的系統層就完全在自己手上,可以直接安裝 Fail2ban、寫 filter 跟 jail、改防火牆規則,這正是前面談到的整套機制真正派得上用場的情境。WP fail2ban 官方文件也明確講,只要是自己管理的 VPS、獨立主機或家用伺服器,這款橋接工具一定能正常運作。
這件事在自架 VPS 的情境下,並不是什麼冷僻的進階做法。部分雲端主機服務商推出的 WordPress 專用映像檔,甚至已經預先裝好了橋接外掛,例如 DigitalOcean 的 WordPress Droplet,官方說明這款映像檔就是 WordPress 搭配 WP fail2ban 預先裝好的組合,等於很多使用者其實在沒有特別留意的情況下,就已經帶著這套防護上線。這代表對於有能力自架、也願意維護系統層的使用者來說,把 Fail2ban 納入防護組合,已經是業界常見的預設實務,不是需要另外說服自己去做的冷門選項。
全代管主機碰不到系統層,只能靠外掛與主機商的防護
反過來,如果站台放在全代管平台上,系統層的管理權早就被主機商收回去了,站方本身根本碰不到防火牆設定,更別提去改 Fail2ban 的設定目錄。WP fail2ban 官方文件也直接點名,像 WP Engine、Kinsta、Pantheon 這類全代管主機,會把整套系統堆疊都管起來,安全防護也包含在內,這類主機通常不會開放橋接外掛需要的存取權限,免費版本在這種環境下派不上什麼用場。
共享主機的情況也類似,Fail2ban 這類系統層工具通常由主機商統一控管,站方拿不到直接設定的權限,真的想知道能不能用,得回頭去問主機商是否支援對應的過濾規則。這種情況下,Fail2ban 這條路線在架構上就不成立,退回外掛型防護,或是依賴主機商自己內建的安全機制,才是唯一可行的路,不需要因為裝不了 Fail2ban 就覺得自己的防護矮人一截。真正決定要不要裝的,是主機的控制權握在誰手上,不是哪一種工具本質上比較高明。
有根層權限的主機,把 Fail2ban 這套機制擺進去,能在請求真正碰到 WordPress 之前就把明顯的攻擊擋下來;沒有這個權限的主機,把外掛型防護該設定的次數與時間都設好,一樣能守住基本的登入安全。不管走哪一條路,都別忘了以 IP 為單位的攔截有它的天花板,真正決定帳號安全上限的,始終是密碼強度,以及有沒有搭配雙重驗證這類更根本的認證機制。
