「為什麼客戶說我們網站連不上?」「為什麼公司 SSH 出去 timeout?」「為什麼 LINE Notify webhook 突然送不出去?」
那天早上看到 Mattermost 一堆 alert 同時跳出來。15 分鐘後我們發現 — fail2ban 把我們自己對外的 WAN IP ban 進 ipset blackhole。
但詭異的地方:我們沒有任何成員從那個 IP 掃過任何漏洞 path。攻擊 log 顯示的 attacker IP 是其他國家的,為什麼會 ban 到自己?
追了一整天才找到 root cause,串起來是 6 步因果鏈。寫出來給跟我們一樣自架 fail2ban 的人參考 — 多 vhost 環境的隱形陷阱。
觸發環境 #
典型的小團隊自架 stack:
- 一台對外 reverse proxy(Apache)
- 後面接多個 vhost
- fail2ban 跑數個 jail,含「漏洞掃描偵測」類 jail(抓
/wp-login.php/.env/phpmyadmin等) - action 把違規者寫進 ipset blackhole(永久、全 port 一律 DROP)
這套用一陣子一直好好的。直到那天。
症狀 #
對外連線突然異常:
- 從辦公室訪問自家網站 → timeout
- 對外的 webhook 跟 API 整合 → 502
- 部分 DNS 查詢也慢 / timeout
但內網一切正常:機房內部互連、容器互連、本地服務都活著。
第一直覺:對外 router 掛了?檢查網路一切正常。 第二直覺:被 ISP 鎖了?打去客服說沒事。 第三直覺:看 fail2ban blackhole — 發現自家 WAN IP 在 ban list 裡。
sudo ipset list blackhole | grep <我們的 WAN IP>
203.0.113.10 ← 對,就是公網出口 IP
自己被自己 ban 了。
Root Cause 完整 6 步 #
從 access log + fail2ban.log 對齊時間 trace 出來的因果鏈:
Step 1:某些 vhost 沒設 CustomLog #
環境裡有一兩個 vhost 是「臨時實驗用」的,只開了 port 80 vhost、沒做 LE-SSL 版本,且沒明確指定 CustomLog:
<VirtualHost *:80>
ServerName vhost-a.our-domain.tw
DocumentRoot /var/www/vhost-a
# ← 沒有 CustomLog
</VirtualHost>
當時的想法:「短期用一下、不用設這麼齊」。就是這個短期帶來長期災難。
Step 2:Fallback 到 other_vhosts_access.log #
Apache 的 apache2.conf 預設裡有一行:
CustomLog /var/log/apache2/other_vhosts_access.log vhost_combined
沒明確 CustomLog 的 vhost 的所有請求全部落這裡。
我們以為「會被分流到對的 log」其實沒有 — fail2ban 一直在讀這個檔案。
Step 3:vhost_combined 格式行首是 hostname 不是 IP #
這是關鍵 — 標準 combined vs vhost_combined 兩個 LogFormat 行首完全不一樣:
# combined(行首是 client IP)
%h %l %u %t "%r" %>s ...
# vhost_combined(行首是 vhost:port,然後才是 client IP)
%v:%p %h %l %u %t "%r" %>s ...
實際 log 長這樣:
vhost-a.our-domain.tw:80 203.0.113.45 - - [18/May/2026:06:54:23 +0800] "GET /wp-login.php HTTP/1.1" 404 ...
↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
行首是 vhost hostname:port
Step 4:fail2ban filter logpath glob 把這檔案包進來了 #
漏洞掃描偵測類 jail 常見設定:
[漏洞掃描偵測 jail]
logpath = /var/log/apache2/*access*.log
Glob *access*.log 會 match 進 other_vhosts_access.log。這個 fallback log 也被掃進來(容易忽略)。
Step 5:fail2ban <HOST> macro 不只 match IP,也 match hostname #
fail2ban 內建的 <HOST> macro 定義:
<HOST> = (?:::f{4,6}:)?(?P<host>[\w\-.^_]*\w)
這個 regex 同時 match IPv4、IPv6、hostname。Filter 寫 ^<HOST> .* /wp-login 在 vhost_combined 格式下會:
- 抓到
vhost-a.our-domain.tw(行首的 hostname) - 而不是
203.0.113.45(真實 attacker IP,藏在後面)
Step 6:usedns = warn 預設 → DNS lookup → 解出自家 WAN IP → ban 自己 #
這是最後一根稻草。fail2ban [DEFAULT] 區塊沒明確寫 usedns 時,預設不是 no 而是 warn。意思:
- 遇到 hostname 時做 DNS lookup 解析成 IP
- log 一筆 warning(給 admin 知道)
- 照樣 ban 解出來的 IP
vhost A 記錄指向自家 WAN IP → fail2ban 解出 WAN IP → 加進 blackhole ipset → 整套出口流量被自家 server 擋掉。
時序對齊(典型 log 樣貌) #
凌晨某時刻,attacker 從某海外 IP 掃 /wp-login.php:
02:05:06 fail2ban.log: [apache-vuln-scan] Found 203.0.113.10 ← 自家 WAN IP(DNS lookup 解出來的)
02:05:07 fail2ban.log: [apache-vuln-scan] Found 203.0.113.45 ← 真實 attacker IP
02:05:08 fail2ban.log: [apache-vuln-scan] Ban 203.0.113.10 ← 但 maxretry=1 → 先 ban 自己
Found 203.0.113.10 跟 Found 203.0.113.45 是同一行 access log 觸發的:
- vhost_combined 行首
vhost-a.our-domain.tw:80→ match hostname → DNS lookup → 解出 WAN IP - 行內後面
203.0.113.45 - -→ match IP → 直接拿
兩個都被 fail2ban 當「來源 IP」處理,按出現順序 ban。自家 WAN IP 出現在前 → 先被 ban 掉。
解法:雙重防護 #
光修一個地方還不夠 — 雙保險才能避免類似事情再發生。
A. 每個 vhost 明確 CustomLog(首選) #
<VirtualHost *:80>
ServerName vhost-a.our-domain.tw
DocumentRoot /var/www/vhost-a
ErrorLog ${APACHE_LOG_DIR}/vhost-a_error.log
CustomLog ${APACHE_LOG_DIR}/vhost-a_access.log combined
# ↑ 用 combined 不是 vhost_combined,行首是 client IP
</VirtualHost>
做完 apachectl configtest + systemctl reload apache2,新請求就會落到自己的 access.log(格式正確、fail2ban 能正確抓 IP)。
B. fail2ban 全域關 DNS lookup(輔助) #
/etc/fail2ban/jail.local:
[DEFAULT]
usedns = no
sudo systemctl restart fail2ban
# 注意:jail config 改了要 restart,不能只 reload
意義:未來又有 vhost 漏設 CustomLog 時,至少不會誤殺。
C. ignoreip 加自家 WAN IP(保險的保險) #
[DEFAULT]
ignoreip = 127.0.0.1/8 192.168.0.0/16 <YOUR_WAN_IP>/32
怎麼確認自己有沒有踩到 #
# 1. 看有沒有 vhost 落到 other_vhosts_access.log
tail -20 /var/log/apache2/other_vhosts_access.log
# 期望:沒有新增(或檔案不存在)
# 2. 看 fail2ban 有沒有反查過 hostname(log 會有 warning)
sudo grep -i "DNS.*resolved" /var/log/fail2ban.log | head
# 期望:沒輸出
# 3. 看 blackhole 有沒有自家 IP
sudo ipset list blackhole | grep <YOUR_WAN_IP>
# 期望:沒輸出(或 clean)
教訓 #
| 教訓 | 為什麼 |
|---|---|
新增 vhost 永遠寫明確 CustomLog combined |
vhost_combined 行首是 hostname,配上 fail2ban DNS lookup 就完了 |
fail2ban usedns = no 應該第一天設 |
Debian / Ubuntu 預設 warn,會解 hostname 然後 ban,是隱性陷阱 |
| 「臨時用一下」常常會變長期 | 缺設定的 vhost 一旦上線就容易被忘記 |
| 多元件互動的 bug 最難 debug | 4 個元件(vhost、log format、filter、DNS)獨立看都沒問題,串起來才出事 |
| 自家 IP 被自己 ban 是難排查的 | 直覺會懷疑 ISP / router / firewall,最後才查到 fail2ban |
想做類似的維運強化 #
我們做小規模機房 IT 維運 / 資安強化 / 老系統現代化。如果你也是「自己當 MIS、自己 deploy」的小團隊:
- 一次性 audit + 強化清單(fail2ban、Apache、Nginx、Docker、SSH、防火牆)
- 持續維運合約 — 我們幫看週報 + alert 處理
- 跨地伺服器 / VPN 整合架構規劃
聯絡:0912852835 / henryccy@icloud.com / LINE @3q3tw / 線上表單
延伸閱讀: