本文へ移動
購読
GUIDE#security更新 2026.09.24

CrowdSecで不正アクセスを自動ブロックする:fail2banとの違いと導入手順

CrowdSecでSSHなどへの総当たりを検知し、nftablesで自動遮断する手順。fail2banとの違い、許可リスト、シミュレーションモードの落とし穴、中央APIへの送信の止め方まで実例付き。

level
中級
time
約40分
stack
CrowdSec 1.8.1 / crowdsec-firewall-bouncer-nftables 0.0.36 / Ubuntu 24.04
author
@ryoma_t
// 前提条件
Ubuntu Serverのroot権限 / SSHの鍵認証を設定済みであること / nftablesの基礎知識
CrowdSecがSSHの総当たりを検知してbanを決定し、nftablesのブロックリストに対象アドレスが入ったことを確認したターミナル
画像:nagoya.dev(実際の画面・実行結果をもとに作成)

CrowdSecとは、サーバーのログから総当たりや脆弱性探索などのふるまいを検知し、攻撃元のIPアドレスをファイアウォールなどで遮断するオープンソースのセキュリティエンジンです。fail2banと同じ役割を持ちますが、検知と遮断が別のプロセスに分かれていて、複数のサーバーの検知結果を1か所に集めたり、nftables以外の場所(nginxやCDN)で遮断したりできる点が異なります。

このガイドでは、Ubuntu 24.04にCrowdSec 1.8.1とnftables用のファイアウォールブロッカーを入れ、SSHへの総当たりを検知して遮断するまでを通します。そのうえで、fail2banとの使い分け、誤遮断を防ぐ許可リストとシミュレーションモード、つまずきやすい点を、検証用コンテナで実際に確かめた結果とともに示します。

この記事でわかること

  • ログの取り込みから遮断までの流れ(パーサー・シナリオ・ローカルAPI・ブロッカー)
  • fail2banとの違いを7つの観点で比べた表と、どちらを選ぶかの判断基準
  • Ubuntuへの導入、nginxのログの追加、nftablesでの遮断の確認手順
  • 自社のIPアドレスを遮断しないための許可リストと、シミュレーションモードの落とし穴
  • 中央APIに送られる情報と、送らない設定

CrowdSecの仕組み:検知エンジンとブロッカーが分かれている

CrowdSecは大きく2つの部品でできています。1つはログを読んで判断する検知エンジン(Security Engine)で、判断結果を保存するローカルAPI(LAPI)を内蔵しています。もう1つは遮断を実行するブロッカー(公式の呼び名はRemediation Component)で、ローカルAPIから「このIPアドレスを4時間遮断」という決定(decision)を定期的に取りに行き、nftablesのセットなどに反映します。

検知エンジンの中では、ログの1行が段階を追って処理されます。cscli explain を使うと、この流れを1行ずつ確かめられます。検証環境でsshdの失敗ログを1行与えると、次のように各段階を通過して、SSHの総当たり系のシナリオ6つに渡されました(出力の色付き記号は省いています)。

cscli explain の出力(抜粋・記号を省略)
line: 2026-09-23T23:10:01.123456+09:00 web01 sshd[2211]: Failed password for invalid user admin from 203.0.113.50 port 51234 ssh2
	├ s00-raw
	|	└ crowdsecurity/syslog-logs (+12 ~9)
	├ s01-parse
	|	└ crowdsecurity/sshd-logs (+8 ~1)
	├ s02-enrich
	|	├ crowdsecurity/dateparse-enrich (+2 ~2)
	|	├ crowdsecurity/geoip-enrich (+9)
	|	├ crowdsecurity/public-dns-allowlist (unchanged)
	|	└ crowdsecurity/whitelists (unchanged)
	├-------- parser success
	├ Scenarios
		├ crowdsecurity/ssh-bf
		├ crowdsecurity/ssh-bf_user-enum
		├ crowdsecurity/ssh-slow-bf
		├ crowdsecurity/ssh-slow-bf_user-enum
		├ crowdsecurity/ssh-time-based-bf
		└ crowdsecurity/ssh-time-based-bf_user-enum
  • s00-raw:syslog形式の日時・ホスト名・プログラム名を切り出します。
  • s01-parse:sshd用のパーサーが「失敗したログイン」「送信元IPアドレス」「ユーザー名」を取り出します。
  • s02-enrich:日時の正規化、国やASの付与、許可リストの判定を行います。許可リストに一致した行はここで捨てられ、シナリオに届きません。
  • シナリオ:送信元ごとに「バケツ」を用意し、一定時間内に溜まったイベント数が上限を超えると警告(alert)を出します。短時間の連続失敗を見る ssh-bf、間隔を空けた試行を見る ssh-slow-bf のように、時間の幅が違うシナリオが並行して動きます。

警告を受けたローカルAPIは、/etc/crowdsec/profiles.yaml の規則に従って決定を作ります。既定は type: ban、duration: 4h で、4時間の遮断です。同じファイルにはコメントアウトされた duration_expr: Sprintf('%dh', (GetDecisionsCount(Alert.GetValue()) + 1) * 4) の行があり、有効にすると、過去に遮断された回数に応じて4時間ずつ延びるようになります。

fail2banとの違いと使い分け

SSHの総当たり対策だけならfail2banでも同じ結果になります。違いは構成と、守れる範囲の広さです。メモリ使用量は、検証環境(Ubuntu 24.04のコンテナ)で起動直後のRSSを測った値です。

観点fail2banCrowdSec
検知の書き方正規表現のフィルターと、jailごとの回数・時間(maxretry・findtime・bantime)パーサーとシナリオをYAMLで記述。Hubで配布されるコレクションを入れるのが基本
構成1つのプロセスが検知と遮断を行う検知エンジン(ローカルAPIを含む)とブロッカーが別プロセス。複数台の検知結果を1つのローカルAPIに集められる
遮断する場所そのサーバーのiptables・nftables、または任意のコマンドnftables・iptablesのほか、nginxやCDNなど、ブロッカーを選んで置いた場所
他の利用者との共有なし検知結果を中央APIへ送り、コミュニティのブロックリストを受け取る(無効にできる)
設定の確認fail2ban-regexcscli explain
メモリ(実測)約26MB(sshdのjail 1つ)約150MB(linux・sshdのコレクション)と、ブロッカー約20MB
向いている環境SSHなど少数のサービスを守るサーバー1台Webアプリを含む複数台や、HTTPへの探索行為も止めたい場合

選び方の基準は単純です。VPS 1台でSSHだけを守るならfail2banで十分で、Linuxサーバーの基本ハードニングの設定がそのまま使えます。nginxの前で脆弱性探索やログイン画面への総当たりも止めたい場合や、サーバーが3台以上あって遮断をまとめて管理したい場合はCrowdSecを選びます。どちらも、鍵認証のみのSSHや管理元の制限の代わりにはならず、その外側に置く追加の層です。

Ubuntu 24.04へのインストール

公式のパッケージリポジトリを追加してから、aptで入れます。リポジトリ追加用のスクリプトは、実行前にダウンロードして中身を読んでください。執筆時点のスクリプトは、packagecloud.ioの署名鍵を /etc/apt/keyrings/ に置き、signed-by 付きの /etc/apt/sources.list.d/crowdsec_crowdsec.list を作るだけのものでした。

terminal(対象サーバー)
curl -s https://install.crowdsec.net -o install-crowdsec.sh
less install-crowdsec.sh            # 中身を確認してから実行する
sudo sh install-crowdsec.sh
sudo apt install crowdsec

sudo cscli version | head -1
# version: v1.8.1-debian-pragmatic-amd64-909b5157(執筆時点)

