自宅サーバー監視モニターをPHPで作る【実装編】

前記事では、自宅サーバーのアクセスログやWordPressのファイル変更を確認するために作った「Server Monitor」の概要を紹介しました。

この記事では、その監視ページを実際に構築する手順をまとめます。

前記事と重複する説明はできるだけ省き、サーバー上で上から順番に作業していけば完成するように整理しました。

なお、この記事では匿名化のため、ドメイン名を site1.example.com、site2.example.com、サーバー上のディレクトリを /home/web/html/site1、/home/web/html/site2 としています。

自分の環境に合わせて読み替えてください。

1.監視用ディレクトリを作る

まず、監視ページを置くディレクトリを作ります。

mkdir -p /var/www/html/monitor/data

ここでは、

/var/www/html/monitor/
├── index.php
└── data/

という構成にします。

data ディレクトリには、WordPressなどのファイル状態を記録したベースラインファイルを保存します。

2.Apacheに監視専用ログを追加する

通常のApacheアクセスログとは別に、監視ページで扱いやすい形式のログを作ります。

ApacheのVirtualHost設定に、次の LogFormat を追加します。

LogFormat "%{%Y/%m/%d %H:%M:%S}t %v %a %m %U %>s" monitor_format

この形式では、ログを次の順番で記録します。

日時 ドメイン IPアドレス Method アクセス先 ステータスコード

例えば、

2026/09/30 20:45:17 site1.example.com 198.51.100.20 GET /flamini.php 404

のようになります。

続いて、監視対象のVirtualHostに CustomLog を追加します。

CustomLog /var/log/httpd/monitor_access_log monitor_format

既存の CustomLog は削除せず、監視用ログを追加する形にします。

複数のサイトを監視する場合は、それぞれのVirtualHostに追加します。

設定を確認します。

apachectl configtest

次のように表示されればOKです。

Syntax OK

問題がなければApacheをリロードします。

systemctl reload httpd

ログファイルが作成されたことを確認します。

ls -l /var/log/httpd/monitor_access_log

アクセスを発生させて、

tail -n 20 /var/log/httpd/monitor_access_log

で内容を確認します。

3.PHPの監視ページを作る

監視ページ本体を、

/var/www/html/monitor/index.php

として作成します。

最初にPHPのタイムゾーンを設定します。

date_default_timezone_set('Asia/Tokyo');

これでログの日時や「今日」の判定を日本時間に統一できます。

4.ログイン機能を追加する

監視ページにはアクセスログやサーバー情報が表示されるため、誰でも見られる状態にはしません。

PHPセッションを利用してログイン機能を付けます。

セッションCookieは30日間有効にします。

session_set_cookie_params([
    'lifetime' => 60 * 60 * 24 * 30,
    'path' => '/',
    'secure' => isset($_SERVER['HTTPS']),
    'httponly' => true,
    'samesite' => 'Lax'
]);

session_start();

ログアウト処理は次のようにします。

if (isset($_GET['logout'])) {
    $_SESSION = [];
    session_destroy();
    header('Location: /monitor/');
    exit;
}

ログイン時には、パスワードそのものをPHPファイルに書かず、ハッシュ値を使用します。

ハッシュはサーバー上で次のように生成できます。

php -r "echo password_hash('ここにパスワード', PASSWORD_DEFAULT), PHP_EOL;"

生成されたハッシュをPHP側に設定し、ログイン時には password_verify() で確認します。

ユーザー名は固定値との比較、パスワードはハッシュとの比較にします。

実際の記事では、ユーザー名やハッシュは公開しません。

5.監視対象とログの場所を設定する

次に、監視対象をまとめて設定します。

記事では実際のサイト名を匿名化して、次のようにします。

$fileTargets = [
    'site1' => '/home/web/html/site1',
    'site2' => '/home/web/html/site2'
];

$dataDir = '/var/www/html/monitor/data';

$logFiles = [
    '/var/log/httpd/monitor_access_log'
];

$fail2banLog = '/var/log/fail2ban.log';

ここで設定しているのは、

  • ファイル変更を監視するディレクトリ
  • ファイル状態を保存するディレクトリ
  • Apacheの監視専用ログ
  • Fail2Banのログ

です。

6.PHPでApacheログを読み込む

まず、ログをPHPから読み込みます。

監視専用ログは、

日時 ドメイン IP Method URL ステータス

という固定形式なので、正規表現で分解できます。

foreach ($logFiles as $logFile) {

    if (!is_readable($logFile)) {
        continue;
    }

    $lines = @file(
        $logFile,
        FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES
    );

    if ($lines === false) {
        continue;
    }

    foreach ($lines as $line) {

        if (
            !preg_match(
                '/^(\d{4}\/\d{2}\/\d{2}\s+\d{2}:\d{2}:\d{2})\s+' .
                '(\S+)\s+' .
                '(\S+)\s+' .
                '(\S+)\s+' .
                '(\S+)\s+' .
                '(\d{3})$/',
                $line,
                $m
            )
        ) {
            continue;
        }

        $datetime = $m[1];
        $domain   = $m[2];
        $ip       = $m[3];
        $method   = $m[4];
        $path     = $m[5];
        $status   = $m[6];

        // ここで集計・表示用の配列に追加する
    }
}

これで、1行のログを、

日時
ドメイン
IPアドレス
Method
アクセス先
ステータスコード

に分解できます。

7.今日のアクセス数と404を集計する

アクセスログを読み込むときに、今日のアクセス数も同時に集計します。

$totalToday = 0;
$total404 = 0;

$today = date('Y/m/d');

ログの日時が今日なら、

if (strpos($datetime, $today . ' ') === 0) {
    $totalToday++;

    if ($status === '404') {
        $total404++;
    }
}

とします。

これで、

今日のアクセス
404

を取得できます。

8.要注意アクセスを抽出する

次に、監視対象として確認したいアクセスを抽出します。

今回の監視では、例えば次のようなパスを対象にします。

/.env
/.git/
/wp-config.php
/xmlrpc.php
/phpmyadmin/
/pma/
/adminer/
/server-status
/server-info
/.aws/
/.ssh/

正規表現では次のようにまとめます。

$suspiciousPattern =
    '/\/\.env(?:\/|$)' .
    '|\/\.git(?:\/|$)' .
    '|\/wp-config\.php(?:\?|\/|$)' .
    '|\/xmlrpc\.php(?:\?|\/|$)' .
    '|\/phpmyadmin(?:\/|$)' .
    '|\/pma(?:\/|$)' .
    '|\/adminer(?:\/|$)' .
    '|\/server-status(?:\/|$)' .
    '|\/server-info(?:\/|$)' .
    '|\/\.aws(?:\/|$)' .
    '|\/\.ssh(?:\/|$)' .
    '/i';

これらに該当したアクセスを要注意アクセスとして記録します。

さらに、存在しないPHPファイルを探しているようなアクセスも確認できるようにします。

例えば、

/flamini.php
/old.php
/test.php

などです。

今回は、

if (
    preg_match($suspiciousPattern, $path) ||
    (
        $status === '404' &&
        preg_match('/\.php(?:\?|$)/i', $path)
    )
) {
    $suspicious[] = $item;
}

という判定にします。

ここでの「要注意」は、攻撃が成功したという意味ではありません。

あくまで、

「一度確認しておいたほうがよさそうなアクセス」

として抽出しています。

9.最新100件を保存する

ログそのものは大量になるため、画面では最新100件だけを表示します。

アクセスを配列に追加したあと、

$accesses = array_reverse($accesses);
$accesses = array_slice($accesses, 0, 100);

とします。

要注意アクセスも同様です。

$suspicious = array_reverse($suspicious);
$suspicious = array_slice($suspicious, 0, 100);

