そう感じているサーバー管理者は少なくありません。/etc/nginx/nginx.confや/etc/cron.d/配下のファイルは、手動作業やデプロイスクリプトで気づかないうちに書き換わることがあります。gitで管理していれば理想的ですが、全サーバーに導入するのは現実的ではない場合もあります。
この記事では、シェルスクリプトとdiffコマンドを組み合わせて設定ファイルの変更を自動検知する設計を解説します。スナップショット取得・差分比較・メール通知・cronでの定期監視まで、実務で使えるパターンを段階的に学べます。
実行環境はRHEL 9.4 / Ubuntu 24.04 LTSで動作確認済みです。
この記事のポイント
・diffの終了コード(0=差分なし・1=差分あり)がスクリプトの分岐判定に使える
・スナップショットをtarで保存し前回と比較するパターンが実務の定石
・変更を検知したらmailコマンドで差分をメール送信して即時通知できる
・cronで定期実行すれば設定ファイルの無断変更を自動監視できる
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
なぜシェルスクリプトで設定変更を検知するのか
Linuxサーバーの設定ファイルは、複数人が管理する環境では意図しない変更が紛れ込む危険があります。よくある場面はこちらです。・デプロイスクリプトが設定ファイルを上書きしてしまった
・別の担当者がテスト目的で一時変更し、戻し忘れた
・パッケージアップデートで設定ファイルが置き換わった
inotifywaitを使ったリアルタイム監視も有効ですが、監視デーモンを常駐させる必要があります。一方、スナップショット比較ならcronで定期的に差分を確認するだけでよく、常駐プロセスなしで軽量に運用できます。gitが導入できない環境でも使える点も実務で重宝します。
変更検知の仕組みは単純です:「今の状態」と「前回保存した状態」を比べる。差分があれば変更されたと判断します。
diffコマンドの終了コードを設計に活かす
diffコマンドは比較結果を終了コードで通知します。| 終了コード | 意味 |
|---|---|
0 |
差分なし(ファイルは同一) |
1 |
差分あり(ファイルが異なる) |
2 |
エラー(ファイルが読めない等) |
if ! diff -q /etc/nginx/nginx.conf /tmp/snapshot/nginx.conf >/dev/null 2>&1; then echo "変更を検知しました" fi
設定ファイルのスナップショット比較を設計する
1. スナップショットを取得して保存する
最初の実行時に「現在の正常な状態」をスナップショットとして保存します。以降の監視でこのスナップショットと比較します。#!/bin/bash set -euo pipefail SNAPSHOT_DIR="/var/lib/config-monitor/snapshots" MONITOR_FILES=( "/etc/nginx/nginx.conf" "/etc/ssh/sshd_config" "/etc/cron.d" "/etc/sudoers" ) mkdir -p "${SNAPSHOT_DIR}" for target in "${MONITOR_FILES[@]}"; do if [[ -f "$target" ]]; then # ファイル名のスラッシュをアンダースコアに置換してスナップショット名を作成 snapshot_name="${SNAPSHOT_DIR}/$(echo "${target}" | tr '/' '_')" cp -p "${target}" "${snapshot_name}" echo "スナップショット作成: ${target}" elif [[ -d "$target" ]]; then snapshot_name="${SNAPSHOT_DIR}/$(echo "${target}" | tr '/' '_').tar.gz" tar czf "${snapshot_name}" -C "$(dirname "${target}")" "$(basename "${target}")" echo "ディレクトリのスナップショット作成: ${target}" fi done
2. 現在の状態とスナップショットを比較する
スナップショットと現在のファイルをdiffで比較します。変更があればその差分を記録します。#!/bin/bash set -euo pipefail SNAPSHOT_DIR="/var/lib/config-monitor/snapshots" DIFF_LOG="/var/log/config-monitor/changes.log" MONITOR_FILES=( "/etc/nginx/nginx.conf" "/etc/ssh/sshd_config" "/etc/sudoers" ) mkdir -p "$(dirname "${DIFF_LOG}")" changed_files=() for target in "${MONITOR_FILES[@]}"; do snapshot_name="${SNAPSHOT_DIR}/$(echo "${target}" | tr '/' '_')" if [[ ! -f "$snapshot_name" ]]; then echo "[WARN] スナップショットが存在しません: ${target}" >&2 continue fi if ! diff -q "${target}" "${snapshot_name}" >/dev/null 2>&1; then echo "$(date '+%Y-%m-%d %H:%M:%S') [変更検知] ${target}" >> "${DIFF_LOG}" diff -u "${snapshot_name}" "${target}" >> "${DIFF_LOG}" 2>&1 || true changed_files+=("${target}") fi done if [[ ${#changed_files[@]} -gt 0 ]]; then echo "変更されたファイル: ${changed_files[*]}" exit 1 # 変更検知時はexit 1で呼び出し元に通知 else echo "変更なし" fi
3. 変更があった場合のみ後続処理を実行する
`exit 1`で変更を通知する設計にすると、cronジョブとの連携が簡単になります。# cronから呼び出す場合の例 # 変更があったときだけメール通知スクリプトを実行 /usr/local/bin/check-config.sh && echo "変更なし" || /usr/local/bin/notify-change.sh
変更検知時に通知する設計
1. mailコマンドで差分をメール送信する
変更を検知したら差分の内容をメールで通知します。cronジョブが無人実行されても気づけるようにします。#!/bin/bash set -euo pipefail SNAPSHOT_DIR="/var/lib/config-monitor/snapshots" NOTIFY_MAIL="admin@example.com" HOSTNAME_VAL=$(hostname -s) MONITOR_FILES=( "/etc/nginx/nginx.conf" "/etc/ssh/sshd_config" "/etc/sudoers" ) TMPFILE=$(mktemp /tmp/config-diff.XXXXXX) trap 'rm -f "${TMPFILE}"' EXIT changed_count=0 for target in "${MONITOR_FILES[@]}"; do snapshot_name="${SNAPSHOT_DIR}/$(echo "${target}" | tr '/' '_')" if [[ ! -f "$snapshot_name" ]]; then continue fi if ! diff -q "${target}" "${snapshot_name}" >/dev/null 2>&1; then echo "=== 変更検知: ${target} ===" >> "${TMPFILE}" diff -u "${snapshot_name}" "${target}" >> "${TMPFILE}" 2>&1 || true echo "" >> "${TMPFILE}" (( changed_count++ )) fi done if [[ $changed_count -gt 0 ]]; then mail -s "[要確認] ${HOSTNAME_VAL} で設定ファイルの変更を検知 (${changed_count}件)" \ "${NOTIFY_MAIL}" < "${TMPFILE}" echo "通知メール送信完了: ${changed_count}件の変更" exit 1 fi echo "変更なし"
2. loggerでsyslogに記録する
メール通知に加えてsyslogに記録しておくと、Zabbixなどの監視ツールがsyslogを読んでいれば自動的にアラートを上げられます。# 変更検知をsyslogに記録(facility: local0, priority: warning) logger -t config-monitor -p local0.warning "設定変更を検知: ${target}"
cronで定期的な変更監視を自動化する
監視スクリプトをcronに登録します。変更検知時にはMATOMEVAR(`MAILTO`)経由でメールが届くように設定します。# /etc/cron.d/config-monitor MAILTO=admin@example.com PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin # 毎時0分に設定変更を確認(変更ありでexit 1を返すとcronがメール送信) 0 * * * * root /usr/local/bin/check-config.sh
実際に検証機(RHEL 9.4)でこの監視スクリプトをテスト実行した例です。
# /etc/ssh/sshd_configを意図的に変更してテスト $ echo "# test change" >> /etc/ssh/sshd_config $ /usr/local/bin/check-config.sh 変更されたファイル: /etc/ssh/sshd_config # changesログで差分内容を確認 $ tail -20 /var/log/config-monitor/changes.log 2026-10-01 17:30:15 [変更検知] /etc/ssh/sshd_config --- /var/lib/config-monitor/snapshots/_etc_ssh_sshd_config 2026-10-01 09:00:00.000000000 +0900 +++ /etc/ssh/sshd_config 2026-10-01 17:30:12.000000000 +0900 @@ -129,3 +129,4 @@ # This is the sshd server system-wide configuration file. # See sshd_config(5) man page +# test change
スナップショットの定期更新設計
変更を確認して問題なければスナップショットを更新します。更新しないと次回以降も同じ変更を検知し続けます。スナップショットの更新を自動化する場合は、変更が「承認済み」かどうかを確認する仕組みが必要です。#!/bin/bash # スナップショットを現在の状態で更新する(承認後に実行) update_snapshot() { local target="$1" local snapshot_name="${SNAPSHOT_DIR}/$(echo "${target}" | tr '/' '_')" cp -p "${target}" "${snapshot_name}" echo "$(date '+%Y-%m-%d %H:%M:%S') [更新] スナップショットを更新: ${target}" \ >> /var/log/config-monitor/changes.log logger -t config-monitor -p local0.info "スナップショット更新: ${target}" } # 使用例(変更確認後に手動で実行) update_snapshot "/etc/ssh/sshd_config"
複数サーバーで設定整合性を確認する設計
複数台のサーバーで同じ設定ファイルが同じ内容かを確認したい場合は、SSHでファイルを取得してdiffします。#!/bin/bash set -euo pipefail MASTER_FILE="/etc/nginx/nginx.conf" SERVERS=("web01.example.com" "web02.example.com" "web03.example.com") SSH_USER="deploy" for server in "${SERVERS[@]}"; do remote_file=$(ssh -o StrictHostKeyChecking=no -o ConnectTimeout=10 \ "${SSH_USER}@${server}" "cat ${MASTER_FILE}" 2>/dev/null) if ! diff -q \ <(echo "$remote_file") \ "${MASTER_FILE}" >/dev/null 2>&1; then echo "[WARN] ${server}: ${MASTER_FILE} が基準と異なります" >&2 diff <(echo "$remote_file") "${MASTER_FILE}" || true else echo "[OK] ${server}: 整合性確認済み" fi done
トラブルシュート
1. 常に差分が出力されてアラートが止まらない
タイムスタンプや一時的なコメントが含まれる設定ファイルでは、内容は変わっていなくてもファイルの更新日時が変わりdiffが差分を検出することがあります。`diff -q`は内容の比較なのでタイムスタンプ問題は起きませんが、設定ファイル内に動的な値(バージョン番号・自動生成コメント)が含まれている場合は誤検知の原因になります。対処は2通りです。
・問題の行をdiffの`--ignore-matching-lines`で除外する
・動的な行を正規化した状態で比較する
# タイムスタンプ行を無視して比較(例: # Generated: 2026-10-01 という行を除外) diff -q \ <(grep -v '^# Generated:' "${snapshot}") \ <(grep -v '^# Generated:' "${target}") >/dev/null 2>&1
2. rootのみ読めるファイルの比較でPermission deniedになる
/etc/shadowや/etc/sudoersはrootのみ読み取り可能です。スクリプトをroot権限で実行するか、sudoersでスクリプトの実行を許可します。cronの`root`ユーザー行として登録すれば権限の問題は解消します。3. diffのexit 1が set -e で誤終了する
`set -euo pipefail`を使っている場合、`diff`が差分を検出してexit 1を返すとスクリプトが終了してしまいます。# NGパターン: set -e 下でdiffが差分を検出するとスクリプト終了 diff -u "${snapshot}" "${target}" # OKパターン: || true でexit 1を吸収する diff -u "${snapshot}" "${target}" >> "${DIFF_LOG}" || true # または条件式内なら set -e の影響を受けない if ! diff -q "${snapshot}" "${target}" >/dev/null 2>&1; then # 変更処理 fi
まとめ
| やりたいこと | 実装 |
|---|---|
| 変更の有無だけを確認 | diff -q ファイル1 ファイル2 >/dev/null 2>&1 |
| 差分内容をログに記録 | diff -u スナップショット 現在 >> ログ || true |
| 変更を検知したらメール通知 | mail -s "件名" 宛先 < 差分ファイル |
| syslogに記録 | logger -t タグ -p local0.warning "メッセージ" |
| 変更なしのcron実行は静か | exit 0+出力なしでMATOMEVARへの通知が抑制される |
| set -e 下でdiffを使う | diff ... || true または if文内で使う |
現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、設定変更検知・trapによる安全設計・バックアップ自動化まで、シェルスクリプトの実践的なノウハウをセミナーでお伝えしています。
3,100名以上が実践した「型」を無料で公開中
プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら
- 前のページへ:bashの組み込みTCPソケット(/dev/tcp)でポート疎通確認とHTTP監視を実装するシェルスクリプト設計
- この記事の属するカテゴリ:シェルスクリプトへ戻る

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