インストールの途中で、動いているサービスとログの場所が自動で調べられ、取り込み設定が /etc/crowdsec/acquis.d/ に作られます。検証環境では /var/log/auth.log を読む setup.sshd.yaml と、syslogを読む setup.linux.yaml ができ、crowdsecurity/linux・crowdsecurity/sshd・crowdsecurity/whitelist-good-actors の3つのコレクションが有効になりました。rsyslogを入れていない最小構成のサーバーでは、ファイルの代わりにjournaldから読む設定が作られます。

terminal
$ sudo cscli collections list -o raw
name,status,version,description
crowdsecurity/linux,enabled,0.4,core linux support : syslog+geoip+ssh
crowdsecurity/sshd,enabled,0.9,sshd support : parser and brute-force detection
crowdsecurity/whitelist-good-actors,enabled,0.4,Good actors whitelists

$ cat /etc/crowdsec/acquis.d/setup.sshd.yaml    # コメント行は省略
filenames:
  - /var/log/auth.log
  - /var/log/secure
labels:
  type: syslog
source: file

この時点では、検知はしても遮断はしません。インストール完了時にも CrowdSec alone will not block any IP address. と表示されます。遮断するには次のブロッカーが必要です。

nftablesのブロッカーで遮断する

ファイアウォール用のブロッカーは、nftables版とiptables版がパッケージで分かれています。nftables版を入れると、ローカルAPIへの登録(APIキーの発行)まで自動で行われます。

terminal
sudo apt install crowdsec-firewall-bouncer-nftables

# 登録されたブロッカーと、最後に決定を取りに来た時刻
$ sudo cscli bouncers list -o raw | cut -d, -f1-4
name,ip,revoked,last_pull
cs-firewall-bouncer-1790210705,127.0.0.1,validated,2026-09-24T00:45:24Z

設定は /etc/crowdsec/bouncers/crowdsec-firewall-bouncer.yaml にあり、既定値は mode: nftables、update_frequency: 10s(10秒ごとに決定を取得)、deny_action: DROP です。ブロッカーは ip crowdsec と ip6 crowdsec6 という専用のテーブルを作り、inputとforwardの両方に優先度 filter - 10 のチェーンを取り付けます。自作のファイアウォールより先に評価され、Dockerで公開したポートへの転送も遮断の対象になります。遮断するアドレスは、検知元ごとに別のセットへ入ります。

terminal(検証環境)
$ sudo nft list table ip crowdsec
table ip crowdsec {
	...
	set crowdsec-blacklists-crowdsec {
		type ipv4_addr
		flags timeout
		elements = { 203.0.113.50 timeout 3h59m29s expires 3h59m16s975ms }
	}

	chain crowdsec-chain-input {
		type filter hook input priority filter - 10; policy accept;
		counter name "processed"
		ip saddr @crowdsec-blacklists-crowdsec counter name "crowdsec-blacklists-crowdsec" drop
	}
	...
}

セット名の末尾は決定の出どころで、crowdsec は自分のサーバーの検知、cscli は手作業での追加、CAPI はコミュニティのブロックリストです。ブロッカーの設定ファイルにはローカルAPIのキー(api_key)が平文で入っているので、パーミッションを変えたり、設定管理のリポジトリにそのまま入れたりしないでください。漏れたら sudo cscli bouncers delete ブロッカー名 で無効にし、登録し直します。

自作のnftablesを systemctl restart nftables で読み直すと、パッケージ付属のユニットの停止処理(nft flush ruleset)でブロッカーのテーブルまで消えます。ブロッカーを再起動すれば作り直されますが、それまでの間は遮断が効きません。自作ルールの反映は systemctl reload nftables で行い、設定ファイルも自分のテーブルだけを作り直す書き方にします。書き方はnftables入門で扱っています。

nginxなど、ほかのログを取り込む