ここで重要なのは、表示件数と集計件数を分けることです。

今日のアクセス数や要注意アクセス数は、最新100件に絞る前に集計します。

そのため、

今日のアクセス数
今日の404件数
今日の要注意アクセス数

はログ全体から正しく集計されます。

10.24時間のアクセス数を集計する

次に、1時間単位でアクセス数を集計します。

最初に24時間分の配列を0で初期化します。

$hourlyAccess = array_fill(0, 24, 0);
$hourly404    = array_fill(0, 24, 0);

今日のログについて、日時の11文字目と12文字目から「時」を取り出します。

$hour = (int)substr($datetime, 11, 2);

if ($hour >= 0 && $hour <= 23) {
    $hourlyAccess[$hour]++;

    if ($status === '404') {
        $hourly404[$hour]++;
    }
}

これで、

00時
01時
02時
...
23時

それぞれのアクセス数を取得できます。

11.24時間グラフを作る

24時間の配列を利用して、ヘッダー部分に小さな棒グラフを表示します。

最大値を基準にして、各時間の棒の高さを決めます。

$maxHourly = max($hourlyAccess);

if ($maxHourly <= 0) {
    $maxHourly = 1;
}

例えば最大アクセス数を50px相当として、

$height = $count > 0
    ? max(4, ($count / $maxHourly) * 50)
    : 2;

とします。

アクセスがない時間帯も2px表示することで、24時間の位置関係が分かります。

12.404をグラフに重ねる

アクセス数だけではなく、その中に404がどの程度含まれているのかも分かるようにします。

$height404 = 0;

if ($count404 > 0 && $count > 0) {
    $height404 = max(
        2,
        ($count404 / $count) * $height
    );

    if ($height404 > $height) {
        $height404 = $height;
    }
}

$heightNormal = $height - $height404;

これで、

アクセス全体
└── 404部分

という積み上げ表示になります。

さらに、PCブラウザでグラフにマウスを乗せたときに、

15時 23件(404: 4件)

のような情報が表示されるようにします。

title="<?= h($hour . '時 ' . $count . '件(404: ' . $count404 . '件)') ?>"

13.WordPressなどのファイル監視を追加する

アクセスログだけではなく、監視対象ディレクトリのファイル変更も確認できるようにします。

現在のファイル状態を取得する関数を作ります。

function getDirectoryState($dir)
{
    $state = [];

    if (!is_dir($dir)) {
        return $state;
    }

    try {

        $iterator =
            new RecursiveIteratorIterator(
                new RecursiveDirectoryIterator(
                    $dir,
                    FilesystemIterator::SKIP_DOTS
                )
            );

        foreach ($iterator as $file) {

            if (!$file->isFile()) {
                continue;
            }

            $path = $file->getPathname();

            $state[$path] = [
                'mtime' => $file->getMTime(),
                'size'  => $file->getSize()
            ];
        }

    } catch (Throwable $e) {
        return $state;
    }

    return $state;
}

ここでは各ファイルについて、

ファイルパス
更新時刻
ファイルサイズ

を取得します。

14.ファイル状態のベースラインを作る

現在の状態と比較するため、最初に正常な状態をベースラインとして保存します。

例えば、

find /home/web/html/site1 -type f -printf '%p|%T@|%s\n' > /var/www/html/monitor/data/site1_files.txt

もう一つのサイトも同様です。

find /home/web/html/site2 -type f -printf '%p|%T@|%s\n' > /var/www/html/monitor/data/site2_files.txt

保存されるデータは、

ファイルパス|更新時刻|サイズ

という形式です。

例えば、

/home/web/html/site1/index.php|1759230000.123|12345

のようになります。

15.ベースラインをPHPから読み込む

ベースラインを読み込む関数を作ります。

