.
以前の記事
(1) f16: xserver vps + ubuntu + 海外アクセス抑止 + 自動更新
https://qiita.com/cxfgp/items/ff18545adb3c1dc1bec2
(2) f23: ubuntu + 海外アクセス抑止 + 自動更新(2) ufw 更新版
https://qiita.com/cxfgp/items/37029713088114f22a73
の派生メモです
上記 自動更新(2) ufw 更新版 の設定から少しして、
再起動時にエラーが多発するようになりました
[FAILED] Failed to start Wait for Network to be Configured.
See 'systemctl status systemd-networkd-wait-online.service' for details.
「まれに適用時エラーになることがあります」という記載を
いたしましたが、この多発は、そこの記載で想定していた
「何かの処理時のエラー」ということではなく、
国内許可IPリストの読み込みに時間がかかりすぎて
タイムアウトエラーが頻発するようになりました
(そのため、再起動時に ssh 接続が不可になります)
ということで、
・systemd-networkd-wait-online を無効化
・systemd-networkd-wait-online.service Timeout値を延長
・UFW の遅延ロード (起動完了後にルール適用)
・cloud-init の待機解除 (VPSの場合不要のようで意味なし)
など、いくつか苦肉も含めた対応策はありますが、
本来、起動時に CIDR (約数千〜数万行)を
iptables にロードする処理は推奨されていないようで、
そもそもの根底を変えなければあまり改善にならないため、
・ufw backend + ipset
(ipset: IPリストをハッシュテーブルで管理)
という手法に切り替えました
(結論としては、現時点ではこの方法一択です)
[ 00. メニュー ]
01. キッチン
02. 概要
03. 環境設定
04. スクリプト(1) drop_except_jp.sh
05. スクリプト(2) check_ufw.sh
06. 自動更新 (cron) 設定
07. ipset 適用確認
08. デザート
09. 参考
[ 01. キッチン ]
・Xserver VPS / 2GB プラン
-> Ubuntu 22.04 (64bit)
$ which ufw
/usr/sbin/ufw
$ which iptables
/usr/sbin/iptables
$ which ipset
/usr/sbin/ipset
$ systemctl is-enabled ufw
-> enabled
$ systemctl is-enabled ipset-restore-jp.service
-> enabled
※ ufw / iptables はよほどないと思いますが、
ipset は、docker などを利用されていないと
入っていないケースがありますので、その際は以下で入れてください
$ sudo apt update
$ sudo apt install ipset
[ 02. 概要 ]
自動更新(2) ufw 更新版 からの変更点
============================
[1] drop_except_jp.sh にて、
・cidr.txt の入れ替え
・ufw (after.init にルール適用-> iptables 更新)
「A. ポートの許可」
「B. 例外の許可」
の部分はそのままに、以下を追加
・cidr.txt の ipset への仕込み (save to file)
============================
[2]「C. 国内許可IP設定」 の部分のみ、
drop_except_jp.sh から切り離し
サービス ipset (ipset-restore-jp.service) を作成し、
再起動後のサービス ipset での設定に切り替えます
============================
処理の流れ
============================
[1] (例: 03:00am) cron -> drop_except_jp.sh
1. cidr.txt を DL(JP 行を含む元データ)
2. ufw after.init にルール適用
A. ポートの許可 (国外でも許可するポート)
B. 例外の許可 (固定 IP 許可)
3. cidr.txt から JP IP を取得して ipset にセットし、
再起動後のリストア用にファイルに保存
============================
[2] (例: 03:10am) cron -> OS reboot
(ipset 適用の流れ)
systemd 起動
└ network-pre.target
└ network 起動
└ network-online.target
└ ipset-restore-jp.service 起動
・ExecStart 実行
└ (存在すれば) ipset destroy JP-CIDR
└ ipset restore < /etc/ipset/jp-cidr.restore
(ファイル -> RAM 上の ipset へ JP-CIDR(IPリスト)を復元)
└ ufw.service 起動
└ before.rules 読み込み
(-A ufw-before-input -m set --match-set JP-CIDR src -j ACCEPT)
└ user.rules 読み込み
└ after.rules 読み込み
└ iptables-restore
└ firewall(iptables) ルールを RAM にロード
└ JP-CIDR ルールの有効化
-> iptables で ipset 参照可能となる
============================
[3] OS 起動後の処理(@reboot) -> check_ufw.sh
# ufw ステータスチェック
systemctl status ufw
# ipset-restore-jp.service ステータスチェック
systemctl status ipset-restore-jp.service
# iptables (JP-CIDR) 設定チェック
iptables -S ufw-before-input | grep JP-CIDR
============================
[ 03. 環境設定 ]
============================
(01) ディレクトリ構成
(スクリプトの実行デイレクトリ例 -> /home/aaa/)
/home/aaa/
└─ drop_except_jp.sh ( スクリプト1 )
└─ check_ufw.sh ( スクリプト2 )
└─ cidr.txt.gz ( cidr ダウンロード )
└─ cidr.txt ( cidr 展開 )
/home/aaa/logs/
└─ reboot.log ( リブートログ )
└─ exjp.log ( スクリプト処理ログ )
└─ ufws.log ( ufw ステータス確認ログ )
└─ ipse.log ( ipset サービス スターテス確認ログ )
/home/aaa/iptables/
└─ backup.日付 ( 念の為の iptables backup )
/etc/ufw/
└─ after.init.drop_except_jp ( after.init 設定ファイル )
└─ before.rules ( before.rules 設定ファイル )
/etc/systemd/system/
└─ ipset-restore-jp.service ( ipset リストアサービス )
/etc/ipset/
└─ jp-cidr.restore ( ipset restore ファイル )
============================
(02) 構築順序、設定項目
'---------------------------------------------
1. ipset restore 用ファイル作成
[ 作成 ]
# mkdir -p /etc/ipset
# vi /etc/ipset/jp-cidr.restore
create JP-CIDR hash:net family inet hashsize 32768 maxelem 200000
'---------------------------------------------
2. systemd サービス (ipset-restore-jp.service) 作成
[ 作成 ]
# vi /etc/systemd/system/ipset-restore-jp.service
[Unit]
Description=Restore-JP-CIDR-ipset
After=network.target
Before=ufw.service
[Service]
Type=oneshot
ExecStart=/bin/sh -c '/sbin/ipset list JP-CIDR >/dev/null 2>&1 && /sbin/ipset destroy JP-CIDR; /sbin/ipset restore < /etc/ipset/jp-cidr.restore'
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
# chown root:root /etc/systemd/system/ipset-restore-jp.service
# chmod 644 /etc/systemd/system/ipset-restore-jp.service
[ service 有効化 ]
# systemctl daemon-reload
# systemctl enable ipset-restore-jp.service
[ 確認 ]
# systemctl is-enabled ipset-restore-jp.service
-> enabled
'---------------------------------------------
3. after.init の追記
(自動更新(2) ufw 更新版と同じ設定です
A. ポートの許可、B. 例外の許可 用の設定です)
/etc/ufw/after.init の start に以下追記します
( /etc/ufw/after.init.drop_except_jp )
[ 追記 ]
# vi /etc/ufw/after.init
start)
# typically required
/etc/ufw/after.init.drop_except_jp
;;
以下を実施し、after.init と after.init.drop_except_jp を
実行可能状態にしておきます
# chmod +x /etc/ufw/after.init
# touch /etc/ufw/after.init.drop_except_jp
# chmod +x /etc/ufw/after.init.drop_except_jp
'---------------------------------------------
4. before.rules の追記
/etc/ufw/before.rules に 以下を追記します
( -A ufw-before-input -m set --match-set JP-CIDR src -j ACCEPT )
-> JP-CIDR に含まれるIP(=日本IP)からの通信を許可するルール
[ 追記 ]
# vi /etc/ufw/before.rules
-A ufw-before-input -m set --match-set JP-CIDR src -j ACCEPT
追記箇所のポイントは「ufw-before-input」かつ
「RELATED,ESTABLISHED」の場所です
(以下 例1 と 例2 の間)
例1: -A ufw-before-input -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
例2: -A ufw-before-output -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
例3: -A ufw-before-forward -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
'---------------------------------------------
5. OS reboot
上記 1. - 4. の設定が完了しましたら、OS 空リブートを行っても
エラーが発生することなく OS 起動されるようになります
( 3. after.init, 4. before.rules の追記の設定を
有効にするために、OS reboot ( または ufw reload ) が
必要になりますが、この段階で一度 OS reboot するか、
cron での OS reboot でも、どちらのタイミングでも大丈夫です )
'---------------------------------------------
============================
※ 以降 スクリプト設定、自動更新設定 に続きます
[ 04. スクリプト(1) drop_except_jp.sh ]
( chmod 604 drop_except_jp.sh )
#!/bin/bash
# set script dir (例 /home/aaa/)
s_dir=$(cd $(dirname $0) && pwd)
log1=${s_dir}/logs/exjp.log
# JP-CIDR の作業用ファイルを設定 (.tmp)
IPSET_RESTORE="/etc/ipset/jp-cidr.restore"
TMP_RESTORE="${IPSET_RESTORE}.tmp"
# xt_multiport は、複数ポートを一括指定する際に必要
# -> 現段階では単一で80や443を指定してるので無くても動作する
/sbin/modprobe xt_multiport 2>/dev/null || true
#----------------------------------------
# バックアップ ( iptables backup )
#----------------------------------------
iptables-save > /home/aaa/iptables/backup.$(date +%F)
AFTER_INIT=/etc/ufw/after.init.drop_except_jp
cid1=${s_dir}/cidr.txt.gz
cid2=${s_dir}/cidr.txt
cid2tmp=${cid2}.tmp
#----------------------------------------
# 古い CIDR ファイル削除
#----------------------------------------
for fe2 in "$cid1" "$cid2"; do
if [ -f "$fe2" ]; then
rm -rf "$fe2"
sleep 1s; dates1=$(date +"%Y/%m/%d %T")
echo "$dates1 : dr1: delete OK: ${fe2##*/}" | tee -a "$log1" > /dev/null
else
sleep 1s; dates1=$(date +"%Y/%m/%d %T")
echo "$dates1 : dr1: delete NG: not found ${fe2##*/}" | tee -a "$log1" > /dev/null
fi
done
#----------------------------------------
# 新しいCIDRファイル (cidr.txt.gz) 取得
#----------------------------------------
wget -q -N http://nami.jp/ipv4bycc/cidr.txt.gz -O "$cid1"
wget_status=$?
if [ $wget_status -eq 0 ] && [ -f "$cid1" ]; then
sleep 2s; dates2=$(date +"%Y/%m/%d %T")
echo "$dates2 : dr1: dl: OK: cidr.txt.gz" | tee -a "$log1" > /dev/null
else
sleep 2s; dates2=$(date +"%Y/%m/%d %T")
echo "$dates2 : dr1: dl: NG: wget failed ($wget_status)" | tee -a "$log1" > /dev/null
exit 1
fi
#----------------------------------------
# gunzipして cidr.txt.gz を解凍
#----------------------------------------
if gunzip -f -c "$cid1" > "$cid2tmp"; then
mv "$cid2tmp" "$cid2"
sleep 2s; dates3=$(date +"%Y/%m/%d %T")
echo "$dates3 : dr1: OK: to gunzip cidr.txt.gz" | tee -a "$log1" > /dev/null
else
rm -rf "$cid2tmp"
sleep 2s; dates3=$(date +"%Y/%m/%d %T")
echo "$dates3 : dr1: NG: failed to gunzip cidr.txt.gz" | tee -a "$log1" > /dev/null
exit 1
fi
if [ -s "$cid2" ]; then
# ufw/after.init (after.init.drop_except_jp) に書き出し (A. と B. )
{
echo "#!/bin/sh"
echo "# generate by drop_except_jp.sh on $(date +"%Y/%m/%d %T")"
echo ""
# ===== A. 国外でも許可するポート =====
# 国外でも許可するポート (80,443)
echo "ufw allow 80/tcp"
echo "ufw allow 443/tcp"
echo ""
# ===== B. 固定IP許可 =====
FIXED_IPS=(
# 例 google
"35.190.247.0/24" "35.191.0.0/16" "64.233.160.0/19" "66.102.0.0/20"
)
for ip2 in "${FIXED_IPS[@]}"; do
echo "ufw allow from $ip2"
done
} > "$AFTER_INIT"
chmod +x "$AFTER_INIT"
dates4=$(date +"%Y/%m/%d %T")
echo "$dates4 : dr1: OK: ufw after.init file updated" | tee -a "$log1" > /dev/null
# ===== C. 国内 CIDR を ipset にロードし、リストア用ファイルに書き出し =====
NEW_SET_JP="JP-CIDR-NEW"
OLD_SET_JP="JP-CIDR"
if ! ipset list "$OLD_SET_JP" >/dev/null 2>&1; then
ipset create "$OLD_SET_JP" hash:net family inet hashsize 32768 maxelem 200000
fi
ipset destroy "$NEW_SET_JP" 2>/dev/null
ipset create "$NEW_SET_JP" hash:net family inet hashsize 32768 maxelem 200000
# cidr.txt から JP 行のみ抽出して ipset に追加
# 国内IP許可
while read -r cid3; do
[ -z "$cid3" ] && continue
# 重複 IP は無視して skip
ipset add "$NEW_SET_JP" "$cid3" -exist
done < <(sed -n 's/^JP[[:space:]]\+//p' "$cid2")
# 入れ替え
ipset swap "$NEW_SET_JP" "$OLD_SET_JP"
# 書き出し
ipset save "$OLD_SET_JP" > "$TMP_RESTORE" || exit 1
mv "$TMP_RESTORE" "$IPSET_RESTORE"
chmod 644 "$IPSET_RESTORE"
echo "$dates4 : dr1: OK: cidr to ipset is ready" | tee -a "$log1" > /dev/null
# 不要になった側を削除
ipset destroy "$NEW_SET_JP"
else
dates4=$(date +"%Y/%m/%d %T")
echo "$dates4 : dr1: NG: not found cidr.txt or not a file" | tee -a "$log1" > /dev/null
exit 1
fi
[ 05. スクリプト(2) check_ufw.sh ]
( chmod 604 check_ufw.sh )
#!/bin/bash
# set script dir (例 /home/aaa/)
s_dir=$(cd $(dirname $0) && pwd)
log1=${s_dir}/logs/exjp.log
log2=${s_dir}/logs/ufws.log
log6=${s_dir}/logs/ipse.log
#----------------------------------------
# ufw ステータスチェック
#----------------------------------------
sleep 20s; systemctl status ufw -l > "$log2"
sleep 2s; dates3=`date +"%Y/%m/%d %T"`
# for check ufw status date
dates4=`date +"%Y-%m-%d"`
# ufw active check
if grep -q "active (exited)" "$log2"; then
comp4=`grep "Active:" $log2 | { IFS= read -r lines1 && trimmed_lines1=$(echo "$lines1" | awk '{$1=$1};1'); echo "$trimmed_lines1"; }`
echo "$dates3 : ck1: $comp4" | tee -a $log1 > /dev/null
rid_date2=$(echo "$comp4" | awk '{print $6}')
if [ "$rid_date2" = "$dates4" ]; then
echo "$dates3 : ck1: OK: ufw status: active (exited) + $rid_date2" | tee -a $log1 > /dev/null
else
echo "$dates3 : ck1: NG: ufw status: active (exited) + date NOT-FOUND: $dates4" | tee -a $log1 > /dev/null
exit 1
fi
else
grep "Active:" $log2 | { IFS= read -r lines1 && trimmed_lines1=$(echo "$lines1" | awk '{$1=$1};1'); echo "$dates3 : ck1: NG: NOT-FOUND ufw active: $trimmed_lines1"; } | tee -a $log1 > /dev/null
exit 1
fi
#----------------------------------------
# ipset-restore-jp.service ステータスチェック
#----------------------------------------
sleep 10s; systemctl status ipset-restore-jp.service -l > "$log6"
sleep 2s; dates5=`date +"%Y/%m/%d %T"`
# for check ipset status date
dates6=`date +"%Y-%m-%d"`
# ipset active check
if grep -q "active (exited)" "$log6"; then
comp6=`grep "Active:" $log6 | { IFS= read -r lines2 && trimmed_lines2=$(echo "$lines2" | awk '{$2=$2};1'); echo "$trimmed_lines2"; }`
echo "$dates5 : ck1: $comp6" | tee -a $log1 > /dev/null
rid_date4=$(echo "$comp6" | awk '{print $6}')
if [ "$rid_date4" = "$dates6" ]; then
echo "$dates5 : ck1: OK: ipset status: active (exited) + $rid_date4" | tee -a $log1 > /dev/null
else
echo "$dates5 : ck1: NG: ipset status: active (exited) + date NOT-FOUND: $dates6" | tee -a $log1 > /dev/null
exit 1
fi
else
grep "Active:" $log6 | { IFS= read -r lines2 && trimmed_lines2=$(echo "$lines2" | awk '{$2=$2};1'); echo "$dates5 : ck1: NG: NOT-FOUND ipset active: $trimmed_lines2"; } | tee -a $log1 > /dev/null
exit 1
fi
#----------------------------------------
# iptables (JP-CIDR) 設定チェック
#----------------------------------------
sleep 2s; dates7=$(date +"%Y/%m/%d %T")
echo "$dates7 : ck1: check set JP-CIDR from before.rules to iptables" | tee -a $log1 > /dev/null
iptables -S ufw-before-input | grep JP-CIDR | tee -a $log1 > /dev/null
[ 06. 自動更新 (cron) 設定 ]
cron (root user) で、以下の 3つの設定を行います
[ 設定 ]
# crontab -u root -e
# a. 例. 火曜 03:00am update cidr
00 3 * * 2 bash /home/aaa/drop_except_jp.sh
# b. 例. 火曜 03:10am os reboot
10 3 * * 2 /bin/echo "$(date '+\%Y-\%m-\%d \%H:\%M:\%S') - reboot started" >> /home/aaa/logs/reboot.log 2>&1; /usr/sbin/shutdown -r now
# c. after os reboot
@reboot /bin/echo "$(date '+\%Y-\%m-\%d \%H:\%M:\%S') - done reboot as run @reboot" >> /home/aaa/logs/reboot.log 2>&1 && bash /home/aaa/check_ufw.sh
[ 確認 ]
# crontab -l
( よくいわれます、crontab -r の削除オプションと
間違えないようご注意ください )
※ この 自動更新設定 にて、環境構築は完了です
あとは、cron の実行を待ちます
(補足)
# b. 例. 火曜 03:10am os reboot
# c. after os reboot
において、
cron で、エスケープシーケンスを用いて
%Y-%m-%d %H:%M:%S を使用している関係でログに
\2026-\05-\01 \21:\39:\04 - reboot started
2026-05-01 21:39:24 - done reboot as run @reboot
のように、日付の前に \ が出力される場合があり、
これを、printf に変更して回避してみたりしましたが、
どうも不安定で、処理が実行されなかったりしましたので、
現状は、\2026-\05-\01 \21:\39:\04 のように
出力される場合もありますが、echo にしてます
[ 07. ipset 適用確認 ]
上記 [ 06. 自動更新 (cron) 設定 ] までの設定で、
ufw backend + ipset が動作する環境となり、
cron 実行より、最終的に OS再起動 で、
ipset にロード(リストア)され機能する流れですが、
「設定は有効」で「海外IP抑止ができているのか」
の確認方法をいくつか記載します
(本来は実際の日本IP、海外IPからのアクセスで、
許可/不許可のテストを行えば確実ですが、
その環境の用意が難しいケースもありますので、以下の確認を行ないます)
==============================================
(1) ipset が正しくロードされているか確認 [ 設定確認 ]
'---------------------------------------------
1. JP-CIDR の中身が存在するか確認します
# ipset list JP-CIDR | head
[結果例]
Name: JP-CIDR
Type: hash:net
Revision: 7
Header: family inet hashsize 32768 maxelem 200000 bucketsize 12 initval 0x44a452b1
Size in memory: 145680
References: 1
Number of entries: 3174
Members:
123.456.789.0/24
12.345.67.0/24
※ head をつけることで、上部のみ出力されます
'---------------------------------------------
2. JP-CIDR の登録件数確認(Members以降のIP件数を表示)
# ipset list JP-CIDR | sed '1,/Members:/d' | wc -l
[結果例]
3174 (3174件のIP登録があり)
'---------------------------------------------
==============================================
(2) iptables が ipset を参照しているかの確認 [ 設定確認 ]
# iptables -S ufw-before-input | grep JP-CIDR
[結果例]
-A ufw-before-input -m set --match-set JP-CIDR src -j ACCEPT
-> ufw → iptables → ipset の接続が成立している
==============================================
(3) 実際にパケットがヒットしているか [ 実トラフィック確認 ]
# iptables -L ufw-before-input -n -v | grep JP-CIDR
[結果例]
12345 987K ACCEPT all -- * * 0.0.0.0/0 match-set JP-CIDR src
[項目] [意味]
12345 -> ヒット回数
987K -> 通過バイト
ACCEPT -> 許可されている
match-set JP-CIDR src -> JPのIPにヒット
-> 数値が増えていれば 実際に JP IP の通信がヒットしている
==============================================
(4) 海外IPが拒否されているか確認 [ ログ確認 ]
# dmesg | grep UFW
(結構な件数が表示される可能性がありますのでご注意)
[結果例]
[UFW BLOCK] IN=eth0 SRC=45.xx.xx.xx
JP以外のIPが BLOCK されていれば成功
(=海外IPが実際に遮断されている)
[念のため確認 (必要の場合) ]
$ whois 45.xx.xx.xx
(sudo apt install whois 後)
==============================================
(5) 上記 (3) JPヒット + (4) ブロック の件数表示 [ 実トラフィック + ログ確認 ]
# echo "JP HIT:" $(iptables -L ufw-before-input -n -v | grep JP-CIDR | awk '{print $1}') " / BLOCK:" $(dmesg | grep -c "UFW BLOCK")
[結果例]
JP HIT: 73201 / BLOCK: 1543
==============================================
[ 08. デザート ]
これで、OS 再起動時、30分以上かかってタイムアウトしてた状態から
数分で設定完了 ( + SSH 接続 OK ) の状態になりました
2026-04-07 03:10:01 - reboot started
2026-04-07 03:11:12 - done reboot as run @reboot
2026-04-21 03:10:01 - reboot started
2026-04-21 03:11:00 - done reboot as run @reboot
2026-05-03 20:14:02 - reboot started
2026-05-03 20:16:26 - done reboot as run @reboot
[ 09. 参考させていただいた記事 ]
.