Webサーバーを守るには、コレクションを追加し、ログの場所を取り込み設定に書きます。crowdsecurity/nginx を入れると、nginxのログ用パーサーと、HTTP全般の探索行為を見るシナリオ群(crowdsecurity/base-http-scenarios)が一緒に入ります。シナリオ群には、http-probing(存在しないパスへの連続アクセス)、http-sensitive-files(.env などの機密ファイル探し)、http-sqli-probing、http-admin-interface-probing などが含まれていました。

terminal
sudo cscli collections install crowdsecurity/nginx

sudo tee /etc/crowdsec/acquis.d/nginx.yaml <<'EOF'
filenames:
  - /var/log/nginx/access.log
  - /var/log/nginx/error.log
labels:
  type: nginx
source: file
EOF

sudo systemctl reload crowdsec

labels.type は、その行をどのパーサーに渡すかを決める値です。ここを間違えると、ログは読まれてもパースされず、シナリオに届きません。反映後に sudo cscli metrics show acquisition parsers を実行し、nginxのファイルの Lines read と Lines parsed が増えていることを確かめます。

自社のIPアドレスを遮断しないための許可リスト

既定の許可リスト(crowdsecurity/whitelists)に入っているのは、127.0.0.0/8・10.0.0.0/8・172.16.0.0/12・192.168.0.0/16・::1 です。検証で送信元が192.168.1.50の行を cscli explain に与えると、parser success, ignored by whitelist (private ipv4/ipv6 ip/ranges) となり、シナリオに届きませんでした。一方、社内からインターネット経由でアクセスするときの送信元は会社のグローバルIPアドレスなので、この既定では守られません。

執筆時点の1.8系では、cscli allowlists で名前付きの許可リストを作れます。本社や拠点の固定IPアドレスを登録しておくと、検知されても決定は作られません。

terminal(検証環境)
$ sudo cscli allowlists create office -d "社内の固定IP"
allowlist 'office' created successfully
$ sudo cscli allowlists add office 203.0.113.60 198.51.100.0/28 -d "本社と拠点"
added 2 values to allowlist office
$ sudo cscli allowlists check 203.0.113.60
203.0.113.60 is allowlisted by item 203.0.113.60 from office (本社と拠点)

検証で、この203.0.113.60から総当たりに相当するログを流すと、/var/log/crowdsec.log に alert source 203.0.113.60 is allowlisted by 203.0.113.60 from office (本社と拠点), skipping と記録され、決定の一覧には何も増えませんでした。許可リストは「検知しない」ではなく「遮断しない」なので、社内から不審な試行があったこと自体はログに残ります。

シミュレーションモードで様子を見る

Webのシナリオを入れた直後は、正規の利用者や監視ツールを遮断しないかを確かめるため、しばらく「検知するが遮断しない」状態で動かします。このモードで作られた決定は simulated が true になり、ブロッカーは無視します。

terminal
# すべてのシナリオを遮断なしで動かす(様子見の期間)
sudo cscli simulation enable --global
sudo systemctl reload crowdsec

# 一覧では Action が (simul)ban になる
sudo cscli decisions list

# 問題がなければ元に戻す
sudo cscli simulation disable --global
sudo systemctl reload crowdsec

モードの切り替えは、reload するまで効きません。検証では、cscli simulation enable crowdsecurity/ssh-bf を実行しただけで総当たりのログを流すと、実際に遮断されました。また、シナリオ単位で指定すると、同時に発火する兄弟のシナリオ(ssh-bf_user-enum など)は対象外のままで、そちらが通常の遮断を出しました。様子見の期間は --global を使い、切り替えのたびに reload してください。

手作業での遮断と解除

インシデント対応で特定のアドレスをすぐ止めたいときや、誤って遮断した自社のアドレスを外したいときは、cscli decisions で直接操作します。追加した決定は10秒以内にブロッカーが取得し、crowdsec-blacklists-cscli のセットに入ります。

terminal(検証環境)
$ sudo cscli decisions add --ip 198.51.100.23 --duration 1h --reason "manual block test"
level=info msg="Decision successfully added"

$ sudo cscli decisions delete --ip 203.0.113.50
level=info msg="2 decision(s) deleted"