function loadFileState($file)
{
    $state = [];

    if (!is_readable($file)) {
        return $state;
    }

    $lines = @file(
        $file,
        FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES
    );

    if ($lines === false) {
        return $state;
    }

    foreach ($lines as $line) {

        $parts = explode('|', $line);

        if (count($parts) < 3) {
            continue;
        }

        $state[$parts[0]] = [
            'mtime' => (int)floor((float)$parts[1]),
            'size'  => (int)$parts[2]
        ];
    }

    return $state;
}

find の %T@ は小数を含む場合があるため、

(int)floor((float)$parts[1])

としてPHPのファイル更新時刻と比較できる形にします。

16.新規・変更・削除を判定する

現在の状態とベースラインを比較します。

新しいファイルが存在すれば、

作成

とします。

以前存在していたファイルがなくなっていれば、

削除

です。

さらに、ファイルサイズまたは更新時刻が変わっていれば、

変更

とします。

処理は次のようになります。

foreach ($currentState as $path => $info) {

    if (!isset($previousState[$path])) {

        $fileChanges[] = [
            'type'   => 'created',
            'target' => $name,
            'path'   => $path,
            'time'   => date('Y/m/d H:i:s')
        ];

    } elseif (
        $previousState[$path]['mtime'] !== $info['mtime'] ||
        $previousState[$path]['size'] !== $info['size']
    ) {

        $fileChanges[] = [
            'type'   => 'modified',
            'target' => $name,
            'path'   => $path,
            'time'   => date('Y/m/d H:i:s')
        ];
    }
}

foreach ($previousState as $path => $info) {

    if (!isset($currentState[$path])) {

        $fileChanges[] = [
            'type'   => 'deleted',
            'target' => $name,
            'path'   => $path,
            'time'   => date('Y/m/d H:i:s')
        ];
    }
}

ここで重要なのは、監視ページを表示しただけではベースラインを更新しないことです。

つまり、誰かにファイルを追加された場合、そのファイルを自動的に「正常」として登録してしまうことはありません。

WordPressやプラグインを自分で更新した場合は、内容を確認したうえでベースラインを作り直します。

17.ファイル変更を実際にテストする

テスト用ファイルを作ります。

touch /home/web/html/site1/monitor_test.txt

次に内容を変更します。

echo test >> /home/web/html/site1/monitor_test.txt

最後に削除します。

rm /home/web/html/site1/monitor_test.txt

監視ページを確認すると、

作成
変更
削除

として検出できます。

18.Fail2BanのBAN履歴を読み込む

次に、Fail2BanのログからBAN履歴を取得します。

/var/log/fail2ban.log

から、

Ban IPアドレス
Unban IPアドレス

を解析します。

BAN理由については、今回の監視用Jailに合わせて、

$banReason = [
    'monitor-env'      => '.env',
    'monitor-git'      => 'Git',
    'monitor-xmlrpc'   => 'XML-RPC',
    'monitor-php-scan' => '連続スキャン'
];

のように対応させます。

BANログは正規表現で、

if (
    preg_match(
        '/^(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2}),\d+.*NOTICE\s+\[([^\]]+)\]\s+Ban\s+([0-9a-fA-F:.]+)\s*$/',
        $line,
        $m
    )
) {
    // BANとして記録
}

とします。

UNBANについても同様に解析します。

監視画面には、直近100件のBAN履歴を表示します。

また、今日発生したBAN件数も集計して、ダッシュボードに表示します。

19.IPアドレスからWHOISを確認できるようにする

アクセスログとBAN履歴に表示するIPアドレスから、WHOISを確認できるようにします。

function whoisUrl($ip)
{
    return 'https://www.whois.com/whois/' . rawurlencode($ip);
}

画面上のIPアドレスをクリックすると、そのIPについてWHOIS情報を確認できます。

20.ドメイン名を短く表示する

ログには完全なドメイン名を残しますが、監視画面では横幅を節約するため短く表示します。

例えば、

site1.example.com

を、

