年末年始のサーバー停止を防ぐ自動バックアップスクリプト自作ガイド|tar・rsyncとcronによる世代管理・ディスク枯渇防止

スクリプト設計実務レシピ

年末年始や夏季休暇、ゴールデンウィークなどの大型連休が近づくと、インフラエンジニアやシステム管理者、社内情シスの頭を悩ませるのが「無人運用中のサーバー停止事故」です。

連休中のサーバー運用で最も恐ろしいシナリオは2つあります。1つは、ハードウェア障害やランサムウェア感染、誤操作によってデータが消失した際に「バックアップが数ヶ月前から失敗していたことに誰も気付いていなかった」というバックアップ形骸化の悲劇。もう1つは、「バックアップファイルがディスク内に無制限に増殖し続け、元日の未明にディスク使用率100%(Disk Full)でWebサーバーやデータベースが巻き込まれ停止した」という本末転倒のサーバー停止事故です。

こんにちは、Bash玄(ばっしゅげん)です。当サイト「Bash道」のモットーは「手作業は負け」「スクリプトはシンプルに」。正月休みや旅行中に「サーバーが落ちました!」と緊急電話で呼び出される悪夢は、絶対に避けたいですよね。

本記事では、Linuxの標準ツールである tar、rsync、cron、find を組み合わせ、「完全自動化・世代管理ローテーション・容量枯渇防止ガード・Slack異常通知」を1本に集約した実戦用バックアップスクリプトを完全公開します。商用バックアップツールの導入や月額コストをかけず、コピペとわずかな環境変数設定だけで堅牢なバックアップ体制を構築できます。

休暇前にスクリプトを仕込み、安心して年末年始を迎えましょう!

年末年始や大型連休を無人運用で乗り切るには、バックアップ自動化と並んでログ監視の自動化が不可欠です。長期休暇前のサーバー停止を防ぐ!logrotate総点検とディスク監視自動化スクリプト と合わせて設定し、サーバー停止リスクを全方位で遮断しましょう。

—

## 【即実戦投入】世代管理付き自動バックアップスクリプト完成版コード

まずは「今すぐ動く完成品が欲しい」という方に向けて、ファーストビュー直下にそのまま使える完成版シェルスクリプトを提示します。

### スクリプトの全体像と主要な設定変数一覧

このスクリプトは、以下の処理をワンストップで自律実行します:

1. 二重起動防止: flock による排他制御で、前回の処理が長引いた場合の重複実行を防止
2. 容量事前チェック: df コマンドで空き容量を確認し、使用率が閾値(デフォルト85%)を超えていたら処理を中断
3. アーカイブ作成: tar で不要キャッシュを除外しながら高速gzip圧縮
4. リモート転送: rsync で別サーバーやバックアップ専用ストレージへ安全に同期転送
5. 世代管理ローテーション: find コマンドで指定日数(デフォルト7世代)を超えた古いバックアップを自動消去
6. 障害通知: 途中でエラーが発生した場合、trap が即座に捕捉してSlackへアラートを発報

スクリプト冒頭で設定する主要変数は以下の通りです。

| 変数名 | デフォルト値 / 設定例 | 説明 |
| :— | :— | :— |
| `BACKUP_SRC` | `/var/www/html /etc/nginx` | バックアップ対象ディレクトリ(半角スペース区切りで複数指定可) |
| `BACKUP_DEST_LOCAL` | `/var/backups/system` | ローカルのバックアップアーカイブ一時保存先 |
| `REMOTE_USER` | `backupuser` | リモートバックアップサーバーのSSHユーザー名 |
| `REMOTE_HOST` | `backup.example.com` | リモートバックアップサーバーのホスト名またはIPアドレス |
| `REMOTE_DIR` | `/data/backups/web01` | リモートサーバー側の保存先ディレクトリ |
| `RETENTION_DAYS` | `7` | バックアップの保持世代数(保持日数。これを超えたアーカイブを自動削除) |
| `DISK_THRESHOLD` | `85` | バックアップ実行を許可するディスク使用率の上限(%) |
| `SLACK_WEBHOOK_URL` | `https://hooks.slack.com/…` | バックアップ失敗時に警告を送信するIncoming Webhook URL |

### コピペ用シェルスクリプト(backup_rotation.sh)全文

以下のスクリプトを /usr/local/bin/backup_rotation.sh として保存してください。