解除で2件消えているのは、同じ送信元に ssh-bf と ssh-bf_user-enum の2つのシナリオがそれぞれ決定を出していたためです。1件だけ消すと、残ったもう1件で遮断が続きます。IPアドレス指定で消せば、そのアドレスに関する決定がまとめて外れます。

中央APIに送られる情報と、送らない設定

既定では、検知エンジンは中央API(CAPI)に接続し、遮断した攻撃元の情報(IPアドレス、シナリオ名、時刻など)を送る代わりに、コミュニティのブロックリストを受け取ります。検証環境でも、起動直後から2時間間隔でブロックリストを取得し、crowdsec-blacklists-CAPI のセットに1万件を超えるアドレスが入りました。送られるのはログの行そのものではありませんが、社外へ送信が発生する以上、社内規程で外部送信を届け出る必要がある組織では、導入前に確認します。

送りたくない場合は、/etc/crowdsec/config.yaml の api.server の下にある online_client: と、その下の credentials_path: /etc/crowdsec/online_api_credentials.yaml の2行をコメントにして再起動します。コミュニティのブロックリストも届かなくなりますが、自分のサーバーでの検知と遮断はそのまま動きます。状態は sudo cscli capi status で確かめられます。

動作確認

本番のサーバーでは、偽のログを流さずに、経路を2つに分けて確かめます。検知側はサンプルの1行を cscli explain に与え、パーサーとシナリオまで届くか。遮断側は、影響のないドキュメント用アドレスで決定を手作業で追加し、nftablesのセットに入るかです。

terminal(対象サーバー)
# 検知側:Scenarios の下に ssh-bf などが並べばよい
sudo cscli explain --type syslog \
  --log "2026-09-23T23:10:01.123456+09:00 web01 sshd[2211]: Failed password for invalid user admin from 203.0.113.50 port 51234 ssh2"

# 取り込み量:Lines read と Lines parsed が増えているか
sudo cscli metrics show acquisition parsers

# 遮断側:ドキュメント用アドレスを5分だけ遮断し、セットに入るかを見る
sudo cscli decisions add --ip 192.0.2.99 --duration 5m --reason "bouncer test"
sleep 12
sudo nft list set ip crowdsec crowdsec-blacklists-cscli
sudo cscli decisions delete --ip 192.0.2.99

使い捨ての検証環境なら、失敗ログを実際に流して全体を通しで確かめられます。本番のログに偽の行を混ぜると監査の記録として使えなくなるので、この方法は検証用のサーバーに限ってください。検証では、TEST-NETのアドレス203.0.113.50から Failed password for invalid user admin の行を8行 /var/log/auth.log に追記すると、6件目で crowdsecurity/ssh-bf が発火して次の決定ができ、12秒待って確認した時点でnftablesのセットにも入っていました。

terminal(使い捨ての検証環境だけで実行)
# TEST-NETのアドレスからの失敗ログを8行作り、auth.log に追記する
for i in 1 2 3 4 5 6 7 8; do
  echo "$(date +%Y-%m-%dT%H:%M:%S.%6N%:z) web01 sshd[4242]: Failed password for invalid user admin from 203.0.113.50 port 5${i}022 ssh2"
done > sample-auth.log
cat sample-auth.log | sudo tee -a /var/log/auth.log > /dev/null && sleep 12

$ sudo cscli decisions list -o raw
id,source,ip,reason,action,country,as,events_count,expiration,simulated,alert_id
1,crowdsec,Ip:203.0.113.50,crowdsecurity/ssh-bf,ban,,,6,3h59m48s,false,1

$ sudo nft list set ip crowdsec crowdsec-blacklists-crowdsec
table ip crowdsec {
	set crowdsec-blacklists-crowdsec {
		type ipv4_addr
		flags timeout
		elements = { 203.0.113.50 timeout 3h59m53s expires 3h59m47s589ms }
	}
}

