2026/06/27(土)nginxでGeoIP2とUserAgentを利用したアクセス制御を実装してみた
過去に何度かBOTなどを避けるためのアクセス制御を導入していたが、結果どれも微妙だった。しかしどうにかしたい、そうだ、nginxは高機能なのでできるんじゃないか?と思って実践してみたログ。
User-AgentはGEOIP2と何も関係ないが、ついでにやったので書いている。
確認環境
| Env | Ver(apt上) | 備考 |
|---|---|---|
| nginx | 1.24.0-2ubuntu7.13 | nginx -vではnginx/1.24.0 (Ubuntu) |
| nginx-common | 1.24.0-2ubuntu7.13 | |
| libnginx-mod-stream | 1.24.0-2ubuntu7.13 | |
| libnginx-mod-http-geoip2 | 1:3.4-5build2 |
やったこと
互換性確保のためnginxのダウングレード
インストールされていたnginxが1.26.1-2~jammyで、libnginx-mod-http-geoip2との互換性がなかったためダウングレードすることにした。
まず次のコマンドで整合性を確認した。コマンド自体はGPT-5.5が作ってくれたものであるが、こうすることでダウングレードを許可した上での互換性チェックができるようだ。
コマンドの実行結果としては既に追加されているコンポーネント、ダウングレードされるコンポーネント、新たに追加されるコンポーネントの一覧が出てくる。
# ngx_http_geoip2_module.soが存在しないことを確認。既にある場合は本項の工程は飛ばせると思う
ls -la /usr/lib/nginx/modules
sudo apt install -s --allow-downgrades \
nginx=1.24.0-2ubuntu7.13 \
nginx-common=1.24.0-2ubuntu7.13 \
libnginx-mod-stream=1.24.0-2ubuntu7.13 \
libnginx-mod-http-geoip2
上記の構成でインストールできることが分かったので-sを外してnginx本体をダウングレードし、他をインストールする。
sudo apt install --allow-downgrades \
nginx=1.24.0-2ubuntu7.13 \
nginx-common=1.24.0-2ubuntu7.13 \
libnginx-mod-stream=1.24.0-2ubuntu7.13 \
libnginx-mod-http-geoip2
# ngx_http_geoip2_module.soが増えていることを確認
ls -la /usr/lib/nginx/modules
ngx_http_geoip2_module.so
GEOIP2データベースの入手と配置
MMDBなら何でも行けると思うので、Matomoで使ってる無料の奴を使う。理想的にはCRONで定期取得するのがいいと思うが、面倒なので今回はやってない。
sudo mkdir -p /usr/share/GeoIP
sudo wget https://download.db-ip.com/free/dbip-country-lite-2026-06.mmdb.gz -O /usr/share/GeoIP/dbip-country-lite.gz
gunzip /usr/share/GeoIP/dbip-country-lite.gz
nginxの設定でGEOIPの有効化を行う
/etc/nginx/nginx.confに以下の設定を追記すると、$geoip2_で始まる変数にMMDBの中身が入るようになる。
load_module modules/ngx_http_geoip2_module.so;
http {
...
geoip2 /usr/share/GeoIP/dbip-country-lite {
auto_reload 24h;
$geoip2_metadata_country_build metadata build_epoch;
$geoip2_country_code country iso_code;
$geoip2_country_name country names en;
}
...
nginxのJSONログに国コードを含める
JSONログにキーを追加してやればよい。
http {
...
log_format main_json escape=json
'{'
...
'"country_code":"$geoip2_country_code"'
'}';
国コードとUserAgentの判定フィルタの元ネタを作る
nginxのmap構文を使って判定用の元ネタを作る。
左側は~で始めると正規表現扱いになり、~*とすると大文字小文字の違いを無視してくれるようになるらしい。
http {
# headlesschromeを2にしてるのは、これはちょっと毛色が違うと思ったため、便宜上分類分けしている。
# 但し現時点でこの値を特別扱いしておらず、1と等価なので無駄ではある。
map $http_user_agent $deny_ua {
default 0;
"~*meta-externalagent" 1;
"~*baidu" 1;
"~*headlesschrome" 2;
"" 1;
}
map $geoip2_country_code $deny_ca {
default 0;
"CN" 1;
"BR" 1;
"IN" 1;
}
# nginxのif文にはandやorに相当する演算子がないのでmapで合成しておく
map "$deny_ua:$deny_ca" $deny_client {
default 1;
"0:0" 0;
}
....
}
雰囲気で読んでいるが構文の意味合いはこうだと思う。KeyValueで並べていくと入力値($input_variable)のKey(input_value)に対するValue(output_value)が出力値($output_variable)に入るのだと思う。
map $input_variable $output_variable {
default <default_value>; # デフォルト値
input_value output_value;
...
}
フィルタ用のsnippetsを作る
vhostに撒くように/etc/nginx/snippets/deny_client.confのようなsnippetsを作る。
前述のmapで判定をまとめているのでif文一つで制御できるようになっている。
if ($deny_client = 1) {
return 403;
}
snippetsをvhostに撒く
反映したいvhostの設定にsnippetsを撒いていく。
server {
...
include snippets/deny_client.conf;
...
動作検証
こんな感じで検証して弾かれてれることを確認した。国コードはどうにもならないが原理上問題ないはずなので大丈夫だろう。
curl -H "User-Agent:meta-externalagent/1.1" https://lycolia.info
あとは念のために国コードが出ているかどうかをnginxのログを見て出ていたらOK。
関連記事
- 自宅サーバーの一部がダウンしたが監視を入れていてよかった話
- Facebook(Meta)のBOTのせいでサーバーが落ちた話
- Anubisを雑に設置したが、結果微妙だったので撤去した話
- Anubisで防げないか試してみたが、Anubisのストレージや、アクセス解析との親和性などの関係でなかったことにしたやつ
- このブログにやってくる海外IPのBOTの挙動を軽く調べた
- 取り敢えずどんな迷惑アクセスがあるか調べたときの奴
- CGIのラッパーCGIを作る方法
- この記事ではadiaryに対するアクセス制御のラッパーを書いていた
あとがき
今回のアクセス制御の導入にあたり、運用面で課題のあるAnubisを回避しつつ、大量に存在するCGIにラッパーCGIを嚙ますなどの対応を回避できたのはよかった。
nginxは何とも高性能だ。そして自宅サーバーに全部乗せしたおかげで、レンタルサーバーでは決して手が届かないことができるのが良いと思った。
しかしググってもGEOIP無印の情報ばかりで、GEOIP2の情報が全然なかったので地味にハマってしまった。こういう時LLMに情報を漁ってもらうと取っ掛かりが得られて突破口を見つけられたりするので便利だと感じる。
パッケージを見ていて気になったこと
パッケージを見ていて気になったことは元のコードは保守されてないにもかかわらず、ディストリごとにコードが保守されているように見えたことだ。
今回導入したのはlibnginx-mod-http-geoip2だが、これは元を辿ればDebianのlibnginx-mod-http-geoip2のようで、更に元を辿るとleev/ngx_http_geoip2_module: Nginx GeoIP2 moduleに行きつく。
何故ならDebianのリポジトリがGitHubより新しく、READMEの中身が同じだからだ。設定方法もまるっきり同じに見える。
GitHubのリポジトリは2年以上放置されており「Support nginx 1.23.0」で時が止まっているが、Ubuntuでは「Rebuild against new nginx 1.30.1.」とあるため、だいぶ新しいところまでサポートが進んでいる。
よく考えるとパッケージのバージョンに-ubuntuみたいなのがついているのも良くあることなので、もしかしたらディストリごとにこういったアプリケーションを保守していたりするのだろうか?
そういえばPHPなんかも本家がサポート切れててもUbuntuではサポート内とか聞いたことがある。動作サポートなのかセキュリティサポートなのか、何のサポート課なのかまでは知らないが、星の数ほどあるパッケージをディストリごとに保守しているとしたら、これは凄いことだなと思ったし、他人が書いた得体のしれないパッケージをどう保守しているのかも気になった。
Linuxの世界は興味深い…。
nginxのダウングレードからのバージョンの復旧について
ダウングレードしても基本的に影響はないと思うが、元のバージョンに上げようとするとUbuntuそのもののバージョンアップが必要なことを知った。
しかし24.04.04 LTSから26.04への安全なアップグレードはまだ提供されていないようなのでいったん断念した。26.04.01が出ると上げられるようになるらしく、現状はお試し版みたいな感じらしい。
よくあるx.00は安定板だけど不具合がある、x.01で真の安定板になるみたいな話はUbuntuにもあるんだなと思った。
2026/06/16(火)WSL2の中からPowershellでOSへ通知トーストを投げる
PowerShellはWSL2の中から叩けるため、WSL2の中から通知を出したいときに使える。
PowerShellなのでホスト側のWindowsからも使える。
確認環境
PowerShell 7では動かないので注意。
| Env | Ver |
|---|---|
| OS | Windows 11 Pro 25H2 (OSビルド 26200.8655) |
| PowerShell | 5.1.26100.8655 |
やり方
この内容を
toast.ps1など適当な名前で保存する。文字コードはUTF-8 BOM, 改行コードはLFにしておくparam ( [String]$subject = "WSL Notice", [String]$title = "たいとるですー", [String]$message = "お知らせでーす", [String]$icon = "C:/env/1e1b9fdf82a6a544.png", [switch]$keep ) [Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] > $null $template = [Windows.UI.Notifications.ToastNotificationManager]::GetTemplateContent([Windows.UI.Notifications.ToastTemplateType]::ToastImageAndText04) $xml = New-Object Windows.Data.Xml.Dom.XmlDocument $xml.LoadXml($template.GetXml()) $toastTextElements = $xml.GetElementsByTagName("text") $toastTextElements.Item(0).AppendChild($xml.CreateTextNode($title)) > $null $toastTextElements.Item(1).AppendChild($xml.CreateTextNode($message)) > $null $toastImageElements = $xml.GetElementsByTagName("image") $toastImageElements.Item(0).SetAttribute("src", $icon) > $null if ($keep) { $xml.DocumentElement.SetAttribute("scenario", "reminder") > $null $actions = $xml.CreateElement("actions") $action = $xml.CreateElement("action") $action.SetAttribute("content", "閉じる") > $null $action.SetAttribute("arguments", "dismiss") > $null $action.SetAttribute("activationType", "background") > $null $actions.AppendChild($action) > $null $xml.DocumentElement.AppendChild($actions) > $null } $toast = [Windows.UI.Notifications.ToastNotification]::new($xml) $appId = $subject $notifier = [Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier($appId) $notifier.Show($toast)- 次の書式で叩くと通知トーストが出る
powershell.exe -File 'C:/path/to/toast.ps1' -subject hoge -title fuga -message piyo -icon C:/path/to/icon.png -keep
このスクリプトのオプション
| オプション | 役割 |
|---|---|
-subject |
通知の表題。ここを変えると別の通知として扱われる |
-title |
通知のタイトル |
-message |
メッセージ本文。一行のみ |
-icon |
アイコン。Windowsホストの絶対パスで指定 |
-keep |
このフラグを立てると通知が消えなくなる |
-subjectを変えると別の通知扱いになるため、通知を別々に貯めることができるようになる。
Windows側から叩く方法
powershell 'C:/path/to/toast.ps1' -subject hoge -title fuga -message piyo -icon C:/path/to/icon.png -keep
Windows側から叩く場合、-Fileは省略できる。
参考情報
- ToastTemplateType 列挙型 (Windows.UI.Notifications) - Windows apps | Microsoft Learn
- ここの内容とググった情報からXMLの構造をエスパーして実装している
あとがき
Claude Codeの確認と終了のイベントで通知が出ると便利かなと思って作ってみたが、まだ試せていない。
取りあえずWSL2の中と、Windowsホスト側から叩いたときに通知が出ることは確認している。
PowerShell 7で動かそうと思ったらエラーまみれで動かず、PowerShell 7の場合、NuGetパッケージを取得するか、DLLのパスを直に指定するか、サードパーティの通知ライブラリを使うかということで、諦めた。
2026/06/07(日)シェルの設定にRPS1を入れると微妙だったのでやめた話
投稿日:
私はzshをシェルとして使っており、かれこれ5年ほどプロンプトにPROMPTとRPS1を設定していたのだが、使っていてRPS1が非常に邪魔だったのでPROMPTに統一することにした。
邪魔だったもの
これまで私は画像のようにRPS1に日時と異常終了コードを出すようにしていたのだが、複数行選択したときに必然的に巻き込んでしまう問題があった。
これはコマンドの実行ログをブログに書く時や、複数コマンドをコピペするときにRPS1の部分を除去する必要があり、手間だった。
そこでRPS1を使うのをやめてPROMPTに統一することにした。行は増えたが、こっちのほうが視線移動もなくコピペ時の巻き込み問題も減るので楽になった気がする。
他にもRPS1はターミナルの幅が狭くなったり、一部のターミナルから開くと崩れやすかったので、今回の対応でそういったこともまとめて解消されてよかった。
これは余談だがPS1でなくPROMPTを使っている理由はPS1だとRPS1を使っているときに表示が崩れる環境があったが、PROMPTだとこれが起きなかったからだ。何故かはよくわかってない。
2026/06/05(金)Ubuntuのadiaryをlibfcgi-perlで動かす方法
サイト環境を移転したのでadiaryのパフォーマンス計測をやってみたの環境構築した時のログ。
確認環境
| Env | Ver |
|---|---|
| Ubuntu | 24.04.4 LTS |
| nginx | 1.26.1 |
| adiary | 3.52dev / Extends 0.25.0 |
| libfcgi-perl | 0.82+ds-3build2 |
前提
nginxとperlがインストール済
手順
- libfcgi-perlをインストールする
sudo apt install libfcgi-perl /etc/systemd/system/adiary.serviceを次のように作成し、adiaryのFastCGIデーモンを作る[Unit] Description=adiary daemon After=network.target [Service] Type=simple User=www-data Group=www-data WorkingDirectory=/var/www/path/to ExecStart=/usr/bin/perl /var/www/path/to/adiary.fcgi /var/www/path/to/adiary.sock 10 100 Restart=always [Install] WantedBy=multi-user.target- サービスを有効化する
sudo systemctl enable adiary.service sudo systemctl start adiary.service nginxの設定を書く
server { listen 443 ssl; listen [::]:443 ssl; server_name blog.example.com; client_max_body_size 100M; ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem; root /var/www/path/to/blog; location / { try_files $uri @adiary; } location /__cache { deny all; } location /data { deny all; } location ~ \.cgi$ { deny all; } location @adiary { include fastcgi_params; fastcgi_pass unix:/var/www/path/to/adiary.sock; fastcgi_param Basepath /; } }- nginxを再起動する
sudo systemctl restart nginx
あとがき
adiary.httpd.plがオススメらしいのは見たが、HTTPサーバーは脆弱になりやすいことや、ログの使い勝手がどうなのか確認するのが面倒なこと、IPv6対応させるのが面倒だったことがあり採用しなかった。





