バックアップの自動化は、シェルスクリプトとcronを組み合わせるだけで実現できます。ただし、動けばOKのスクリプトと、現場で長期間安定稼働するスクリプトには大きな差があります。エラーを検知してアラートを送る仕組み、ログへの記録、古いファイルの世代管理。これらをまとめて設計してはじめて「運用に耐えるバックアップ」になります。
この記事では、シェルスクリプトでバックアップ処理を書き、cronで定期実行し、ログに残し、失敗時にアラートを送るところまでを実践的に解説します。RHEL 9.4 / Ubuntu 24.04 LTSで動作確認済みのコードを使って、コピーして使えるレベルまで仕上げます。
この記事のポイント
・シェルスクリプトでrsyncを使ったバックアップ処理を安全に書ける
・cronで定期実行するときの環境変数・パスの落とし穴を避けられる
・ログファイルにタイムスタンプ付きで記録し、エラーを判定できる
・バックアップの世代管理(古いファイルの自動削除)を実装できる
・バックアップ失敗時にメールまたはWebhookでアラートを自動送信できる
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
なぜシェルスクリプトでバックアップを自動化するのか
手動バックアップの最大の問題は「忘れる」ことです。深夜のメンテナンス後、重要な作業前のバックアップを取り忘れてトラブルになったケースは数え切れません。私がセミナーで3,100名以上を指導してきた中で、バックアップが原因で復旧できなかったという相談を何度も受けてきました。ほぼ全てのケースで共通しているのが「自動化していなかった」という点です。さらに、「スクリプトは動かしていたが、失敗しても気づかなかった」という問題も少なくありません。動いているように見えて、実はエラーが続いていたというケースです。
シェルスクリプトとcronを組み合わせることで、人間の手を介さずに定期実行できます。さらに以下のメリットがあります。
・一貫性:毎回同じ手順でバックアップが取れる
・記録:ログに残すことで後から確認できる
・検知:終了コードでエラーを即座に把握できる
・世代管理:古いバックアップを自動削除してディスクを節約できる
・アラート:バックアップが失敗したらメールやSlackに即通知できる
バックアップスクリプトの基本構成を作る
1. ディレクトリ構成と変数定義
最初に変数と保存先を整理します。後から変更しやすい設計にすることが重要です。ログ出力を統一するために、先頭でlog()関数も定義しておきます。#!/bin/bash # backup.sh -- ディレクトリバックアップスクリプト(RHEL 9.4 / Ubuntu 24.04 確認済み) # ---- 設定変数 ---- SRC_DIR="/var/www/html" # バックアップ元 DST_DIR="/mnt/backup" # バックアップ先 LOG_DIR="/var/log/backup" # ログ保存先 TIMESTAMP=$(date +%Y%m%d_%H%M%S) # タイムスタンプ LOG_FILE="${LOG_DIR}/backup_${TIMESTAMP}.log" KEEP_DAYS=14 # 保持する日数 ALERT_EMAIL="admin@example.com" # アラート送信先 # ---- ディレクトリ確認 ---- mkdir -p "${DST_DIR}" "${LOG_DIR}" # ---- ログ出力関数 ---- log() { local level="$1"; shift echo "[$(date '+%Y-%m-%d %H:%M:%S')] [${level}] $*" | tee -a "${LOG_FILE}" }
2. rsyncを使ったコピー処理
バックアップの実体となるrsyncコマンドを書きます。# ---- バックアップ実行 ---- log "INFO" "バックアップ開始: ${SRC_DIR} -> ${DST_DIR}" /usr/bin/rsync -av --delete "${SRC_DIR}/" "${DST_DIR}/" >> "${LOG_FILE}" 2>&1 EXIT_CODE=$? log "INFO" "rsync 終了コード: ${EXIT_CODE}"
・-v(--verbose):転送ファイルを出力してログに残す
・--delete:バックアップ元で削除されたファイルをバックアップ先からも削除する
・2>&1:標準エラーを標準出力にまとめてログファイルに書き込む
rsyncのフルパス(/usr/bin/rsync)を指定しているのは、cronの実行環境でPATHが省略されていても確実に動かすためです。コマンドがフルパス指定でなければ「command not found」になるケースは実務で非常に多いです。
rsyncのオプションは組み合わせで動作が変わります。--deleteを外すと削除が反映されない「増分バックアップ」になります。用途に合わせて選んでください。
3. 終了コードでエラーを判定する
rsyncの終了コード($?)を確認してエラー処理を書きます。# ---- 終了コード判定 ---- if [ "${EXIT_CODE}" -eq 0 ]; then log "OK" "バックアップ成功" elif [ "${EXIT_CODE}" -eq 24 ]; then # 転送中にファイルが消えた(正常範囲) log "WARN" "一部ファイルが転送中に消えました(終了コード24)" else log "ERROR" "rsync 失敗(終了コード ${EXIT_CODE})" send_alert "rsync 失敗(終了コード ${EXIT_CODE})" exit 1 fi
古いバックアップを自動削除する世代管理
バックアップが溜まり続けるとディスクが枯渇します。findコマンドで古いログ・バックアップを定期削除しましょう。# ---- 古いログを削除(14日以上前) ---- /usr/bin/find "${LOG_DIR}" -name "backup_*.log" -mtime +${KEEP_DAYS} -delete log "INFO" "${KEEP_DAYS}日以上前のログを削除しました"
# 複数世代バックアップ(スナップショット型)の例 # 日付ごとのサブディレクトリにバックアップする TODAY=$(date +%Y%m%d) DST_DATED="${DST_DIR}/${TODAY}" mkdir -p "${DST_DATED}" /usr/bin/rsync -av "${SRC_DIR}/" "${DST_DATED}/" >> "${LOG_FILE}" 2>&1 # 14日以上前のスナップショットを削除 /usr/bin/find "${DST_DIR}" -maxdepth 1 -mindepth 1 -type d -mtime +${KEEP_DAYS} -exec rm -rf {} + log "INFO" "14日超のスナップショットを削除しました"
cronで定期実行するときの落とし穴
スクリプトが手動では動くのにcronでは動かない、という問題はシェルスクリプトの落とし穴としてよく知られています。4. cronの環境変数とPATHの問題
cronは非常に限定的な環境で動作します。通常のログインシェルとは異なり、PATH・LANG・ホームディレクトリの変数がほとんど設定されていません。# crontab -e で設定する内容(毎日 2:00 に実行) # cronのデフォルトPATHは /usr/bin:/bin のみ PATH=/usr/local/bin:/usr/bin:/bin:/sbin:/usr/sbin SHELL=/bin/bash 0 2 * * * /usr/local/bin/backup.sh >> /var/log/backup/cron.log 2>&1
また、スクリプト内のコマンドはフルパスで書くと安全です。
rsyncではなく/usr/bin/rsyncのように書けば、cronのPATH設定に依存しません。cronと同等の最小環境を手元でシミュレートして確認するには以下のコマンドが役立ちます。# cron 同等の最小環境で PATH を確認する env -i /bin/sh -c env | grep PATH # 出力例: PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin # rsync のフルパスを確認してスクリプト内に記述する which rsync # 出力例: /usr/bin/rsync
5. スクリプトに実行権限を付ける
スクリプトをcronに登録する前に、実行権限を確認してください。# スクリプトを /usr/local/bin/ に配置 sudo cp backup.sh /usr/local/bin/backup.sh sudo chmod 700 /usr/local/bin/backup.sh sudo chown root:root /usr/local/bin/backup.sh # 動作確認(root権限でテスト実行) sudo /usr/local/bin/backup.sh # ログを確認 ls -lh /var/log/backup/
セミナーでよく聞かれる質問が「なぜ755ではなく700にするのか」です。バックアップスクリプトは内部に変数でパスを持ち、誤操作で別の場所を上書きしてしまうリスクがあります。root専用に絞っておくことで事故を防ぎます。
バックアップ失敗時にアラートを送る
スクリプトが失敗しても誰も気づかない状態は「バックアップがない」と同義です。運用で最も重要なのは、エラーを自動で検知して担当者に即通知する仕組みです。6. mailxでメール通知する
rsyncが失敗したとき(終了コードが0でも24でもないとき)にメールを送る関数を定義します。# ---- アラート送信関数(スクリプト冒頭で定義) ---- ALERT_EMAIL="admin@example.com" WEBHOOK_URL="" # Slack Webhook URL(使わない場合は空のまま) send_alert() { local message="$1" local subject="[バックアップ失敗] $(hostname): ${message}" local body body="$(printf 'ホスト: %s メッセージ: %s 発生時刻: %s' "$(hostname)" "${message}" "$(date '+%Y-%m-%d %H:%M:%S')")" # メール通知(mailx が使える環境の場合) if command -v mailx &>/dev/null; then printf "%s" "$body" | mailx -s "$subject" "${ALERT_EMAIL}" fi # Webhook 通知(Slack / Teams 等) if [ -n "${WEBHOOK_URL}" ]; then curl -s -X POST "${WEBHOOK_URL}" -H "Content-Type: application/json" -d "{"text": "${subject}"}" > /dev/null fi }
command -v mailxでmailxの有無を確認してから送信するので、mailxがインストールされていない環境でもスクリプトが落ちません。WebhookのURLが空の場合は通知をスキップします。両方を設定してもかまいませんし、どちらか一方だけでも機能します。7. mailxのインストールと送信テスト
mailxがない環境ではパッケージをインストールします。# RHEL 9.4 / Rocky Linux の場合 sudo dnf install -y mailx # Ubuntu 24.04 の場合 sudo apt-get install -y mailutils # 送信テスト echo "テスト送信" | mailx -s "backup_test" admin@example.com # 送信ログの確認 tail -20 /var/log/maillog # RHEL系 tail -20 /var/log/mail.log # Ubuntu系
スクリプト完成版(コピーして使えるコード)
ここまでのパーツを組み合わせた完成版スクリプトです。#!/bin/bash # backup.sh -- バックアップ自動化スクリプト # 対象OS: RHEL 9.4 / Ubuntu 24.04 LTS # 用途: Webサーバーコンテンツの日次バックアップ set -euo pipefail # エラー・未定義変数・パイプ失敗で即停止 # ---- 設定変数 ---- SRC_DIR="/var/www/html" DST_DIR="/mnt/backup" LOG_DIR="/var/log/backup" TIMESTAMP=$(date +%Y%m%d_%H%M%S) LOG_FILE="${LOG_DIR}/backup_${TIMESTAMP}.log" KEEP_DAYS=14 ALERT_EMAIL="admin@example.com" WEBHOOK_URL="" # ---- 初期化 ---- mkdir -p "${DST_DIR}" "${LOG_DIR}" # ---- ログ出力関数 ---- log() { local level="$1"; shift echo "[$(date '+%Y-%m-%d %H:%M:%S')] [${level}] $*" | tee -a "${LOG_FILE}" } # ---- アラート送信関数 ---- send_alert() { local message="$1" local subject="[バックアップ失敗] $(hostname): ${message}" local body body="$(printf 'ホスト: %s メッセージ: %s 発生時刻: %s' "$(hostname)" "${message}" "$(date '+%Y-%m-%d %H:%M:%S')")" if command -v mailx &>/dev/null; then printf "%s" "$body" | mailx -s "$subject" "${ALERT_EMAIL}" fi if [ -n "${WEBHOOK_URL}" ]; then curl -s -X POST "${WEBHOOK_URL}" -H "Content-Type: application/json" -d "{"text": "${subject}"}" > /dev/null fi } log "INFO" "=== バックアップ開始 ===" log "INFO" "元: ${SRC_DIR}" log "INFO" "先: ${DST_DIR}" # ---- rsync実行 ---- /usr/bin/rsync -av --delete "${SRC_DIR}/" "${DST_DIR}/" >> "${LOG_FILE}" 2>&1 EXIT_CODE=$? if [ "${EXIT_CODE}" -eq 0 ]; then log "OK" "バックアップ完了" elif [ "${EXIT_CODE}" -eq 24 ]; then log "WARN" "転送中ファイル消失(終了コード24)" else log "ERROR" "rsync 失敗(終了コード ${EXIT_CODE})" send_alert "rsync 失敗(終了コード ${EXIT_CODE})" exit 1 fi # ---- 世代管理 ---- /usr/bin/find "${LOG_DIR}" -name "backup_*.log" -mtime +${KEEP_DAYS} -delete log "INFO" "${KEEP_DAYS}日超のログを削除しました" log "INFO" "=== 処理終了 ==="
set -euo pipefailは現場スクリプトの必須設定です。eはエラーで即停止、uは未定義変数の参照をエラー扱い、pipefailはパイプ途中のエラーを拾います。この1行があるだけで、無言で失敗し続けるスクリプトを防げます。関連記事として、シェルスクリプトのエラー処理全般については「Linux 基本コマンドの解説」も合わせて参照してください。
ログファイルの設計と実サーバーでの出力例
ログがきちんと残っているか、実際のサーバーでの出力を見てみましょう。# 実行後のログ出力例(実サーバー: web01.example.com) [2026-06-24 02:00:03] [INFO] === バックアップ開始 === [2026-06-24 02:00:03] [INFO] 元: /var/www/html [2026-06-24 02:00:03] [INFO] 先: /mnt/backup sending incremental file list ./ index.html wp-content/uploads/2026/06/image001.png wp-content/uploads/2026/06/image002.png sent 1,234,567 bytes received 128 bytes 2,469,390.00 bytes/sec total size is 125,432,890 speedup is 101.58 [2026-06-24 02:00:09] [OK] バックアップ完了 [2026-06-24 02:00:09] [INFO] 14日超のログを削除しました [2026-06-24 02:00:09] [INFO] === 処理終了 ===
ログファイルは日ごとに新しいファイルに分かれます(TIMESTAMP変数でファイル名を分岐)。古いログはfindで14日後に自動削除されます。問題が起きたときに「2週間分のログをいつでも参照できる」状態が、運用の最低ラインです。
ディスク使用量の確認はls コマンドの基本オプションで確認できます。バックアップ先の容量監視も合わせて行いましょう。
トラブルシュート|よくあるエラーと対処法
「rsync: change_dir failed」が出る
バックアップ元ディレクトリが存在しないか、権限がありません。スクリプト実行ユーザーが対象ディレクトリを読み取れるか確認してください。# ディレクトリ存在確認 ls -la /var/www/html # 実行ユーザー確認 whoami # パーミッション確認 stat /var/www/html
cronからは動かないが手動では動く
最も多いのがPATHの問題です。which rsyncでrsyncのフルパスを確認し、スクリプト内をフルパスに書き換えてください。# rsyncのフルパス確認 which rsync # 出力例: /usr/bin/rsync # cron同等の環境でPATH確認 env -i /bin/sh -c env | grep PATH
バックアップ先ディスクが容量不足
KEEP_DAYSを短くするか、世代管理の削除処理が動いているかログで確認してください。# ディスク使用量確認 df -h /mnt/backup # 古いファイルのサイズ確認(14日以上前) find /mnt/backup -mtime +14 -ls | awk '{sum += $7} END {print sum/1024/1024 " MB"}'
アラートメールが届かない
mailxが動作していないか、SMTPの設定が通っていない可能性があります。まずコマンドラインから直接送信テストをして切り分けます。# mailx の手動テスト送信 echo "テスト" | mailx -s "backup test" admin@example.com # 送信ログを確認 tail -30 /var/log/maillog # RHEL系 tail -30 /var/log/mail.log # Ubuntu系 # mailx がインストールされているか確認 command -v mailx && echo "OK" || echo "未インストール"
本記事のまとめ
シェルスクリプトでバックアップを自動化する手順と、cronで安定稼働させるポイントを解説しました。| やりたいこと | 実装方法 |
|---|---|
| ファイル一式をバックアップする | rsync -av --delete SRC/ DST/ |
| cronで毎日2時に実行する | crontab -e で 0 2 * * * /path/to/backup.sh |
| タイムスタンプ付きでログに記録する | log()関数で [YYYY-MM-DD HH:MM:SS] [LEVEL] 形式を追記する |
| エラーで即停止させる | set -euo pipefail をスクリプト冒頭に記述 |
| 古いログを自動削除する | find LOG_DIR -name "*.log" -mtime +14 -delete |
| 失敗時にメールでアラートを送る | send_alert()関数でmailxを呼び出す(終了コード判定後) |
| 失敗時にSlack等へWebhook通知する | send_alert()内でcurl -X POSTでWebhook URLに送信する |
| cronでコマンドが見つからない場合 | スクリプト内をフルパスで記述、またはcrontabでPATHを明示する |
シェルスクリプトを体系的に学びませんか?
バックアップ自動化のスクリプトをきちんと書けるようになるには、変数・条件分岐・エラー処理の基礎設計を体系的に習得することが近道です。
ネットの切れ端の情報をコピペするだけでなく、現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、『Linuxサーバー構築入門マニュアル(図解60P)』を完全無料でプレゼントしています。
「独学の時間がもったいない」「プロから直接、現場の技術を最短で学びたい」という本気の方には、2日で実務レベルのスキルが身につく【初心者向けハンズオンセミナー】も開催しています。
3,100名以上が実践した「型」を無料で公開中
プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら
- 次のページへ:Linuxのネットワークボンディングを設定する方法|bond0作成・VLANとブリッジのnmcli実践例
- 前のページへ:OllamaのREST APIを業務システムに組み込む方法|curl・Python・社内ツール連携の実装手順
- この記事の属するカテゴリ:Linuxtips・シェルスクリプトへ戻る

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