「アクセスログをクラウドの分析サービスに送りたいが、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解析する全体構成
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
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
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%で、主要なページは正常に配信されています。
エラー多発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}件")
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"';
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)
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
よくある問題と対処法
スクリプトを実行してうまくいかないときに確認する点をまとめます。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 POST | stream: 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ハンズオン形式で実施しています。
・Ubuntu ServerでローカルLLMを構築する方法|Ollamaで機密データを外に出さず業務AIを動かす完全ガイド
・社内でChatGPTが使えないときの代替手段|機密データを守るローカルLLMという選択肢
・ローカルLLMのモデルを比較する方法|Llama3.3・Mistral・Gemma・Phi-4をUbuntuで使い分けるポイント
3,100名以上が実践した「型」を無料で公開中
プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら
- 前のページへ:OllamaのAPIでマルチターン会話を実装する方法|Pythonで会話履歴を管理してフレームワークなしでチャットAPIを本番品質に仕上げる手順
- この記事の属するカテゴリ:ローカルLLMへ戻る

無料メルマガで学習を続ける
Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。
登録無料・いつでも解除できます