OllamaでApache/NginxアクセスログをAI解析する方法|不審アクセスの自動検出と日次レポート生成をLinuxサーバーで実現する手順

運営:リナックスマスター.JP(代表 宮崎智広)
HOME > Linux技術 リナックスマスター.JP(Linuxマスター.JP) > ローカルLLM > OllamaでApache/NginxアクセスログをAI解析する方法|不審アクセスの自動検出と日次レポート生成をLinuxサーバーで実現する手順
「Webサーバーのアクセスログが毎日数万行に達していて、不審なIPや攻撃の兆候を手作業で確認するのが追いつかない」
「アクセスログをクラウドの分析サービスに送りたいが、IPアドレスなど個人情報が含まれるため、社内セキュリティポリシーに抵触する」

そんな悩みを抱えるLinuxサーバー管理者は多いはずです。この記事では、OllamaのローカルLLMを使ってApacheやNginxのアクセスログをサーバーだけで自動解析する手順を解説します。
Pythonでログを集計してOllama REST APIに渡し、不審なIPやエラーパターンを日本語レポートとして生成するところから、cronで日次自動通知するところまでを順を追って説明します。ログが外部に出ない設計なので、プライバシーへの配慮が必要な環境でも問題なく運用できます。

この記事のポイント

・ローカルLLMで解析するためアクセスログが外部サービスに送出されない
・PythonのreモジュールでCombined Logを集計し、Ollama REST APIで日本語レポートを生成する
・Apache Combined LogとNginxデフォルトログの両方に同じスクリプトで対応できる
・cronとmailコマンドで日次の自動通知まで完成させる


OllamaでApache/NginxアクセスログをAI解析する方法|不審アクセスの自動検出と日次レポート生成をLinuxサーバーで実現する手順

「このままじゃマズい」と感じていませんか?
参考書を開く気力もない、同年代に取り残される不安——
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

OllamaでApache/NginxアクセスログをAI解析する全体構成

Webサーバーのアクセスログには、正常なページビューだけでなく、攻撃者の探索行動を示すパターンも記録されています。
存在しないパスへの繰り返しアクセス、/admin/や/.envへのプローブ、短時間に集中する大量のPOSTリクエストなどです。

OllamaのローカルLLMを組み合わせると、集計した数値をプロンプトに渡すだけで「404エラーが急増したIPが3件あります。うち2件は/wp-admin/やphpMyAdminへのアクセスを繰り返しており、スキャン行動の可能性があります」のような日本語レポートが得られます。ログ解析の流れは以下の3段階です。

1. ログを読み込んで集計する

Pythonスクリプトがアクセスログを1行ずつ解析し、ステータスコード別件数・IPアドレス別アクセス数・URL別リクエスト数などを集計します。
生の全行をそのままLLMに渡すのではなく、まず数値に変換することでトークン数を抑えます。大量のログファイルでも処理速度が落ちにくくなります。

2. 集計結果をプロンプトに変換してOllama REST APIに渡す

集計した数値を構造化したプロンプトテキストに整形し、Ollama REST APIにPOSTします。
モデルはLlama3.3やMistralなど、使用環境のVRAMに合わせて選択します。モデル選択の判断基準についてはローカルLLMのモデルを比較する方法が参考になります。

3. 解析結果をメールで受け取る

Ollamaが返した日本語テキストをmailコマンドで管理者宛てに送信します。cronで毎朝実行すれば、ログを開かなくても注目すべき傾向が日次メールとして届きます。

Ollamaをまだ導入していない場合は、Ubuntu ServerでローカルLLMを構築する方法で環境を整えてから本記事の手順に進んでください。

解析スクリプトの事前準備

スクリプトを実行する前に、Ollamaサービスとアクセスログへのアクセス権限を確認しておきます。
手順どおりに進めるために、まず状態を整えます。

1. Ollamaの起動とモデルを確認する

Ollamaサービスが動作しているかを確認します。

# systemctl is-active ollama active # ollama ps NAME ID SIZE PROCESSOR UNTIL llama3.3:70b-instruct-q4_0 2a01b6f8c3d9 43 GB 100% GPU 4 minutes from now

サービスが停止している場合は `systemctl start ollama` で起動します。モデルがリストに表示されていない場合は `ollama pull llama3.3:70b-instruct-q4_0` で取得してください。
VRAMが少ない環境では `mistral:7b-instruct-q4_0` や `phi-4:14b-q4_0` でも動作します。

2. アクセスログの場所を確認する

ログのパスはディストリビューションとWebサーバーの種類によって異なります。

# Ubuntu/Debian系(Apache) # ls -lh /var/log/apache2/access.log -rw-r--r-- 1 root adm 128M Nov 14 06:25 /var/log/apache2/access.log # RHEL/AlmaLinux系(Apache) # ls -lh /var/log/httpd/access_log -rw-r--r-- 1 root root 95M Nov 14 06:25 /var/log/httpd/access_log # Nginx(多くのディストリビューション共通) # ls -lh /var/log/nginx/access.log -rw-r--r-- 1 www-data adm 86M Nov 14 06:25 /var/log/nginx/access.log

ログファイルがrootやadmグループしか読めない設定の場合は、後述のcron設定でrootとして実行するか、adm グループにスクリプト実行ユーザーを追加します。

3. 作業用ディレクトリを用意する

スクリプトの配置場所を作成します。

# mkdir -p /opt/log-analyzer # ls /opt/log-analyzer (空のディレクトリ)

Apacheアクセスログを集計してOllamaに解析させる基本スクリプト

ここでは2つのPythonファイルを作成します。設定をまとめた `config.py` と、ログを集計してOllamaに渡す `analyze_access_log.py` です。外部ライブラリは不要で、標準ライブラリだけで動作します。

1. 設定ファイルを作る

LOG_FILEはサーバー環境に合わせて変更してください。

# /opt/log-analyzer/config.py # 解析対象のアクセスログ(環境に合わせて変更する) LOG_FILE = "/var/log/apache2/access.log" # OllamaのエンドポイントとモデルID OLLAMA_URL = "http://localhost:11434/api/generate" MODEL = "llama3.3:70b-instruct-q4_0" # レポートの送信先メールアドレス REPORT_EMAIL = "admin@example.com" REPORT_SUBJECT = "日次アクセスログ解析レポート"

2. ログ集計とOllama連携のメインスクリプトを作る

Apache Combined Log形式(Nginxのデフォルトもこのフォーマットのためそのまま使えます)を解析するスクリプトです。

# /opt/log-analyzer/analyze_access_log.py import re import json import datetime import subprocess from collections import Counter import urllib.request import config # Apache Combined Log / Nginxデフォルトログの正規表現 LOG_PATTERN = re.compile( r'(?P<ip>\S+) \S+ \S+ \[(?P<time>[^\]]+)\] ' r'"(?P<method>\S+) (?P<path>\S+) [^"]*" ' r'(?P<status>\d+) (?P<size>\S+)' ) # 管理画面・設定ファイルへのアクセス検出キーワード SUSPICIOUS_PATHS = [ "/admin", "/.env", "/wp-admin", "/phpMyAdmin", "/config", "/.git", "/backup", "/.htaccess" ] def parse_log(log_file): ip_counter = Counter() status_counter = Counter() path_counter = Counter() error_ips = Counter() suspicious_counter = Counter() total = 0 with open(log_file, encoding="utf-8", errors="replace") as f: for line in f: m = LOG_PATTERN.match(line) if not m: continue total += 1 ip = m.group("ip") status = m.group("status") path = m.group("path").split("?")[0] # クエリ文字列を除外する ip_counter[ip] += 1 status_counter[status] += 1 path_counter[path] += 1 if int(status) >= 400: error_ips[ip] += 1 for suspicious in SUSPICIOUS_PATHS: if path.startswith(suspicious): suspicious_counter[ip] += 1 return total, ip_counter, status_counter, path_counter, error_ips, suspicious_counter def build_prompt(total, ip_counter, status_counter, path_counter, error_ips, suspicious_counter): today = datetime.date.today().strftime("%Y年%m月%d日") # エラーを5件以上発生させているIPを抽出する high_error_ips = [(ip, cnt) for ip, cnt in error_ips.most_common(10) if cnt >= 5] prompt = f"""{today}のWebサーバーアクセスログ集計結果を以下に示します。 セキュリティ上の問題や注目すべき傾向があれば日本語で指摘してください。 ## 総リクエスト数 {total:,}件 ## ステータスコード別件数 """ for code, count in sorted(status_counter.items()): prompt += f" HTTP {code}: {count:,}件\n" prompt += "\n## アクセス数上位10件のIPアドレス\n" for ip, count in ip_counter.most_common(10): prompt += f" {ip}: {count:,}回\n" prompt += "\n## アクセス数上位10件のURL\n" for path, count in path_counter.most_common(10): prompt += f" {path}: {count:,}回\n" if high_error_ips: prompt += "\n## エラー(4xx/5xx)を多発させているIPアドレス\n" for ip, count in high_error_ips: prompt += f" {ip}: エラー{count}件\n" if suspicious_counter: prompt += "\n## 管理画面・設定ファイルへのアクセスがあったIPアドレス\n" for ip, count in suspicious_counter.most_common(5): prompt += f" {ip}: {count}回\n" return prompt def ask_ollama(prompt): payload = json.dumps({ "model": config.MODEL, "prompt": prompt, "stream": False }).encode("utf-8") req = urllib.request.Request( config.OLLAMA_URL, data=payload, headers={"Content-Type": "application/json"} ) with urllib.request.urlopen(req, timeout=120) as res: result = json.loads(res.read().decode("utf-8")) return result["response"] if __name__ == "__main__": total, ip_counter, status_counter, path_counter, error_ips, suspicious = \ parse_log(config.LOG_FILE) prompt = build_prompt(total, ip_counter, status_counter, path_counter, error_ips, suspicious) report = ask_ollama(prompt) print(report)

3. スクリプトを実行して出力を確認する

実行権限を設定してから動作を確認します。

# chmod +x /opt/log-analyzer/analyze_access_log.py # python3 /opt/log-analyzer/analyze_access_log.py 2026年11月14日のWebサーバーアクセスログを分析しました。 【注目すべき点】 1. エラー件数の増加 HTTP 404エラーが2,847件(全体の8.4%)と多めです。 IPアドレス 198.51.100.42 が404エラーを318件発生させています。 /wp-admin/、/phpMyAdmin/、/.envへのアクセスが確認されており、 脆弱なパスを探索するスキャン行動の可能性があります。 2. 管理画面へのアクセス 198.51.100.42 から管理系パスへのアクセスが23回ありました。 このIPからのアクセスをファイアウォールでブロックすることを検討してください。 3. 正常な傾向 HTTP 200レスポンスは全体の87.3%で、主要なページは正常に配信されています。

LLMの出力はモデルによって文体や詳細度が変わります。試しながら、業務に合ったモデルを選んでください。

エラー多発IPと管理画面プローブで不審アクセスの検出精度を上げる

基本スクリプトをそのまま使っても不審アクセスの傾向はつかめますが、集計の粒度を上げるとLLMへのプロンプトがより具体的になり、指摘の精度も上がります。

1. エラー発生IPを時間帯別に集計する

短時間に集中した大量のエラーは、自動化されたスキャンツールの特徴です。タイムスタンプを解析してエラーの時間分布を追加できます。

# parse_log 関数に時間帯別カウンターを追加する例 from collections import defaultdict # 時間帯別エラーカウンター hourly_errors = defaultdict(int) # タイムスタンプから時刻を取り出す正規表現 # 例: "14/Nov/2026:03:22:41 +0900" -> "03" TIME_RE = re.compile(r'\d+/\w+/\d+:(\d{2}):') with open(log_file, encoding="utf-8", errors="replace") as f: for line in f: m = LOG_PATTERN.match(line) if m and int(m.group("status")) >= 400: tm = TIME_RE.search(m.group("time")) if tm: hourly_errors[tm.group(1)] += 1 # エラーが多い時間帯上位5件を出力する for hour, count in sorted(hourly_errors.items(), key=lambda x: -x[1])[:5]: print(f"{hour}時台: {count}件")

特定の時間帯にエラーが集中している場合は、自動スキャンツールによるアクセスの可能性があります。プロンプトにこの情報を追加すると、LLMがより具体的な対処方針を提示します。

2. ログローテーション後の前日分ログを解析する

本番サーバーでは日次でlogrotateが実行されることが多く、当日のアクセスログは途中までしかない場合があります。前日の圧縮ログを解析対象にすると1日分の完全な集計になります。

# 前日の圧縮ログを読む場合は gzip を展開して渡す import gzip def open_log(path): """拡張子が.gzの場合は gzip として、それ以外は通常のファイルとして開く""" if path.endswith(".gz"): return gzip.open(path, "rt", encoding="utf-8", errors="replace") return open(path, encoding="utf-8", errors="replace") # 前日のローテーション済みログのパス例 # Ubuntu/Apache: /var/log/apache2/access.log.1 # または圧縮形式: /var/log/apache2/access.log.1.gz

3. プロンプトをセキュリティ検出に特化させる

汎用的な解析プロンプトに加えて、セキュリティ観点に絞ったプロンプトを追加すると、攻撃の兆候をより明確に指摘させることができます。build_prompt関数のpromptの末尾に次の一文を追加します。

「特に攻撃の兆候(SQLインジェクション、パス探索、ブルートフォース)に該当するパターンがあれば、IPアドレスと該当URLを明記して報告してください。」

NginxアクセスログへのAI解析スクリプト対応

NginxのデフォルトログフォーマットはApache Combined Log Formatと同じ構造なので、前述のスクリプトはほぼそのまま使えます。

1. デフォルトログが同じ形式であることを確認する

Nginxの `/etc/nginx/nginx.conf` にはデフォルトで以下のlog_format設定が含まれています。

# /etc/nginx/nginx.conf(抜粋) # grep -A 4 "log_format" /etc/nginx/nginx.conf log_format combined '$remote_addr - $remote_user [$time_local] ' '"$request" $status $body_bytes_sent ' '"$http_referer" "$http_user_agent"';

フィールドの順序がApache Combined Log Formatと同じため、前述のLOG_PATTERNがそのまま機能します。config.pyのLOG_FILEを `/var/log/nginx/access.log` に変更するだけで解析できます。

2. カスタムlog_formatに変更している場合の対応

カスタムフォーマットを使っている場合は、LOG_PATTERNの正規表現をそのフォーマットに合わせて変更します。設定の確認には `nginx -T | grep -A 3 log_format` を実行してください。

情シス部門など社内全体でローカルLLMの活用を検討している場合の判断材料については、社内でChatGPTが使えないときの代替手段も参考にしてください。

解析結果をメールで自動通知してcronで日次実行する

スクリプトの動作を確認できたら、cronで自動実行してメール通知する仕組みを整えます。

1. mailコマンドでレポートをメール送信する設定を追加する

analyze_access_log.pyのメイン処理部分を次のように修正します。

# analyze_access_log.py の末尾(if __name__ == "__main__": ブロック)を修正する if __name__ == "__main__": total, ip_counter, status_counter, path_counter, error_ips, suspicious = \ parse_log(config.LOG_FILE) prompt = build_prompt(total, ip_counter, status_counter, path_counter, error_ips, suspicious) report = ask_ollama(prompt) # mailコマンドでレポートを送信する today = datetime.date.today().strftime("%Y-%m-%d") subject = f"{today} {config.REPORT_SUBJECT}" proc = subprocess.run( ["mail", "-s", subject, config.REPORT_EMAIL], input=report, text=True, timeout=30 ) if proc.returncode != 0: print("メール送信に失敗しました", flush=True) else: print(f"レポートを {config.REPORT_EMAIL} に送信しました", flush=True)

mailコマンドが入っていない場合は `dnf install mailx` または `apt install mailutils` でインストールします。メールサーバーの設定が必要な場合はpostfixなどのMTAを別途設定してください。

2. cronに登録して日次で自動実行する

毎朝8時に実行してサーバー管理者がメールで確認できるように設定します。

# crontab -e で以下を追加する(root権限でログを読む場合は sudo crontab -e) # MAILTO=admin@example.com で cron 自体のエラーもメール通知される MAILTO=admin@example.com 0 8 * * * /usr/bin/python3 /opt/log-analyzer/analyze_access_log.py >> /var/log/log-analyzer.log 2>&1