“`bash
#!/usr/bin/env bash
# ==============================================================================
# 年末年始・無人運用対応 世代管理付き自動バックアップスクリプト
# ==============================================================================
set -euo pipefail

# ——————————————————————————
# 1. 運用設定パラメータ(環境に合わせて変更してください)
# ——————————————————————————
BACKUP_NAME=”server_backup”
BACKUP_SRC=(
“/var/www”
“/etc/nginx”
“/etc/systemd/system”
)
BACKUP_DEST_LOCAL=”/var/backups/daily”
REMOTE_SYNC_ENABLE=true
REMOTE_USER=”backupuser”
REMOTE_HOST=”192.168.11.100″
REMOTE_PORT=”22″
REMOTE_DIR=”/mnt/backup_storage/web_server”

RETENTION_DAYS=7
DISK_THRESHOLD=85
TARGET_MOUNT=”/”
SLACK_WEBHOOK_URL=”https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX”

TIMESTAMP=”$(date ‘+%Y%m%d_%H%M%S’)”
ARCHIVE_FILENAME=”${BACKUP_NAME}_${TIMESTAMP}.tar.gz”
LOCAL_ARCHIVE_PATH=”${BACKUP_DEST_LOCAL}/${ARCHIVE_FILENAME}”
LOG_FILE=”/var/log/backup_rotation.log”
LOCK_FILE=”/var/run/backup_rotation.lock”

# ——————————————————————————
# 2. ユーティリティ関数(ログ記録・Slack通知・クリーンアップ)
# ——————————————————————————
log() {
local level=”$1″
shift
echo “[$(date ‘+%Y-%m-%d %H:%M:%S’)] [${level}] $*” | tee -a “${LOG_FILE}”
}

send_slack_alert() {
local error_msg=”$1″
local hostname
hostname=”$(hostname)”

if [[ -z “${SLACK_WEBHOOK_URL}” || “${SLACK_WEBHOOK_URL}” =~ XXXXX ]]; then
log “WARN” “Slack Webhook URLが未設定のため、通知をスキップします。”
return 0
fi

local payload
payload=$(cat < /dev/null 2>&1 || true
}

cleanup() {
local exit_code=$?
if (( exit_code != 0 )); then
log “ERROR” “スクリプトが異常終了しました(Exit Code: ${exit_code})。”
if [[ -f “${LOCAL_ARCHIVE_PATH}” ]]; then
log “INFO” “不完全なバックアップアーカイブを破棄します: ${LOCAL_ARCHIVE_PATH}”
rm -f “${LOCAL_ARCHIVE_PATH}”
fi
send_slack_alert “異常終了コード: ${exit_code}。ログ: ${LOG_FILE} を確認してください。”
fi
rm -f “${LOCK_FILE}”
}

trap cleanup EXIT

# ——————————————————————————
# 3. 事前安全点検(多重起動チェック・空き容量判定)
# ——————————————————————————
# 排他制御: 二重起動を防止
exec 200>”${LOCK_FILE}”
if ! flock -n 200; then
log “WARN” “前回のバックアップ処理が実行中のため、重複起動を回避して終了します。”
exit 0
fi

log “INFO” “=== バックアップ処理を開始します [${TIMESTAMP}] ===”

# ディスク使用率の事前チェック
CURRENT_USAGE=$(df -h “${TARGET_MOUNT}” | awk ‘NR==2 {print $5}’ | tr -d ‘%’)
if [[ ! “${CURRENT_USAGE}” =~ ^[0-9]+$ ]]; then
log “ERROR” “ディスク使用率の取得に失敗しました。”
exit 1
fi

log “INFO” “マウントポイント [${TARGET_MOUNT}] の現在使用率: ${CURRENT_USAGE}%(上限閾値: ${DISK_THRESHOLD}%)”
if (( CURRENT_USAGE >= DISK_THRESHOLD )); then
log “ERROR” “ディスク使用率が閾値(${DISK_THRESHOLD}%)を超過しているため、枯渇防止のためバックアップを中止します!”
send_slack_alert “ディスク残容量不足(使用率: ${CURRENT_USAGE}%)。バックアップを緊急停止しました。”
exit 1
fi

# 保存先ディレクトリの存在確認
mkdir -p “${BACKUP_DEST_LOCAL}”

# ——————————————————————————
# 4. アーカイブ作成(tar圧縮・除外指定)
# ——————————————————————————
log “INFO” “tarアーカイブを作成中: ${LOCAL_ARCHIVE_PATH}”

tar -czf “${LOCAL_ARCHIVE_PATH}” \
–exclude=”*.tmp” \
–exclude=”*.log” \
–exclude=”*.cache” \
–exclude=”*/cache/*” \
–exclude=”*/.git/*” \
–exclude=”node_modules” \
“${BACKUP_SRC[@]}” 2>>”${LOG_FILE}”

ARCHIVE_SIZE=$(du -h “${LOCAL_ARCHIVE_PATH}” | cut -f1)
log “INFO” “アーカイブ作成が完了しました(サイズ: ${ARCHIVE_SIZE})”

# ——————————————————————————
# 5. リモート転送(rsync)
# ——————————————————————————
if [[ “${REMOTE_SYNC_ENABLE}” == “true” ]]; then
log “INFO” “リモートサーバーへの同期を開始します: ${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_DIR}”

rsync -avz –partial -e “ssh -p ${REMOTE_PORT} -o ConnectTimeout=15 -o BatchMode=yes” \
“${LOCAL_ARCHIVE_PATH}” \
“${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_DIR}/” >>”${LOG_FILE}” 2>&1

log “INFO” “リモートサーバーへの転送が正常に完了しました。”
fi

# ——————————————————————————
# 6. 世代管理ローテーション(古い世代の自動削除)
# ——————————————————————————
log “INFO” “ローカル保存先の世代管理ローテーション(${RETENTION_DAYS}日以上前のファイルを削除)を実行します…”

OLD_BACKUPS=$(find “${BACKUP_DEST_LOCAL}” -name “${BACKUP_NAME}_*.tar.gz” -mtime +”${RETENTION_DAYS}” -type f)

if [[ -n “${OLD_BACKUPS}” ]]; then
log “INFO” “以下の古いバックアップファイルを削除します:”
echo “${OLD_BACKUPS}” | while IFS= read -r file; do
log “INFO” ” – 削除: ${file}”
rm -f “${file}”
done
else
log “INFO” “削除対象となる古いバックアップファイルはありませんでした。”
fi

# リモート側の古いバックアップ削除(SSH経由でfind実行)
if [[ “${REMOTE_SYNC_ENABLE}” == “true” ]]; then
log “INFO” “リモートサーバー側の世代管理ローテーションを実行します…”
ssh -p “${REMOTE_PORT}” -o ConnectTimeout=15 -o BatchMode=yes “${REMOTE_USER}@${REMOTE_HOST}” \
“find \”${REMOTE_DIR}\” -name \”${BACKUP_NAME}_*.tar.gz\” -mtime +${RETENTION_DAYS} -type f -delete” >>”${LOG_FILE}” 2>&1 || {
log “WARN” “リモート側の古いバックアップ削除中に警告が発生しました。”
}
fi

log “INFO” “=== 全てのバックアップ工程が正常に完了しました ===”
exit 0
“`

—

## スクリプトの処理フローと重要設計ポイント

完成版スクリプトがどのように動作し、なぜトラブルを防げるのか、4つの設計ポイントに分けて深掘り解説します。

### 1. バックアップ対象のアーカイブ圧縮(tarコマンドと除外設定)

スクリプトの中心となるのが、複数ディレクトリを1つの圧縮アーカイブにまとめる tar コマンドです。

“`bash
tar -czf “${LOCAL_ARCHIVE_PATH}” \
–exclude=”*.tmp” \
–exclude=”*.log” \
–exclude=”*.cache” \
–exclude=”*/cache/*” \
–exclude=”*/.git/*” \
–exclude=”node_modules” \
“${BACKUP_SRC[@]}” 2>>”${LOG_FILE}”
“`

実務でバックアップスクリプトを自作する際、「対象ディレクトリを丸ごと無差別に固める」のは厳禁です。一時ファイル(.tmp)や大量のキャッシュファイル、Gitの履歴オブジェクト(.git)、Node.jsの依存パッケージ(node_modules)を含めてしまうと、以下の深刻な問題が発生します。

– アーカイブのファイル容量が数倍〜十数倍に跳ね上がり、ディスクを圧迫する
– 圧縮・解凍処理に膨大なCPUリソースと時間がかかり、サーバーに高負荷を与える
– 一時ファイルが圧縮処理中に書き換えられ、tar: file changed as we read it という非致命的エラーでスクリプトが停止する

#### tar主要オプション早見表

| オプション | 機能説明 | 実務における重要性 |
| :— | :— | :— |
| `-c`(`–create`) | 新しいアーカイブファイルを作成する | バックアップ作成の必須基本オプション |
| `-z`(`–gzip`) | gzip形式で圧縮する | ストレージ容量を約60〜80%削減する |
| `-f <ファイル名>` | 作成するアーカイブファイル名を指定する | 出力先パスを明示的に指定 |
| `-p`(`–preserve-permissions`) | ファイルの所有権・パーミッションを保持する | リストア時に権限崩れによるWeb停止を防ぐ |
| `–exclude=<パターン>` | 指定したパターンに一致するファイル・ディレクトリを除外する | 一時ファイルやキャッシュを排除して容量を節約 |

--exclude は複数回指定できます。WordPressサイトをバックアップする場合は --exclude="*/wp-content/cache/*" を、Laravel等のPHPフレームワークでは --exclude="*/storage/framework/cache/*" を指定するのが鉄則です。

### 2. rsyncによる効率的なローカル・リモート転送(-avzオプション)

作成したアーカイブは、同じサーバー内に置いておくだけでは「ハードウェア故障」や「クラウド事業者のリージョン障害」からデータを守ることができません。そのため、rsync を用いて外部ストレージや別サーバーへ即座に退避させます。

“`bash
rsync -avz –partial -e “ssh -p ${REMOTE_PORT} -o ConnectTimeout=15 -o BatchMode=yes” \
“${LOCAL_ARCHIVE_PATH}” \
“${REMOTE_USER}@${REMOTE_HOST}:${REMOTE_DIR}/” >>”${LOG_FILE}” 2>&1
“`

#### rsyncの `-avz` と実務オプションの真価

rsync は単なるファイルコピーコマンドではなく、タイムスタンプやパーミッションを厳密に同期できる高性能転送ツールです。

– -a(アーカイブモード): 以下のオプションをすべて内包した実務必須のまとめオプションです。
– `-r`(再帰的ディレクトリコピー)
– `-l`(シンボリックリンクをリンクのまま維持)
– `-p`(パーミッションの完全保持)
– `-t`(タイムスタンプの完全保持)
– `-g`(グループ所有権の保持)
– `-o`(ユーザー所有権の保持:root実行時)
– `-D`(デバイスファイル・特殊ファイルの保持)
– -v(詳細出力): 転送されたファイル名や進捗をログに出力します。
– -z(転送時圧縮): ネットワーク転送中にデータを一時圧縮し、帯域幅と転送時間を削減します。
– --partial: ネットワーク寸断などで転送が途中で途切れた場合、途中まで転送されたファイルを保持し、次回実行時に続きから再開(レジューム)できるようにします。

さらに、SSH経由で自動実行する際は -o BatchMode=yes の付与が極めて重要です。これにより、SSH鍵認証が通らない場合にパスワードプロンプトで対話待ち(ハングアップ)になるのを防ぎ、即座にエラーとして終了させることができます。

#### 【応用】ディレクトリ丸ごとバックアップ時の「–link-dest」によるハードリンク増分バックアップ

アーカイブ(.tar.gz)ではなく、展開されたディレクトリ構造のまま日次バックアップを取りたい場合は、rsync の --link-dest オプションを活用した「ハードリンク増分バックアップ」が威力を発揮します。

“`bash
# 前日のバックアップディレクトリを参照し、変更のないファイルはハードリンクで作成
rsync -av –delete \
–link-dest=”/var/backups/yesterday” \
“/var/www/html/” \
“/var/backups/today/”
“`

--link-dest を使用すると、前回のバックアップから変更されていないファイルは新しい実体ディスク領域を消費せず、既存ファイルへの「ハードリンク」として瞬時に作成されます。変更があったファイルのみが新規書き込みされるため、「毎日フルバックアップがあるように見えながら、ディスク消費量は純粋な差分データ分のみ」という理想的なバックアップシステムを実現できます。

### 3. 古い世代の自動削除(findコマンドによる世代管理ローテーション)