site1

のように表示します。

ただし、アクセス先のパスについては省略しません。

/wp-content/plugins/example/test.php

なら、そのまま表示します。

これは、どこにアクセスされたのかを確認することが重要だからです。

21.ステータスコードを色分けする

HTTPステータスコードは、先頭の数字によってクラスを変えます。

function statusClass($status)
{
    $code = (int)$status;

    if ($code >= 200 && $code < 300) {
        return 'status-2xx';
    }

    if ($code >= 300 && $code < 400) {
        return 'status-3xx';
    }

    if ($code >= 400 && $code < 500) {
        return 'status-4xx';
    }

    if ($code >= 500) {
        return 'status-5xx';
    }

    return '';
}

これで、200、404、500などを画面上で区別できます。

22.監視画面をダッシュボード化する

ここまでの情報を一つの画面にまとめます。

画面上部には、

Server Monitor
最終更新時刻
24時間グラフ
ログアウト

を表示します。

その下に、

今日のアクセス
要注意アクセス
404
BAN

を表示します。

さらにタブを用意します。

最新100件
要注意アクセス
ファイル変更
BAN履歴

これで、普段は最新アクセスを見ながら、必要なときだけ要注意アクセスやファイル変更を確認できます。

23.ファイル変更タブの表示

ファイル変更がなければ、通常状態として表示します。

変更があれば、タブに変更を示す表示を付けます。

一覧には、

日時
対象
変更
ファイル

を表示します。

変更内容は、

作成
変更
削除

の3種類です。

これで、アクセスログだけでなく、サーバー上のファイルに変化があったかどうかも同じ画面で確認できます。

24.5分ごとに自動更新する

監視ページなので、ブラウザを開いたままでも定期的に更新されるようにします。

head 内に、

<meta http-equiv="refresh" content="300">

を入れます。

300秒なので、5分ごとにページが更新されます。

JavaScriptを複雑に使ってAjax更新するのではなく、今回はシンプルなページリロード方式にしました。

25.選択中のタブを保持する

ページが5分ごとに更新されるため、そのままだと毎回「最新100件」に戻ってしまいます。

そこで、選択中のタブを localStorage に保存します。

function openTab(event, tabName) {

    document
        .querySelectorAll('.tab-content')
        .forEach(function(tab) {
            tab.style.display = 'none';
        });

    document
        .querySelectorAll('.tab-button')
        .forEach(function(button) {
            button.classList.remove('active');
        });

    document.getElementById(tabName).style.display = 'block';

    event.currentTarget.classList.add('active');

    localStorage.setItem(
        'monitorActiveTab',
        tabName
    );
}

ページを開いたときには、保存されていたタブを復元します。

これで、

要注意アクセス

を開いて監視していても、5分後の自動更新で別のタブに戻ることがなくなります。

26.レスポンシブ表示にする

監視ページはPCだけでなく、タブレットやスマートフォンからも見るため、画面幅が狭くなってもテーブルを確認できるようにします。

特にアクセスログは、

日時
IP
Code
ドメイン
アクセス先
Method

と項目が多いため、狭い画面では無理に文字を折り返さず、横方向にスクロールできる構成にします。

これでタブレットでもログの内容を確認できます。

27.監視ログをlogrotateでローテーションする

監視用ログはアクセスのたびに増えていくため、そのまま保存し続けることは避けます。

今回はLinuxの logrotate を利用します。

例えば、

monitor_access_log
monitor_access_log.1
monitor_access_log.2
monitor_access_log.3

のように世代管理します。

設定では、

weekly
rotate 4

とします。

weekly は1週間ごとにローテーションし、

rotate 4 は4世代を残す設定です。

そのため、おおむね4週間分の監視ログを保持できます。

監視PHPは現在の、

/var/log/httpd/monitor_access_log

を読むだけにして、古いログの保存と削除は logrotate に任せます。