登録できたかは `crontab -l` で確認します。初回は手動でスクリプトを実行してメールが届くことを確認してからcronに登録するのが確実です。

よくある問題と対処法

スクリプトを実行してうまくいかないときに確認する点をまとめます。

1. OllamaのAPIが応答しない

「Connection refused」や「urlopen error」が表示された場合は、Ollamaサービスの状態を確認します。

# サービスの状態を確認する # systemctl status ollama # 起動していない場合は起動する # systemctl start ollama # API疎通確認 # curl -s http://localhost:11434/api/tags | python3 -m json.tool | head -5 { "models": [ { "name": "llama3.3:70b-instruct-q4_0",

2. ログファイルの読み取り権限エラー

Apacheのアクセスログはrootやadmグループしか読めない設定になっているサーバーがあります。cronジョブをrootで実行するか、スクリプト実行ユーザーをadmグループに追加します。

# adm グループにユーザーを追加する(Ubuntu系) # usermod -aG adm <実行ユーザー名> # 変更を反映するには再ログインが必要 # groups <実行ユーザー名> ubuntu adm dialout cdrom sudo ...

3. ログが大きすぎてOllamaがタイムアウトする

1日のアクセスが数百万件に達するサーバーでは、集計処理自体に時間がかかることがあります。
その場合はプロンプトに渡す上位IPやURLの件数をmost_common(10)から most_common(5)に減らします。またask_ollama関数のtimeout値を120から240に増やすと解消されるケースがあります。

Ollamaのパフォーマンスそのものを最適化したい場合は、`num_parallel`や`num_gpu`の設定値を見直すことで改善できるケースがあります。

まとめ

OllamaのローカルLLMとPythonスクリプトを組み合わせることで、Apache/NginxのアクセスログをサーバーだけでAI解析できます。ログが外部に出ない設計なので、個人情報や機密ログを扱う環境でも安心して運用できます。
手順操作・設定ポイント
ログ集計parse_log関数でCombined Logを解析reモジュールの正規表現でIPとステータスを取得
Ollama連携urllib.request.urlopen → REST API POSTstream: Falseで完全な応答を受け取る
不審アクセス検出error_ips・suspicious_counterの集計エラー5件以上のIPをプロンプトに追加
Nginx対応config.pyのLOG_FILEのパスを変更デフォルトログ形式はApacheと同一
日次自動化crontab + mailコマンドMAILTO設定でcron自体のエラーも通知

ローカルLLMによるログ解析を2日間のハンズオンで実践する

この記事で解説した手法を含め、OllamaによるRAG構築・API連携・業務自動化の実践的なノウハウを2日間で習得できます。実機GPU環境で手を動かしながら習得したい方向けに、「ローカルAIマスターセミナー」を開催しています。
少人数(最大8名)ZOOMハンズオン形式で実施しています。

>> ローカルAIマスターセミナーの詳細を確認する
ローカルLLMの構築・運用に関する関連記事もあわせて参考にしてください。

・Ubuntu ServerでローカルLLMを構築する方法|Ollamaで機密データを外に出さず業務AIを動かす完全ガイド
・社内でChatGPTが使えないときの代替手段|機密データを守るローカルLLMという選択肢
・ローカルLLMのモデルを比較する方法|Llama3.3・Mistral・Gemma・Phi-4をUbuntuで使い分けるポイント

無料メルマガで学習を続ける

Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。

登録無料・いつでも解除できます

暗記不要・1時間後にはサーバーが動く

3,100名以上が実践した「型」を無料で公開中

プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。

姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

Linux無料マニュアル(図解60P) 名前とメールで30秒登録

運営

リナックスマスター.JP(代表 宮崎智広)

株式会社イーネットマーキュリーが運営するLinux教育サイトです。代表の宮崎智広は現役のLinuxサーバー管理者として20年以上の実務経験を持ち、累計3,100名以上のエンジニアを指導してきました。実践型のLinuxセミナーや無料マニュアルの配布を通じてLinux人材の育成に取り組んでいます。