自動バックアップを運用する上で最も事故が多発するのが「削除処理の不具合」です。バックアップを作成するだけで古いファイルを消さなければ、数週間〜数ヶ月でディスク使用率が100%に達し、サーバーがクラッシュします。

スクリプト内では、find コマンドを用いて指定日数を超えたアーカイブを特定し、安全に削除しています。

“`bash
OLD_BACKUPS=$(find “${BACKUP_DEST_LOCAL}” -name “${BACKUP_NAME}_*.tar.gz” -mtime +”${RETENTION_DAYS}” -type f)

if [[ -n “${OLD_BACKUPS}” ]]; then
echo “${OLD_BACKUPS}” | while IFS= read -r file; do
rm -f “${file}”
done
fi
“`

#### -mtime +7 の正確な計算と注意点

find コマンドの -mtime(最終更新時刻判定)は、単純な「7日前」ではなく「24時間 × 7 = 168時間以上前」に更新されたファイルを対象とします。

– `-mtime 7`: ちょうど7日前に更新されたファイル(168時間〜192時間前)
– `-mtime -7`: 過去7日以内に更新されたファイル(168時間以内)
– `-mtime +7`: 7日より前に更新された古いファイル(168時間超過)

保持世代数を7世代としたい場合、-mtime +7 を指定することで、過去7日間分のアーカイブ(最新7世代)を確実に手元に残し、8日目以降の古いアーカイブのみを削除対象にできます。

#### 現場の安全作法:いきなり「-delete」を実行しない

実務において、find ... -delete をスクリプト内に直接記述するのは危険が伴います。万が一変数 BACKUP_DEST_LOCAL が空文字やルート(/)になってしまった場合、システム全体の重要ファイルを根こそぎ消し去る大惨事(自爆事故)につながるためです。

完成版スクリプトでは、以下の二重安全策を講じています:

1. `set -u` により、未定義変数が参照された場合はスクリプトを即時強制終了
2. ファイル名パターン(`${BACKUP_NAME}_*.tar.gz`)とディレクトリパスを厳格に指定
3. 一旦対象ファイルリストを変数に格納し、ログに削除対象ファイル名を明記しながら1件ずつ安全に削除

### 4. バックアップ前後のディスク空き容量チェック(dfコマンド連携)

「バックアップを実行したせいでディスクがパンクした」という事故を未然に防ぐため、処理開始直後に df コマンドで対象マウントポイントの使用率をチェックしています。

“`bash
CURRENT_USAGE=$(df -h “${TARGET_MOUNT}” | awk ‘NR==2 {print $5}’ | tr -d ‘%’)
if (( CURRENT_USAGE >= DISK_THRESHOLD )); then
log “ERROR” “ディスク使用率が閾値(${DISK_THRESHOLD}%)を超過しているため、バックアップを中止します!”
send_slack_alert “ディスク残容量不足(使用率: ${CURRENT_USAGE}%)。バックアップを緊急停止しました。”
exit 1
fi
“`

バックアップアーカイブの生成には、当然ながらアーカイブファイル自身の容量分だけディスクの一時空き容量が必要です。すでにディスク使用率が85%や90%に達している状態で数GBの圧縮処理を開始すると、圧縮の途中で空き容量が0バイトになり、MySQL等のデータベースが書き込み不能となって即座にクラッシュします。

スクリプト実行前に容量を事前判定し、閾値を超えている場合は「あえてバックアップを行わず、管理者にSlack通知を飛ばして安全停止する」というフェイルセーフ設計を取り入れています。

バックアップ前後のディスク空き容量チェックでは、マウントポイントごとの容量を正しく判定する必要があります。ディスク容量調査の基本コマンドやinode確認手順については、Linuxディスク容量の確認と調査手順|dfとduコマンドで容量逼迫の原因ファイルを特定する方法 を参考にしてください。

—

## トラブルを未然に防ぐ安全設計・堅牢化テクニック

年末年始の無人運用に耐えうるシェルスクリプトには、「コマンドが正常に動くこと」以上に「エラーや例外が発生した際に安全に倒れること」が求められます。

### set -euo pipefail と trap による安全なクリーンアップ

本スクリプトの冒頭には、Bashスクリプトの堅牢化における黄金律である set -euo pipefail を宣言しています。