役割を分けることで、PHP側を複雑にせずに済みます。

28.PHPの構文を確認する

最後にPHPの構文を確認します。

php -l /var/www/html/monitor/index.php

次のように表示されればOKです。

No syntax errors detected in /var/www/html/monitor/index.php

その状態でブラウザから、

/monitor/

を開きます。

ログイン画面が表示され、ログイン後に監視画面が表示されれば基本部分は完成です。

29.動作を確認する

アクセスログ

通常のページへアクセスして、

最新100件

に表示されることを確認します。

404

存在しないURLへアクセスして、

404

の件数が増えることを確認します。

要注意アクセス

例えば、

/.env
/wp-config.php
/test.php

などへのアクセスを発生させ、要注意アクセスに表示されることを確認します。

ファイル変更

テストファイルを作成、変更、削除して、

作成
変更
削除

が検出されることを確認します。

BAN

Fail2BanがBANした場合に、

BAN履歴

に表示されることを確認します。

自動更新

ページを開いたまま5分待ち、ページが自動更新されることを確認します。

また、更新前に「要注意アクセス」など別のタブを選択しておき、更新後もそのタブが維持されることを確認します。

30.完成したServer Monitor

最終的な構成は次のようになりました。

Apache
  │
  ├── 通常のアクセスログ
  │
  └── monitor_access_log
          │
          ▼
      Server Monitor
          │
          ├── 今日のアクセス
          ├── 要注意アクセス
          ├── 404
          ├── 24時間グラフ
          ├── 最新100件
          ├── ファイル変更
          └── BAN履歴
          
WordPress等
  │
  ▼
固定ベースライン
  │
  ▼
ファイル変更検出

Fail2Ban
  │
  ▼
fail2ban.log
  │
  ▼
BAN履歴

監視ページ自体はPHPで作り、Apacheの監視用ログを読み込みます。

ファイル変更については、あらかじめ作成したベースラインと現在のファイル状態を比較します。

Fail2Banについては既存のログを読み込み、BAN履歴を表示します。

ログの保存期間については、logrotate に任せます。

これで、

アクセスを見る
↓
気になるアクセスを確認する
↓
ファイル変更を確認する
↓
BAN状況を確認する

という一連の確認を、一つの監視ページでできるようになりました。

まとめ

今回は、Apacheの監視用ログを追加し、それをPHPで解析して、

  • アクセス数
  • 404
  • 要注意アクセス
  • 24時間グラフ
  • ファイル変更
  • Fail2BanのBAN履歴

を一つの画面にまとめました。

特別な監視ソフトを追加するのではなく、Apache、PHP、Fail2Ban、logrotateという既存の仕組みを組み合わせて、自分のサーバーに必要な部分だけを監視するページを作ったという構成です。

31.Fail2Banで不審なアクセスを自動BANする

ここまでで、アクセスログをPHPで監視できるようになりました。

ここからは、監視しているアクセスの一部をFail2Banで自動的にBANできるようにします。

今回使用するのは、前半で作った監視専用ログです。

/var/log/httpd/monitor_access_log

構成は、

Apache
  ↓
monitor_access_log
  ↓
Fail2Ban
  ↓
条件に該当したIPをBAN

とします。

今回作るJailは次の4種類です。

  • .envへのアクセス
  • /.git/configへのアクセス
  • xmlrpc.phpへのPOST
  • 存在しないPHPファイルを短時間に繰り返し探すアクセス

32..env用のFilterを作る

まず、.envを探しているアクセスを検出するFilterを作ります。

vi /etc/fail2ban/filter.d/monitor-env.conf

内容は次のようにします。

[Definition]

failregex = ^.*\s+\S+\s+<HOST>\s+GET\s+/(?:\.env.*|env[^/\s]*\.php)(?:\?.*)?\s+\d{3}$

作成したFilterが実際のログにマッチするか確認します。

