FreeBSD14 + Nginx + fail2banで不正なアクセスを自動ブロック
ウェブサーバーへの攻撃が増えてきたので対策したときの覚書。
環境: FreeBSD 14.3-RELEASE-p2, nginx 1.28.0
Gemini 2.5 Proと対話しながら作業している。
1. fail2banとは?
Fail2banは、サーバーのログファイルを監視し、特定のパターン(ブルートフォース攻撃、スキャン活動など)を検知すると、その攻撃元のIPアドレスを自動的にファイアウォールでブロックする、侵入防止ソフトウェア。
動作の仕組み
- 監視 (Monitor): Nginxのアクセスログやエラーログ、SSHの認証ログなどをリアルタイムで監視。
- 検知 (Detect): フィルタと呼ばれる正規表現ルールに基づき、ログの中から「存在しないページへの連続アクセス」といった不正な兆候を探します。
- ブロック (Block): 不正な兆候が一定回数(例: 5分以内に3回)見つかると、アクションを実行して、OSのファイアウォール(FreeBSDの場合はpf)にそのIPアドレスを一時的にブロックするよう命令。
- 解除 (Unban): 設定された時間が経過すると(例: 1時間後)、自動的にブロックを解除。
fail2banとSSHGuardの違い
項目 | fail2ban | SSHGuard |
---|---|---|
主な用途 | ログを監視し、不正アクセスをiptables/pf等でブロック | SSHや各種サービスのログを監視し、攻撃元IPをブロック |
サービス対応範囲 | 汎用的(Nginx, Postfix, Dovecot, SSH など多数のfilter用意) | 主にSSH向け(追加対応も可能だが限定的) |
設定の柔軟性 | 高い(正規表現でログ解析ルールをカスタマイズ可) | 比較的シンプル、細かいカスタマイズは難しい |
導入・設定難易度 | やや高い(filter/jail設定が必要) | 低い(導入して有効化するだけで基本動作) |
FreeBSDでの利用実績 | ports/packages で提供、Nginx対策にもよく使われる | FreeBSD標準での利用実績が多く軽量 |
リソース消費 | やや重い(Python製) | 軽量(Cで実装) |
特徴 | 多機能でNginxなど幅広いサービスを守れる | シンプルで軽量、主にSSH向けに効果的 |
2.fail2banのインストールと設定
pkgでインストール。
# pkg search fail2ban
# pkg install py311-fail2ban
自動起動オン
# sysrc fail2ban_enable="YES"
設定ファイルの編集。
Jailは刑務所の意味。
jail.localファイルにするのがfail2banのお作法らしい。
# cd /usr/local/etc/fail2ban/
# cp jail.conf jail.local
# less jail.local
# =================================================================================# Fail2Ban :: jail.local for Nginx on FreeBSD# =================================================================================## このファイルは `jail.conf` の設定を上書きします。# Nginxのセキュリティ強化に必要な設定のみを記述しています。## ---------------------------------------------------------------------------------# [DEFAULT]セクション: 全てのルールに適用される共通設定# ---------------------------------------------------------------------------------[DEFAULT]# --- 基本設定 ---# 自分自身や信頼できるIPを誤ってブロックしないためのホワイトリスト (最重要)# 127.0.0.1/8 ::1 は必須。カンマかスペース区切りで、ご自身のオフィスのIPなどを追加してください。# 例: ignoreip = 127.0.0.1/8 ::1 123.45.67.89/32ignoreip = 127.0.0.1/8 ::1# ブロックする時間 (bantime)# s: 秒, m: 分, h: 時間, d: 日, w: 週# 最初は短め(10mなど)でテストし、問題なければ長くする(1h or 1d)のがおすすめです。bantime = 1h# 違反を検知する期間 (findtime)findtime = 10m# 上記 `findtime` の間に何回違反したらブロックするか (maxretry)maxretry = 5# --- FreeBSD特有の設定 ---# FreeBSDの標準ファイアウォールである `pf` を使用するよう指定します。banaction = pf# ---------------------------------------------------------------------------------# [JAILS]セクション: Nginx用の個別ルール# ---------------------------------------------------------------------------------## ここでは、Nginxに関連するルールのみを `enabled = true` に設定します。# `jail.conf` に存在する他の多くのルール(sshd, apacheなど)は、# ここで指定しない限りデフォルトで無効(`enabled = false`)のままです。## --- ルール1: Nginxのレート制限超過を検知 ---# Nginxのlimit_reqディレクティブでブロックされたIPを、Fail2banでさらにブロックします。# 攻撃的なクローラーや軽度なDoS攻撃の遮断に非常に効果的です。## ※このルールを機能させるには、別途フィルタファイルを作成する必要があります。# ( /usr/local/etc/fail2ban/filter.d/nginx-limit-req.conf )[nginx-limit-req]enabled = truefilter = nginx-limit-req# Nginxのレート制限エラーはエラーログに出力されます。パスは環境に合わせてください。logpath = /var/log/nginx/error.log# より悪質と判断し、少し厳しめに設定maxretry = 5findtime = 5mbantime = 2h# --- ルール2: NginxのHTTPベーシック認証への総当たり攻撃を検知 ---# .htpasswdなどを使った認証ページへのブルートフォース攻撃をブロックします。[nginx-http-auth]enabled = true# 認証エラーもエラーログに出力されます。logpath = /var/log/nginx/error.log# --- ルール3: 存在しないスクリプトへのスキャン攻撃を検知 ---# 存在しないPHPファイルなどへのアクセスを繰り返す脆弱性スキャナをブロックします。# (例: /wp-config.php.bak, /admin.php など)[nginx-noscript]enabled = true# 404エラーは通常アクセスログに記録されます。logpath = /var/log/nginx/access.log# --- ルール4: 悪意のあるボットによるスキャンを検知 ---# 既知の悪意あるUser-Agentや、脆弱性を探す典型的なリクエストパターンを検知します。[nginx-botsearch]enabled = true# こちらもアクセスログを監視します。logpath = /var/log/nginx/access.log
nginx-limit-reqフィルタの作成。
既に存在していたのでGemini先生の言う通りに置き換える。
# less filter.d/nginx-limit-req.conf
[Definition]failregex = limiting requests, excess:.* by zone ".*", client: <HOST>,ignoreregex =
構文テスト
# fail2ban-client -d
2025-08-18 18:13:44,654 fail2ban.configreader [11974]: ERROR Found no accessible config files for 'filter.d/nginx-noscript' under /usr/local/etc/fail2ban2025-08-18 18:13:44,654 fail2ban.jailreader [11974]: ERROR Unable to read the filter 'nginx-noscript'2025-08-18 18:13:44,655 fail2ban.jailsreader [11974]: ERROR Errors in jail 'nginx-noscript'. Skipping...
Gemini先生にエラー報告して言う通りに対応する。
# vim filter.d/nginx-noscript.conf
[Definition]# Nginxで存在しないスクリプトへのアクセス試行を検知する# 例: "GET /nonexistent.php HTTP/1.1" 404failregex = ^<HOST> -.*GET.*(\.php|\.asp|\.exe|\.pl|\.cgi|\.scgi|\.sh).* 404ignoreregex =
構文テスト
# fail2ban-client -d
Fail2ban起動
# service fail2ban start
3.PFの設定
/etc/pf.conf の最後に追記
# less /etc/pf.conf
...# Fail2Ban Anchor# Fail2Banが作成する全てのアンカー(f2b/で始まるもの)を読み込むanchor "f2b/*"
構文チェックして再読み込み。
# pfctl -nf /etc/pf.conf
# service pf reload
Fail2banも再起動
# service fail2ban restart
4.fail2banの動作確認
テスト用のIPアドレスを手動でブロック
# fail2ban-client set nginx-limit-req banip 192.0.2.1
ブロックリストを確認。
# pfctl -a f2b/nginx-limit-req -t f2b-nginx-limit-req -T show
192.0.2.1
追加されたルールを確認。
# pfctl -a f2b/nginx-limit-req -s rules
block drop quick proto tcp from <f2b-nginx-limit-req> to any port = httpblock drop quick proto tcp from <f2b-nginx-limit-req> to any port = https
手動でブロックを解除
# fail2ban-client set nginx-limit-req unbanip 192.0.2.1
5. ブロックしたIPアドレスを確認
nginx-limit-reqフィルタのJailを確認する。
# fail2ban-client status nginx-limit-req
Status for the jail: nginx-limit-req|- Filter| |- Currently failed: 3 <-- 現在のfindtime内に検知した違反回数| |- Total failed: 57 <-- これまでの累計違反回数| `- File list: /var/log/nginx/error.log`- Actions|- Currently banned: 2 <-- 現在ブロック中のIPアドレス数|- Total banned: 5 <-- これまでの累計ブロックIP数`- Banned IP list: 198.51.100.10 203.0.113.25 <-- ★ブロック中のIPリスト
すべての有効なJailの概要を確認。
# fail2ban-client status
Status|- Number of jail: 4`- Jail list: nginx-botsearch, nginx-http-auth, nginx-limit-req, nginx-noscript
Fail2ban自体の動作ログを確認。
# less /var/log/fail2ban.log
Gemini先生がフィルタごとに一括表示するコマンドを作ってくれたので、.bashrcを新規作成して登録する。
# vim ~/.bashrc
# Fail2banの全JailのブロックIPをpfテーブルから一覧表示するfunction pf-f2b-list() {# 有効になっているJailのリストを取得するlocal jaillist=$(fail2ban-client status | grep "Jail list" | sed -E 's/.*Jail list:[[:space:]]+//' | tr ',' ' ')# 新しいBashシェルを起動し、Jailのリストを引数として渡すbash -c '# 引数(Jail名)を一つずつ処理するループfor jail in "$@"; doecho "--- Table for Jail: $jail ---"# pfctlの出力を変数に格納する (エラーは捨てる)# "local" を削除し、通常の変数として宣言content=$(pfctl -a "f2b/$jail" -t "f2b-$jail" -T show 2>/dev/null)# 変数 `content` に中身があるかどうかをチェックif [ -n "$content" ]; then# 中身があれば表示するecho "$content"else# 中身がなければメッセージを表示するecho "(No entries or table not found)"fiechodone' _ $jaillist}
.bash_profileに.bashrcを読み込むように記述する。
# less ~/.bash_profile
if [ -f ~/.bashrc ]; then. ~/.bashrcfi
.bash_profileはログインシェルとして起動した時に一度だけ読み込まれる。
.bashrcは非ログインシェルとして起動するたびに読み込まれる。
シェルの再読み込み。
# source ~/.bash_profile
実行
# pf-f2b-list
--- Table for Jail: nginx-botsearch ---(No entries or table not found)--- Table for Jail: nginx-http-auth ---(No entries or table not found)--- Table for Jail: nginx-limit-req ---45.156.129.13645.156.129.13745.156.129.13845.156.129.139133.125.37.176--- Table for Jail: nginx-noscript ---13.79.168.14452.169.251.174104.41.205.21135.220.16.255172.192.63.224196.251.66.105
pf-f2b-listの実行結果を5秒ごとに自動更新(再描画)しながら監視するコマンド。
# while true; do clear; pf-f2b-list; sleep 5; done
Fail2banの動作ログとnginxのアクセスログを別コンソールで監視する。
ブロックすると、nginxのアクセスログに出なくなるはず。
# tail -f /var/log/fail2ban.log
# tail -f /var/log/nginx/access.log
分かってきたのでフィルタの設定は後で見直す。
▼ 関連記事
- CentOS Stream 9 + firewalld + SSHGuardで不正な攻撃を自動ブロック
- FreeBSD14 + SSHGuard + PF(Packet Filter)で不正な攻撃を自動ブロック