– set -e: コマンドが1つでもエラー(終了ステータスが0以外)を返した場合、後続処理を実行せず即座にスクリプトを停止
– set -u: 未定義の変数を参照しようとした場合、エラーを出力して即座にスクリプトを停止(`rm -rf “$UNDEF_VAR/*”` によるルート削除事故を防止)
– set -o pipefail: パイプライン処理(`cmdA | cmdB | cmdC`)において、途中の `cmdA` や `cmdB` が失敗した場合でもパイプ全体の終了ステータスをエラーとして検知

さらに、途中でエラー停止した際に残骸を残さないよう、trap コマンドで終了時処理(クリーンアップ)をフックしています。

“`bash
cleanup() {
local exit_code=$?
if (( exit_code != 0 )); then
log “ERROR” “スクリプトが異常終了しました(Exit Code: ${exit_code})。”
if [[ -f “${LOCAL_ARCHIVE_PATH}” ]]; then
log “INFO” “不完全なバックアップアーカイブを破棄します: ${LOCAL_ARCHIVE_PATH}”
rm -f “${LOCAL_ARCHIVE_PATH}”
fi
send_slack_alert “異常終了コード: ${exit_code}。ログ: ${LOG_FILE} を確認してください。”
fi
rm -f “${LOCK_FILE}”
}

trap cleanup EXIT
“`

バックアップ作成中にディスク容量不足や転送エラーでスクリプトが落ちた場合、壊れた中途半端なアーカイブファイル(0バイトや途中で切れた .tar.gz)がディスクに残存してしまいます。

trap cleanup EXIT を仕込んでおくことで、正常終了・異常終了のどちらであっても確実にロックファイルを解放し、異常終了時には中途半端な壊れたアーカイブを自動破棄して管理者のSlackへ即座に通報します。

### 多重起動防止(flock / ロックファイル制御)

バックアップ対象のデータ量が増加したり、ネットワーク帯域が逼迫すると、バックアップ処理の所要時間が普段の数十分から数時間に延びてしまうことがあります。

もし前回の処理が終わらないまま次回の定期実行時刻が訪れると、同じバックアッププロセスが2重、3重に同時起動し、ディスクI/OとCPUが100%に張り付いてサーバー全体がサービス停止に追い込まれます。

この多重起動事故を防ぐため、スクリプトでは Linux 標準の flock コマンドを採用しています。

“`bash
# 排他制御: 二重起動を防止(ファイルディスクリプタ200番を利用)
exec 200>”${LOCK_FILE}”
if ! flock -n 200; then
log “WARN” “前回のバックアップ処理が実行中のため、重複起動を回避して終了します。”
exit 0
fi
“`

#### なぜ「PIDファイルを保存してチェックする方式」はダメなのか?

初心者がよく書く「スクリプト開始時に echo $$ > /var/run/app.pid を書き込み、次回起動時にPIDファイルの存在を確認する」という方式には致命的な欠陥があります。

サーバーが不意に再起動したり、プロセスが kill -9 で強制終了された場合、古いPIDファイルがディスク上に永遠に残り続けます。その結果、「本当は動いていないのに、PIDファイルが存在するせいで二度とバックアップが起動しなくなる」というスタック状態に陥ります。

flock によるファイルディスクリプタ排他制御であれば、プロセスが何らかの理由で強制終了した場合でも、Linuxカーネルがプロセスの消滅を検知して自動的にファイルロックを解放してくれます。手動でのロックファイル削除が不要なため、無人運用に最適な排他制御です。

### バックアップ失敗時のSlack Webhook通知(curl連携)

休暇中にサーバーが異常を検知しても、通知が届かなければ手遅れになります。本スクリプトでは、外部の通知ライブラリや重いツールに依存せず、Linux標準の curl コマンドで直接 Slack の Incoming Webhook へJSONをPOST送信します。

“`bash
send_slack_alert() {
local error_msg=”$1″
local hostname
hostname=”$(hostname)”

local payload
payload=$(cat < /dev/null 2>&1 || true
}
“`

カラーコードに danger(赤色)を指定し、対象ホスト名や発生時刻、終了コードをリッチカード形式で表示させています。スマートフォンにSlackアプリを導入しておけば、休暇の旅行先や帰省先でもプッシュ通知で瞬時に異常を把握できます。

