> nginx / Apache access log 告警

幫你的 access log養一隻看門狗。

httpdog 一行一行讀 nginx 與 Apache 的 access log,一看到不對勁的請求就通知你:有人在掃 /.env、連續登入失敗、突然一堆 5xx。

  • 單一靜態執行檔,約 4 MB
  • 記憶體約 5 MB
  • 不需要資料庫
  • MIT 授權
rules.yaml 監看中 · udp 127.0.0.1:5514
  1. 1listen: 127.0.0.1:5514
  2. 2instance: web-01
  3. 3
  4. 4notifiers:
  5. 5 console: { type: stderr }
  6. 6 ops:
  7. 7 type: webhook
  8. 8 url_env: HTTPDOG_WEBHOOK_URL
  9. 9 secret_env: HTTPDOG_WEBHOOK_SECRET
  10. 10
  11. 11rules:
  12. 12 # someone is looking for leaked config files
  13. 13 - id: sensitive-file-probe
  14. 14 when:
  15. 15 any:
  16. 16 - { field: uri, op: suffix, value: /.env }
  17. 17 - { field: uri, op: prefix, value: /.git/ }
  18. 18 - { field: uri, op: regex, value: '\.(sql|bak)$' }
  19. 19 group_by: remote_addr
  20. 20 cooldown: 10m
  21. 21 notify: [console, ops]
  22. 22 message: "{remote_addr} tried to access {uri} (status {status})"
  23. 23
  24. 24 # the same IP fails to log in 20 times within 60 seconds
  25. 25 - id: login-bruteforce
  26. 26 when:
  27. 27 all:
  28. 28 - { field: uri, op: prefix, value: /login }
  29. 29 - { field: request_method, op: eq, value: POST }
  30. 30 - { field: status, op: in, value: [401, 403] }
  31. 31 group_by: remote_addr
  32. 32 threshold: { count: 20, window: 60s }
  33. 33 cooldown: 10m
  34. 34 notify: [console, ops]
  35. 35 message: "{remote_addr} repeated login failures"
  36. 36
  37. 37 # slow requests, except the export that is slow by design
  38. 38 - id: slow-request
  39. 39 when:
  40. 40 all:
  41. 41 - { field: request_time, op: gt, value: 5 }
  42. 42 - not: { field: uri, op: prefix, value: /export/ }
  43. 43 cooldown: 5m
  44. 44 notify: [console, ops]
  45. 45 message: "{uri} took {request_time}s"
$ journalctl -u httpdog -f0 告警
  1. 等待第一筆符合的請求 ...
01-how-it-works 比對,然後通知

進來一行 log,出去一個判斷。

你把「什麼情況值得通知」寫成規則檔,httpdog 把網頁伺服器寫出的每一行 log 拿來比對。它不儲存 log,也沒有查詢介面:符合就通知你,僅此而已。

  1. 01

    網頁伺服器把 access log 送過來

    nginx 透過 syslog(UDP)一行一行送,Apache 用管線接。兩者預設的 log 格式都能直接讀,網頁伺服器的設定可以維持原樣。

  2. 02

    httpdog 照你的規則比對

    條件可以用 log 的任何欄位,還有門檻(60 秒內 20 次)、分組(每個 IP 各算各的)與冷卻(10 分鐘內最多通知一次)。

  3. 03

    符合就變成一則通知

    以有簽章的 JSON 送到你自己的接收端,或是寫一行到系統 log。接收端再慢、甚至掛掉,都不會拖住收 log。

!httpdog 不是 log 儲存系統。要回頭查歷史 log,請搭配 Loki 或 Graylog 這類工具。

02-rules.yaml 載入時就檢查

告訴它什麼事該叫醒你。

一條規則就是「log 欄位的條件」加上「多久可以通知一次」。整個規則檔在載入時就會完整檢查,打錯字當場指出位置,不會等到半夜三點才發現。

有人在找 /.env、/.git/ 或資料庫備份。單筆請求就觸發,同一個 IP 10 分鐘內最多通知一次。

rules.yaml
- id: sensitive-file-probe
  when:
    any:
      - { field: uri, op: suffix, value: /.env }
      - { field: uri, op: prefix, value: /.git/ }
      - { field: uri, op: regex, value: '\.(sql|bak|tar\.gz)$' }
  group_by: remote_addr
  cooldown: 10m
  notify: [console, ops]
  message: "{remote_addr} tried to access {uri} (status {status})"
觸發時的輸出
ALERT [httpdog] sensitive-file-probe | 203.0.113.7 tried to access /.env (status 404) | remote_addr=203.0.113.7

一條規則的組成

when
條件:看哪個欄位、怎麼比、跟什麼比。可以用 all / any / not 組合
threshold
一段時間內累積幾次才通知
group_by
依某個欄位的值分開計算,例如每個 IP 各算各的
cooldown
同一組在這段時間內不重複通知
notify
要送到哪些 notifier
message
通知的文字,{欄位名} 會換成 log 裡的值

12 種比對方式

  • eq
  • ne
  • in
  • prefix
  • suffix
  • contains
  • regex
  • gt
  • gte
  • lt
  • lte
  • exists

6 份可以直接用的規則檔

涵蓋資安探測、登入濫用、站台健康、流量異常與關鍵業務路徑,每一份都是完整的設定。

規則檔完整說明(英文)

03-features 8 / 8

刻意做得很小。