fail2ban-regex /var/log/httpd/monitor_access_log /etc/fail2ban/filter.d/monitor-env.conf

実際の監視ログに対してマッチすることを確認します。

33..env用のJailを作る

次に、.env用のJailを作ります。

vi /etc/fail2ban/jail.d/monitor-env.local

内容は、

[monitor-env]

enabled  = true
filter   = monitor-env
logpath  = /var/log/httpd/monitor_access_log
maxretry = 1
findtime = 60
bantime  = 86400
action   = nftables[type=allports]

とします。

今回は、60秒以内に1回でも該当アクセスがあればBANします。

86400秒なので、BAN時間は24時間です。

34.Git用のFilterを作る

次に、公開されているGitリポジトリの設定ファイルを探すアクセスを対象にします。

vi /etc/fail2ban/filter.d/monitor-git.conf

内容は、

[Definition]

failregex = ^.*\s+\S+\s+<HOST>\s+GET\s+/\.git/config(?:\?.*)?\s+\d{3}$

とします。

Filterを確認します。

fail2ban-regex /var/log/httpd/monitor_access_log /etc/fail2ban/filter.d/monitor-git.conf

35.Git用のJailを作る

vi /etc/fail2ban/jail.d/monitor-git.local

内容は、

[monitor-git]

enabled  = true
filter   = monitor-git
logpath  = /var/log/httpd/monitor_access_log
maxretry = 1
findtime = 60
bantime  = 86400
action   = nftables[type=allports]

とします。

これで /.git/config へのアクセスを検出したIPを24時間BANします。

36.XML-RPC用のFilterを作る

WordPressの xmlrpc.php へのPOSTを検出します。

vi /etc/fail2ban/filter.d/monitor-xmlrpc.conf

内容は、

[Definition]

failregex = ^.*\s+\S+\s+<HOST>\s+POST\s+/xmlrpc\.php.*$

とします。

Filterを確認します。

fail2ban-regex /var/log/httpd/monitor_access_log /etc/fail2ban/filter.d/monitor-xmlrpc.conf

37.XML-RPC用のJailを作る

vi /etc/fail2ban/jail.d/monitor-xmlrpc.local

内容は、

[monitor-xmlrpc]

enabled  = true
filter   = monitor-xmlrpc
logpath  = /var/log/httpd/monitor_access_log
maxretry = 2
findtime = 60
bantime  = 86400
action   = nftables[type=allports]

とします。

今回は60秒間に2回、XML-RPCへのPOSTを検出した場合にBANします。

38.PHPスキャン用のFilterを作る

次に、存在しないPHPファイルを次々に探しているようなアクセスを検出します。

例えば、

/test.php
/old.php
/flamini.php

などへの404アクセスです。

Filterを作ります。

vi /etc/fail2ban/filter.d/monitor-php-scan.conf

内容は、

[Definition]

failregex = ^.*\s+\S+\s+<HOST>\s+GET\s+/.*\.php(?:/.*)?\s+404$

とします。

Filterを確認します。

fail2ban-regex /var/log/httpd/monitor_access_log /etc/fail2ban/filter.d/monitor-php-scan.conf

39.PHPスキャン用のJailを作る

vi /etc/fail2ban/jail.d/monitor-php-scan.local

内容は、

[monitor-php-scan]

enabled  = true
filter   = monitor-php-scan
logpath  = /var/log/httpd/monitor_access_log
maxretry = 5
findtime = 5
bantime  = 86400
action   = nftables[type=allports]

とします。

こちらは単発の404ではBANせず、

5秒間に5回

PHPファイルへの404アクセスが発生した場合にBANします。

40.4つのJailを確認する

ここまでで、次の4つのJailができました。

monitor-env
monitor-git
monitor-xmlrpc
monitor-php-scan

Filterは、

/etc/fail2ban/filter.d/

Jailは、

/etc/fail2ban/jail.d/

にあります。

全体としては、