スクリプト異常終了時の通知には、Slack Incoming Webhookを活用します。Webhook連携による障害通知や死活監視の全体設計については、【Bash】サーバー死活監視&Slack通知スクリプト自作ガイド|curlとsystemctl連携による自動再起動と無人運用 でも詳しく解説しています。

—

## cron(crontab)による深夜・年末年始の自動定期実行

スクリプトが完成したら、cronを用いて毎夜の無人定期実行を設定します。

### crontabへの安全なスケジュール登録例

WebサイトやAPIのトラフィックが最も少なくなる深夜・未明帯(午前3時〜4時など)に実行するのが一般的です。

“`bash
# rootユーザーのcrontabを編集
sudo crontab -e
“`

以下の行をcrontabに追加します。

“`bash
# 毎日午前3時30分に自動バックアップスクリプトを実行
30 3 * * * /usr/local/bin/backup_rotation.sh >> /var/log/backup_rotation_cron.log 2>&1
“`

時間指定の記述ルールや動かないときのデバッグ法は、crontab書き方早見表|時間指定・曜日・実行間隔の構文と動かない時のデバッグ完全ガイド をご確認ください。

### cron実行時の落とし穴(PATH環境変数の違い・絶対パス指定の徹底)

「手動でターミナルから実行すると完璧に動くのに、cronに登録すると一切動かない」というのは、Linux初心者が最も直面しやすいトラブルの筆頭です。

その原因の99%は、「ログインシェルとcron環境における環境変数PATHの違い」にあります。

一般的なユーザーがログインしたシェルでは /usr/local/bin や /usr/sbin にPATHが通っていますが、cronが起動する非対話型サブシェルでは、極めて限定的なPATH(例: /usr/bin:/bin のみ)しか設定されていません。その結果、rsync や curl、flock が「コマンドが見つかりません(command not found)」となって即死します。

この落とし穴を確実に回避するため、以下の2点を徹底してください:

1. スクリプトの冒頭で明示的にPATHを定義する:
“`bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
export PATH
“`
2. 外部コマンドはなるべくフルパス、または `which` で確認した安全なパスで呼ぶ

### 実行ログの出力先とログローテーション連携

バックアップの実行記録は /var/log/backup_rotation.log に蓄積されますが、このログ自体が肥大化してディスクを圧迫しては本末転倒です。

OS標準の logrotate にバックアップログのローテーション設定を追加しておきましょう。

“`bash
# /etc/logrotate.d/backup_rotation を作成
sudo nano /etc/logrotate.d/backup_rotation
“`

以下の内容を記述します。

“`nginx
/var/log/backup_rotation.log
/var/log/backup_rotation_cron.log {
monthly
rotate 6
compress
delaycompress
missingok
notifempty
create 0640 root root
}
“`

これで、バックアップログは月次でローテーションされ、過去6ヶ月分のみがgzip圧縮されて保管されます。

なお、バックアップログからエラー行を抽出したり、過去の実行記録から異常を検知する際は、grepコマンドをパイプラインで組み合わせると便利です。grepコマンドの使い方と主要オプション一覧早見表|逆引き検索実例と出力読み解き を活用して、効率的なログ解析を行いましょう。

—

## バックアップの動作検証とリストア(復元)テスト手順

システム運用の格言に「リストア(復元)テストを行っていないバックアップは、バックアップが存在しないのと同じである」という言葉があります。

年末年始を迎える前に、必ず以下の2つのテストを実施しておきましょう。

### バックアップアーカイブの整合性確認(tar -tzf)

生成された .tar.gz ファイルが破損していないか、gzipヘッダーやデータブロックが壊れていないかを確認するには、tar の -t(目録表示)オプションを使用します。

“`bash
# アーカイブを展開せずに内部の整合性をチェック
tar -tzf /var/backups/daily/server_backup_YYYYMMDD_HHMMSS.tar.gz > /dev/null

# 終了コードを確認(正常なら 0)
echo $?
“`

-t オプションを付けた tar は、アーカイブを展開することなく内部ファイルを先頭から末尾まで全走査します。もし転送エラーやディスク書き込みエラーでファイルが破損していれば、tar: Unexpected EOF in archive や gzip: stdin: invalid compressed data--format violated といったエラーが出力され、終了コードが1以上になります。

### 擬似障害時の安全なリストア手順

万が一のサーバー障害に備え、バックアップから安全にファイルを復元する手順をリハーサルしておきます。