只做一件事,而且不礙事。

  • [ 輕量 ]

    單一靜態執行檔

    檔案約 4 MB,閒置時記憶體約 5 MB。不需要資料庫、不需要 agent,也沒有其他服務要顧。

  • [ 免改設定 ]

    直接讀你現有的 log

    nginx 與 Apache 預設的 combined 格式可以直接讀。想比對任意欄位時,再改用 JSON 格式。

  • [ reload ]

    改規則不用重啟

    systemctl reload 就生效。新的規則檔寫壞了會沿用舊規則,監控不會因為一個錯字而中斷。

  • [ 不阻塞 ]

    通知不會拖慢比對

    通知在獨立的執行緒送出,每個佇列都有上限。webhook 掛掉不會卡住收 log,也不會吃光記憶體。

  • [ 簽章 ]

    可以驗證的 webhook

    每個請求都帶 HMAC-SHA256 簽章與時間戳。沒設密鑰的 webhook 連載入都過不了。

  • [ 嚴格 ]

    寫錯在載入時就失敗,不是半夜三點

    不認得的鍵、不合法的 regex、型別不對的值、沒設的密鑰,載入時一律擋下。--check 可以只驗證不啟動。

  • [ 不信任輸入 ]

    log 的內容一律當成惡意的

    log 的內容是送請求的人決定的。解析器與規則引擎都做過模糊測試,請求沒辦法在你的系統 log 裡偽造一行。

  • [ systemd ]

    守規矩的服務

    Type=notify、卡死時會重啟的 watchdog、不以 root 執行,而且結束碼會告訴 systemd 什麼時候重啟也沒用。

04-install.sh amd64 + arm64

幾分鐘就能跑起來。

先重播一份範例 log 看規則怎麼觸發,再安裝 Release 的執行檔,把網頁伺服器的 log 接過來。

不需要網頁伺服器:編譯後重播附的範例 log 即可。需要 Rust 1.85 以上。

shell
git clone https://github.com/guanting112/httpdog.git && cd httpdog
cargo build --release

# the example rules notify a webhook; a dummy URL and secret will do for now
export HTTPDOG_WEBHOOK_URL=http://127.0.0.1:9/dummy
export HTTPDOG_WEBHOOK_SECRET=dummy

./target/release/httpdog --stdin --config rules.example.yaml < sample.log
輸出
httpdog started: 4 rule(s), reading from stdin
ALERT [httpdog] sensitive-file-probe | 203.0.113.7 tried to access /.env (status 404) | remote_addr=203.0.113.7
ALERT [httpdog] slow-request | /report/monthly took 7.31s
ALERT [httpdog] sensitive-file-probe | 192.0.2.44 tried to access /backup/db.sql (status 404) | remote_addr=192.0.2.44
done: 6 lines, 0 unparseable, 3 alerts fired

完整步驟見 README

05-webhook.json HMAC-SHA256

你的程式可以信任的告警。

webhook 就是把 JSON 用 POST 送到你指定的網址。告警的每個部分各自一個欄位,接收端可以依規則分流、存起來,或轉成給人看的樣子。

  • 一定有簽章

    簽的是時間戳加上實際送出的位元組,接收端可以確認請求真的來自 httpdog,並拒絕被重送的舊請求。

  • 每個鍵都固定出現

    不適用的欄位是 null,而不是不見。接收端不用處理「鍵不存在」的情況。

  • 知道是哪一台發的

    多台 httpdog 送到同一個接收端時,instance 會告訴你是哪一台。

  • 會重試,但不會沒完沒了

    逾時 5 秒,總共試 3 次。都失敗就把告警寫進系統 log,不會一直重送。

請求標頭
X-Httpdog-Timestamp: 1794416274
X-Httpdog-Signature: sha256=1728aa567d7ef419cab2bf10fd5e1c764cdb608bd22d6f4d051b2043db19b8fe
請求內容
{
  "source": "httpdog",
  "instance": "web-01",
  "version": 1,
  "kind": "rule",
  "rule": "login-bruteforce",
  "timestamp": 1794416274,
  "text": "[httpdog] login-bruteforce | 198.51.100.9 repeated login failures | 3x in 60s | remote_addr=198.51.100.9",
  "message": "198.51.100.9 repeated login failures",
  "group_by": "remote_addr",
  "group": "198.51.100.9",
  "hits": 3,
  "window_secs": 60,
  "dropped": null,
  "event": { "remote_addr": "198.51.100.9", "uri": "/login", "status": 401 }
}

webhook 完整說明與接收端範例(英文)

06-verified 9 / 9 通過

在真的系統上驗證過。

以下每一台都是拿 Release 的執行檔,在真的 systemd 底下搭配真的 nginx 與 Apache 實際跑過:啟動、reload、當掉後重啟、watchdog、webhook 簽章、overload 告警。

發行版架構systemdnginxApache檢查項目
Ubuntu 22.04arm642491.18.02.4.5253 / 53
Ubuntu 24.04arm642551.24.02.4.5853 / 53
Ubuntu 24.04amd642551.24.02.4.5853 / 53
Ubuntu 26.04arm642591.28.32.4.6653 / 53
Debian 11arm642471.18.02.4.6753 / 53
Debian 12arm642521.22.12.4.7153 / 53
Debian 13arm642571.26.32.4.7153 / 53
Fedora 44arm642591.30.6-50 / 50
Rocky Linux 10arm642571.26.3-50 / 50
  • 測試用的是 OrbStack 的機器,它們共用同一個 kernel;Fedora 與 Rocky Linux 上 SELinux 是關閉的。Apache 只在 Ubuntu 與 Debian 上測過。
  • 每次 push 都會對 log 解析器與規則引擎做模糊測試。

已知限制與尚未驗證的部分(英文)

幫你的 access log 養一隻看門狗。

以 MIT 授權開放原始碼。