monitor_access_log
       │
       ├── monitor-env
       ├── monitor-git
       ├── monitor-xmlrpc
       └── monitor-php-scan
              │
              ▼
          Fail2Ban
              │
              ▼
        nftablesでBAN

という構成です。

41.Fail2Banの設定を確認する

Jailを有効にする前に、設定全体をチェックします。

fail2ban-client -t

次のように表示されれば設定は正常です。

OK: configuration test is successful

問題がなければFail2Banをリロードします。

fail2ban-client reload

42.Jailが有効になったことを確認する

現在有効になっているJailを確認します。

fail2ban-client status

個別のJailについて確認する場合は、

fail2ban-client status monitor-env
fail2ban-client status monitor-git
fail2ban-client status monitor-xmlrpc
fail2ban-client status monitor-php-scan

とします。

ここで、

Currently failed
Currently banned
Total failed
Total banned

などを確認できます。

43.Fail2BanのログをServer Monitorから読めるようにする

Server Monitorでは、Fail2BanのBAN履歴も表示します。

そのためPHPから、

/var/log/fail2ban.log

を読み込めるようにします。

Apacheユーザーに読み取り権限を与えます。

setfacl -m u:apache:r /var/log/fail2ban.log

設定を確認します。

getfacl /var/log/fail2ban.log

Apacheユーザーから実際に読み込めるか確認します。

sudo -u apache head -3 /var/log/fail2ban.log

ログが表示されれば完了です。

これで、

Fail2Ban
   ↓
fail2ban.log
   ↓
Server Monitor
   ↓
BAN履歴

という流れになります。

44.BAN理由をServer Monitorに表示する

Server Monitor側では、Jail名と表示するBAN理由を対応させます。

$banReason = [
    'monitor-env'      => '.env',
    'monitor-git'      => 'Git',
    'monitor-xmlrpc'   => 'XML-RPC',
    'monitor-php-scan' => '連続スキャン'
];

これによって、BAN履歴では、

日時
IPアドレス
BAN理由

を確認できます。

IPアドレスは、前半で作ったWHOISリンクから確認できるようにしています。

45.BANが発生したことを確認する

Fail2BanのログからBAN履歴を確認します。

grep 'Ban ' /var/log/fail2ban.log

現在BANされているIPについては、例えば、

fail2ban-client status monitor-env

で確認できます。

nftables側でも状態を確認できます。

nft list ruleset

これで、Filterによる検出から実際のBANまで一通り確認できます。

46.最終的なFail2Ban設定

今回の設定は次のようになりました。

Jail対象条件BAN時間
monitor-env.envなど1回 / 60秒24時間
monitor-git/.git/config1回 / 60秒24時間
monitor-xmlrpcxmlrpc.php POST2回 / 60秒24時間
monitor-php-scanPHPファイル4045回 / 5秒24時間

すべて、

action = nftables[type=allports]

として、該当したIPを全ポート対象でBANする構成です。

47.Server Monitorまで含めた最終構成

これで、Server MonitorとFail2Banを含めた全体構成は次のようになります。

Apache
  │
  └── monitor_access_log
          │
          ├──────────────┐
          │              │
          ▼              ▼
   Server Monitor     Fail2Ban
          │              │
          │              ▼
          │          nftables
          │              │
          │              ▼
          │             BAN
          │
          ├── 最新アクセス
          ├── 要注意アクセス
          ├── 404
          ├── 24時間グラフ
          ├── ファイル変更
          └── BAN履歴
                           │
                           ▼
                    fail2ban.log

これで、アクセスを確認するだけでなく、特定のパターンについてはFail2Banが自動的にIPアドレスをBANし、その結果をServer Monitorから確認できるようになりました。

🗂  サーバー

↓

Getting Started

記事は右側のリストメニューから探せます。
初めての方は下のおすすめ記事からどうぞ。

Quote of the Day

CSSでできることはCSSで。

— ヨシオ
↑