つまずきポイント

「listen tcp 127.0.0.1:8080: bind: address already in use」で起動しない

ローカルAPIは既定で127.0.0.1:8080を使うため、同じポートを使うアプリがあると FATAL local API server stopped with error で止まります。/etc/crowdsec/config.yaml の listen_uri、/etc/crowdsec/local_api_credentials.yaml の url、ブロッカーの設定の api_url の3か所を、空いているポート(例:127.0.0.1:8081)にそろえて変更します。

ログは読まれているのに、何も検知されない

cscli metrics show acquisition で Lines read は増えているのに Lines parsed が0なら、取り込み設定の labels.type がログの形式と合っていません。Lines read 自体が0なら、ファイルのパスが違うか、そのサーバーのログがjournaldにしかありません。どちらも cscli explain にログの実物を1行渡すと、どの段階で止まるかがわかります。

自社のアドレスが遮断された

別の回線やコンソールからサーバーに入り、sudo cscli decisions delete --ip 自社のアドレス で外します。複数のシナリオが決定を出していることがあるので、IPアドレス指定でまとめて消すのが確実です。そのうえで cscli allowlists に登録し、同じことが起きないようにします。監視サービスや脆弱性スキャンの送信元も、同じ理由で登録しておきます。

決定はあるのに通信が止まらない

sudo cscli bouncers list で last_pull が古いか空なら、ブロッカーが決定を取りに来ていません。systemctl status crowdsec-firewall-bouncer でサービスの状態を見て、ローカルAPIのポートを変えた場合は api_url が追従しているかを確かめます。sudo nft list tables に ip crowdsec がなければ、nftablesの再起動でテーブルが消えた可能性があるので、ブロッカーを再起動します。

よくある質問

CrowdSecとは何ですか?

ログから攻撃のふるまいを検知し、攻撃元のIPアドレスを遮断するオープンソースのソフトウェアです。検知エンジンと、ファイアウォールなどで遮断を実行するブロッカーに分かれていて、利用者同士で検知結果を共有したブロックリストも受け取れます。本体とファイアウォール用のブロッカーは無料で使えます。

fail2banとCrowdSecはどちらを使うべきですか?

サーバー1台でSSHだけを守るなら、軽くて設定の少ないfail2banで十分です。Webアプリへの探索行為も止めたい、複数台の遮断をまとめて管理したい、nginxやCDNなどファイアウォール以外の場所でも遮断したい、という条件があればCrowdSecが向いています。両方を同じログに対して動かす必要はありません。

CrowdSec Consoleへの登録は必要ですか?

必須ではありません。登録しなくても、検知・遮断・コミュニティのブロックリストの受け取りは動きます。Consoleは複数台の警告をWeb画面で見たり、追加のブロックリストを購読したりするための管理画面で、使う場合は cscli console enroll で登録します。提供される機能は変わることがあるので、執筆時点の内容で判断してください。

CrowdSecはDockerで動かせますか?

動かせます。公式イメージ crowdsecurity/crowdsec に、守りたいサービスのログを読み取り専用でマウントして使います。遮断はホストのファイアウォールで行う必要があるので、ブロッカーはホストに入れ、コンテナのローカルAPIを 127.0.0.1 に限って公開して接続させる構成が扱いやすくなります。

まとめ

CrowdSecは、ログを段階的に解析して決定を作る検知エンジンと、その決定をnftablesなどに反映するブロッカーの組み合わせです。SSHだけならfail2banで足り、Webや複数台を守るときにCrowdSecの構成が効いてきます。導入後は cscli explain で検知の経路を、手作業の決定で遮断の経路を確かめ、自社の固定IPアドレスを許可リストに入れてから本番の遮断を始めてください。様子見のモードは --global と reload をセットで使う、中央APIへの送信の有無は導入前に決める、の2点も忘れずに。

次に読む

$ subscribe --weekly
毎週月曜、企業ITとホームラボの要点を5分で。