本番ディレクトリにいきなり上書き展開するのは非常に危険です。必ず /tmp や /mnt 配下の作業用ディレクトリに一旦展開し、中身を確認してから配置します。

“`bash
# 1. テスト展開用の一時ディレクトリを作成
mkdir -p /tmp/restore_test

# 2. 一時ディレクトリにアーカイブを展開
tar -xzf /var/backups/daily/server_backup_YYYYMMDD_HHMMSS.tar.gz -C /tmp/restore_test

# 3. 展開されたファイルの内容やパーミッションを確認
ls -la /tmp/restore_test/var/www
ls -la /tmp/restore_test/etc/nginx

# 4. 確認が完了したらテストディレクトリを削除
rm -rf /tmp/restore_test
“`

この手順を確認しておくことで、休暇中に不測のトラブルが発生しても、落ち着いて最短時間でサービスを復旧できます。

年末年始の計画メンテナンスやOSアップデートに伴うサーバー再起動を行う際は、必ずバックアップが正常完了したことを確認してから実施するのが鉄則です。安全なシャットダウン手順については、Linuxのshutdownコマンドの使い方|即時終了・再起動・時間指定・キャンセルの復元 を参照してください。

—

## まとめ:年末年始を安心して迎えるためのサーバー保全チェックリスト

年末年始や大型連休を無人運用で乗り切るための自動バックアップ設計について解説してきました。休暇直前の業務最終日に慌てないよう、以下のチェックリストでサーバーの安全体制を総点検してください。

| 点検項目 | 確認手順・コマンド | 合格基準 |
| :— | :— | :— |
| 1. バックアップ手動試運転 | `sudo /usr/local/bin/backup_rotation.sh` | エラーなく終了しログに正常記録されること |
| 2. ディスク残容量確認 | `df -h /` | ルートパーティションの使用率が80%未満であること |
| 3. Slack通知テスト | テスト用Webhook送信 | スマホのSlackアプリに通知が即時届くこと |
| 4. リストア整合性テスト | `tar -tzf <アーカイブ> > /dev/null` | 終了コードが0でエラーが出ないこと |
| 5. cron自動実行確認 | `sudo crontab -l` | 正しい深夜スケジュールとパスで登録されていること |

「手作業は負け」。信頼性の高い自動化スクリプトを仕込んでおけば、休暇中にシステムトラブルの不安を抱えることなく、家族や友人と心から休まり、新年を気持ちよく迎えることができます。ぜひ本記事のスクリプトを自社サーバーに合わせて調整し、実戦投入してみてください!

—

## あわせて読みたい関連記事

– 長期休暇前のサーバー停止を防ぐ!logrotate総点検とディスク監視自動化スクリプト
– crontab書き方早見表|時間指定・曜日・実行間隔の構文と動かない時のデバッグ完全ガイド
– Linuxディスク容量の確認と調査手順|dfとduコマンドで容量逼迫の原因ファイルを特定する方法
– 【Bash】サーバー死活監視&Slack通知スクリプト自作ガイド|curlとsystemctl連携による自動再起動と無人運用
– grepコマンドの使い方と主要オプション一覧早見表|逆引き検索実例と出力読み解き
– Linuxのshutdownコマンドの使い方|即時終了・再起動・時間指定・キャンセルの復元

Bash玄

はじめまして!Bash玄です。

エンジニアとしてシステム運用に携わる中で、手作業の多さに限界を感じ、Bashスクリプトを活用して業務を効率化したのがきっかけで、この道に入りました。「手作業は負け」「スクリプトはシンプルに」をモットーに、誰でも実践できるBashスクリプトの書き方を発信しています。

このサイトでは、Bashの基礎から実践的なスクリプト作成まで、初心者でもわかりやすく解説しています。少しでも「Bashって便利だな」と思ってもらえたら嬉しいです!

# 好きなこと
- シンプルなコードを書くこと
- コマンドラインを快適にカスタマイズすること
- 自動化で時間を生み出すこと

# このサイトを読んでほしい人
- Bashに興味があるけど、何から始めればいいかわからない人
- 定型業務を自動化したい人
- 効率よくターミナルを使いこなしたい人

Bashの世界に一歩踏み出して、一緒に「Bash道」を極めていきましょう!

Bash玄をフォローする

コメント