シェルスクリプトでバックアップ自動化を作る実践|cronとログ設計まで

宮崎智広 この記事の監修:宮崎智広(Linux実務・教育歴20年以上・受講者3,100名超)
HOME > Linux技術 リナックスマスター.JP(Linuxマスター.JP) > Linuxtips, シェルスクリプト > シェルスクリプトでバックアップ自動化を作る実践|cronとログ設計まで
「毎晩バックアップを手動で動かすのは面倒だ」 「cronで自動化したいけど、エラーが出ても気づけない」 「スクリプトが失敗しても翌朝まで気づかない」 そんな悩みを抱えているサーバー管理者は多いはずです。

バックアップの自動化は、シェルスクリプトとcronを組み合わせるだけで実現できます。ただし、動けばOKのスクリプトと、現場で長期間安定稼働するスクリプトには大きな差があります。エラーを検知してアラートを送る仕組み、ログへの記録、古いファイルの世代管理、そしてcronの二重起動防止。これらをまとめて設計してはじめて「運用に耐えるバックアップ」になります。

この記事では、シェルスクリプトでバックアップ処理を書き、cronで定期実行し、ログに残し、失敗時にアラートを送るところまでを実践的に解説します。さらにcronジョブの二重起動を防ぐflockの使い方、loggerコマンドによるsyslogへの記録まで加えました。RHEL 9.4 / Ubuntu 24.04 LTSで動作確認済みのコードを使って、コピーして使えるレベルまで仕上げます。

この記事のポイント

・シェルスクリプトでrsyncを使ったバックアップ処理を安全に書ける
・trapでどの段階の失敗でもアラートを確実に届ける設計にできる
・cronで定期実行するときの環境変数・パスの落とし穴を避けられる
・flockで二重起動を防ぎcronジョブの競合を確実に回避できる
・バックアップ失敗時にメール・Webhook・syslogでアラートを自動送信できる


シェルスクリプトでバックアップ自動化を作る実践|cronとログ設計まで

「このままじゃマズい」と感じていませんか?
参考書を開く気力もない、同年代に取り残される不安——
でも安心してください。プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

なぜシェルスクリプトでバックアップを自動化するのか

手動バックアップの最大の問題は「忘れる」ことです。深夜のメンテナンス後、重要な作業前のバックアップを取り忘れてトラブルになったケースは数え切れません。

私がセミナーで3,100名以上を指導してきた中で、バックアップが原因で復旧できなかったという相談を何度も受けてきました。ほぼ全てのケースで共通しているのが「自動化していなかった」という点です。さらに、「スクリプトは動かしていたが、失敗しても気づかなかった」という問題も少なくありません。動いているように見えて、実はエラーが続いていたというケースです。

cronで自動化すれば「忘れる」問題は解決できます。ただし、cronのデフォルト設定のままでは3つの運用上の盲点が残ります。

・実行ログが残らない:cronはデフォルトで出力をメールスプール(/var/mail/root)に送ろうとしますが、MTAが設定されていない環境では出力が消えます。MAILTO="" でメールを無効にしている環境では、スクリプトの出力が完全に捨てられます
・エラーで終了しても気づかない:cronのシステムログ(/var/log/cron または journalctl -u crond)にはスクリプトが「起動した」記録は残りますが、「失敗した」情報は記録されません
・実行時間や処理状況が把握できない:どのジョブが何秒かかったのか、デフォルトのcron運用では記録されません。障害発生後に「いつから失敗していたのか」が分からなくなり、原因調査に時間を取られます

シェルスクリプトにログ設計・エラー検知・アラート通知を組み込むことで、これらの盲点をまとめて解消できます。さらに以下のメリットがあります。

・一貫性:毎回同じ手順でバックアップが取れる
・記録:ログに残すことで後から確認できる
・検知:終了コードでエラーを即座に把握できる
・世代管理:古いバックアップを自動削除してディスクを節約できる
・アラート:バックアップが失敗したらメールやSlackに即通知できる
・確実なアラート:trapで異常終了を捕捉し、どの段階の失敗でも通知が届く
・二重起動防止:flockでcronジョブの競合を自動回避できる

シェルスクリプトでバックアップ自動化を作る実践|cronとログ設計まで - 解説1

バックアップスクリプトの基本構成を作る

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" # アラート送信先 LOCK_FILE="/var/lock/backup.lock" # flock用ロックファイル # ---- ディレクトリ確認 ---- mkdir -p "${DST_DIR}" "${LOG_DIR}" # ---- ログ出力関数 ---- log() { local level="$1"; shift echo "[$(date '+%Y-%m-%d %H:%M:%S')] [${level}] $*" | tee -a "${LOG_FILE}" }

変数を先頭にまとめておくことで、バックアップ元やバックアップ先を変更する際にスクリプト全体を読む必要がありません。保守性が大きく上がります。log()関数を定義しておくと、タイムスタンプ付きのログ出力を1行で呼び出せるため、スクリプト全体で一貫したログフォーマットになります。

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}"

・-a(--archive):パーミッション・タイムスタンプ・シンボリックリンクを保持する
・-v(--verbose):転送ファイルを出力してログに残す
・--delete:バックアップ元で削除されたファイルをバックアップ先からも削除する
・2>&1:標準エラーを標準出力にまとめてログファイルに書き込む

rsyncのフルパス(/usr/bin/rsync)を指定しているのは、cronの実行環境でPATHが省略されていても確実に動かすためです。コマンドがフルパス指定でなければ「command not found」になるケースは実務で非常に多いです。

rsyncのオプションは組み合わせで動作が変わります。--deleteを外すと削除が反映されない「増分バックアップ」になります。用途に合わせて選んでください。

除外したいディレクトリやファイルがある場合は--exclude-fromオプションを使うと便利です。除外パターンを外部ファイルに記述しておけば、スクリプト本体を修正せずに管理できます。

# /etc/backup/exclude.conf(1行に1パターン、#でコメント) # /var/www/html/cache/ /var/www/html/tmp/ *.log *.swp *.pid

# --exclude-from で設定ファイルを指定する /usr/bin/rsync -av --delete --exclude-from=/etc/backup/exclude.conf "${SRC_DIR}/" "${DST_DIR}/" >> "${LOG_FILE}" 2>&1

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

rsyncの終了コード24は「転送中にソースファイルが消えた」状態です。ログが高速にローテーションする環境では頻繁に発生します。エラーではなく警告として扱う設計にしておくと、不要なアラートが減ります。終了コード0以外を全て異常扱いにすると、24のような正常範囲のコードでもアラートが飛んでしまいます。

4. trapで異常終了を検知し後片付けを自動化する

set -euo pipefail を設定すると、コマンドが失敗した瞬間にスクリプトが終了します。安全ではあるものの、一つ問題があります。rsyncが異常終了した場合、終了コードを確認してアラートを送る処理にたどり着く前にスクリプトが終了してしまいます。結果として、バックアップが失敗してもアラートが届かないという事態になります。

trap コマンドでEXITシグナルを捕捉すれば、正常終了でも異常終了でも必ず後片付け関数が実行されます。

# ---- 異常終了ハンドラ(log()とsend_alert()の定義直後に書く)---- on_exit() { local rc=$? # $? は即時保存しないと後続コマンドで上書きされる if [ "${rc}" -ne 0 ]; then log "ERROR" "バックアップが異常終了しました(終了コード: ${rc})" send_alert "バックアップ異常終了(終了コード: ${rc})" fi } trap on_exit EXIT

on_exit 関数の最初で local rc=$? として終了コードを即時保存しています。trap内でそのまま $? を使うと、後続のコマンド(例: log関数の実行)が終了コードを上書きするため、元の終了コードが取れなくなります。この1行のために変数に保存しているわけです。

trap on_exit EXIT はスクリプト内で log() と send_alert() を定義した直後に書いてください。関数定義より前に trap を書くと、EXIT時に関数が見つからずエラーになります。

rsyncの終了コード24(転送中のファイル消失)は正常範囲なので、set -e の影響を受けないよう set +e / set -e で囲んで個別に処理します(完成版スクリプトを参照)。

古いバックアップを自動削除する世代管理

バックアップが溜まり続けるとディスクが枯渇します。findコマンドで古いログ・バックアップを定期削除しましょう。

# ---- 古いログを削除(14日以上前) ---- /usr/bin/find "${LOG_DIR}" -maxdepth 1 -name "backup_*.log" -mtime +${KEEP_DAYS} -delete log "INFO" "${KEEP_DAYS}日超のログを削除しました"

-maxdepth 1 を付けることでサブディレクトリを除外します。ログディレクトリ直下のファイルだけを対象にするため、将来的にディレクトリ構造を変えても意図しないファイルを削除するリスクを防げます。

NFS等のネットワークマウント先が含まれるディレクトリでfindを使う場合は-xdevオプションを追加してください。マウントポイントをまたいだ別ファイルシステムを検索対象から外せるので、NFS障害時にfindがハングするトラブルを防げます。

# -xdev でマウントポイントをまたがない(NFS等を含む環境で有効) /usr/bin/find "${LOG_DIR}" -xdev -maxdepth 1 -name "backup_*.log" -mtime +${KEEP_DAYS} -delete

rsync --deleteを使ったミラーリング型では1世代分しか保持しないのでログだけ削除すればOKです。複数世代を保持したい場合は、DST_DIRにタイムスタンプを含めたサブディレクトリを作り、古いサブディレクトリをfindで削除します。

# 複数世代バックアップ(スナップショット型)の例 # 日付ごとのサブディレクトリにバックアップする 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日以上前のスナップショットを削除(-maxdepth 1で直下のみ対象) /usr/bin/find "${DST_DIR}" -maxdepth 1 -mindepth 1 -type d -mtime +${KEEP_DAYS} -exec rm -rf {} + log "INFO" "14日超のスナップショットを削除しました"

スナップショット型を使うと、「3日前の状態に戻したい」という要件に対応できます。ただし毎日フルコピーするのでディスク消費が大きくなります。まずは「タイムスタンプ付きサブディレクトリ+古いフォルダをfindで削除」のシンプルな構成から始めることを推奨します。

シェルスクリプトでバックアップ自動化を作る実践|cronとログ設計まで - 解説2

cronで定期実行するときの落とし穴

スクリプトが手動では動くのにcronでは動かない、という問題はシェルスクリプトの落とし穴としてよく知られています。

5. 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

crontabの先頭でPATHを明示的に設定しておくことを強く推奨します。rsyncやその他のコマンドが「command not found」になる原因の大半はPATHの問題です。

また、スクリプト内のコマンドはフルパスで書くと安全です。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

「手元では動く」「cronでは動かない」の大半はこの環境差分が原因です。上記コマンドで確認したフルパスをスクリプトに直書きするのが最も確実な解決策です。

システム全体のcronジョブとして /etc/cron.d/ に登録する場合は、以下の形式も利用できます。スクリプト内でアラートを管理するなら MAILTO="" でcronの標準出力メールを無効にして二重通知を防げます。

# /etc/cron.d/backup(システムcronへの登録例) SHELL=/bin/bash PATH=/usr/local/bin:/usr/bin:/bin:/sbin:/usr/sbin MAILTO="" # cronの標準出力メールを無効化(スクリプト内で通知を管理) # 毎日 2:00 に root で実行 0 2 * * * root /usr/local/bin/backup.sh

6. スクリプトに実行権限を付ける

スクリプトを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/

chmod 700で「所有者のみ実行可能」に設定します。バックアップスクリプトはシステム全体に影響するため、一般ユーザーには実行させないのが鉄則です。

セミナーでよく聞かれる質問が「なぜ755ではなく700にするのか」です。バックアップスクリプトは内部に変数でパスを持ち、誤操作で別の場所を上書きしてしまうリスクがあります。root専用に絞っておくことで事故を防ぎます。

7. flockで二重起動を防ぐ

バックアップの転送量が多い日にrsyncが終わらないまま次のcronがトリガーされると、2つのrsyncプロセスが同じ転送先に同時に書き込む競合が起きます。バックアップファイルが壊れる原因になるため、二重起動防止は設計に組み込んでおくべき重要な要素です。

flockコマンドを使うと、1つ目のスクリプトが実行中の間はロックファイルを保持し、2つ目の起動を自動的にスキップできます。

# /etc/cron.d/backup(flock で二重起動を防ぐ版) SHELL=/bin/bash PATH=/usr/local/bin:/usr/bin:/bin:/sbin:/usr/sbin MAILTO="" # -n: ロックが取得できない場合は待機せず即時終了(スキップ) 0 2 * * * root flock -n /var/lock/backup.lock /usr/local/bin/backup.sh >> /var/log/backup/cron.log 2>&1

flock -n の -n は「non-blocking(非ブロッキング)」の意味です。前回のバックアップが実行中でロックを保持している場合は、今回の起動を待機させずに即座に終了します。これで二重実行による競合が起きません。

ロックの状態確認や強制解除が必要な場面もあります。

# ロックが保持中かどうかを確認する flock -n /var/lock/backup.lock echo "ロック取得OK(未実行)" || echo "ロック保持中(バックアップ実行中またはロック残留)" # プロセスが既に死んでいる場合のロック強制解除 rm -f /var/lock/backup.lock

flockはファイルベースのロックのため、スクリプトがkillされた後でもOSが次回起動時にロックを解放します。ただしロックファイル自体は残ることがあるので、次のcron実行を確実に動かしたい場合は手動でrm -fして消しておきましょう。

バックアップ失敗時にアラートを送る

スクリプトが失敗しても誰も気づかない状態は「バックアップがない」と同義です。運用で最も重要なのは、エラーを自動で検知して担当者に即通知する仕組みです。

8. 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が空の場合は通知をスキップします。両方を設定してもかまいませんし、どちらか一方だけでも機能します。

9. 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系

メール送信が通らない環境(クラウドのEC2等)では、SMTPリレーの設定が必要です。Webhookが使える環境(Slackを社内で運用しているチームなど)はWebhook通知のほうが設定は簡単です。

10. loggerでシステムログに記録する

メールやWebhookに加えて、loggerコマンドでsyslogにも記録を残すとさらに運用品質が上がります。loggerはシステムのsyslogデーモンにメッセージを書き込む標準コマンドで、ZabbixやCloudWatch Agentなどの監視ツールがsyslogを読んでアラートを上げる仕組みとも連携できます。

# ---- loggerでsyslogに記録する(send_alertに追加するか個別に呼び出す)---- log_to_syslog() { local exit_code="$1" if [ "${exit_code}" -ne 0 ]; then logger -t backup -p user.err \ "FAILED: backup.sh exit=${exit_code} log=${LOG_FILE}" else logger -t backup -p user.info \ "OK: backup.sh log=${LOG_FILE}" fi }

-t backup はsyslogのタグ(ident)です。後でjournalctlで絞り込む際に使います。-p user.err はファシリティ「user」・プライオリティ「err」の指定で、緊急度を明示します。成功時は user.info にしておくと、エラーだけを検索する際に混在しません。

RHEL 9 / Rocky Linux 9でjournalctlを使って確認する例:

# backup タグで絞り込む journalctl -t backup --since "2026-09-18 00:00:00" --no-pager # 出力例(失敗時): # Sep 18 02:01:03 sv01.example.com backup[12345]: FAILED: backup.sh exit=1 log=/var/log/backup/backup_20260918_020100.log # 出力例(成功時): # Sep 18 02:00:09 sv01.example.com backup[12346]: OK: backup.sh log=/var/log/backup/backup_20260918_020000.log

journaldに記録が残ることで「いつから失敗していたか」の追跡が容易になります。メールが届かなかった場合でも、journalctlで過去のすべての実行履歴を確認できます。Zabbixのログ監視アイテムやAWS CloudWatch Logsとの連携を設定すれば、syslogの1行を受け取った時点でアラートを上げる仕組みも実現できます。

スクリプト完成版(コピーして使えるコード)

ここまでのパーツを組み合わせた完成版スクリプトです。trapを加えることで、set -euo pipefailによる即時終了でもアラートが確実に届く設計になっています。flockによる二重起動防止は/etc/cron.dの設定側で行います(後述)。

#!/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 } # ---- 異常終了ハンドラ ---- on_exit() { local rc=$? if [ "${rc}" -ne 0 ]; then log "ERROR" "バックアップが異常終了しました(終了コード: ${rc})" send_alert "バックアップ異常終了(終了コード: ${rc})" logger -t backup -p user.err "FAILED: backup.sh exit=${rc} log=${LOG_FILE}" else logger -t backup -p user.info "OK: backup.sh log=${LOG_FILE}" fi } trap on_exit EXIT log "INFO" "=== バックアップ開始 ===" log "INFO" "元: ${SRC_DIR}" log "INFO" "先: ${DST_DIR}" # ---- rsync実行(終了コード24は転送中消失で正常範囲)---- set +e /usr/bin/rsync -av --delete "${SRC_DIR}/" "${DST_DIR}/" >> "${LOG_FILE}" 2>&1 RSYNC_EXIT=$? set -e if [ "${RSYNC_EXIT}" -eq 0 ]; then log "OK" "バックアップ完了" elif [ "${RSYNC_EXIT}" -eq 24 ]; then log "WARN" "転送中ファイル消失(終了コード24)" else exit "${RSYNC_EXIT}" # on_exit が自動的にアラートを送信する fi # ---- 世代管理 ---- /usr/bin/find "${LOG_DIR}" -maxdepth 1 -name "backup_*.log" -mtime +${KEEP_DAYS} -delete log "INFO" "${KEEP_DAYS}日超のログを削除しました" log "INFO" "=== 処理終了 ==="

冒頭のset -euo pipefailは現場スクリプトの必須設定です。eはエラーで即停止、uは未定義変数の参照をエラー、pipefailはパイプ途中のエラーを拾います。ここにtrap on_exit EXITを加えることで、どの段階で終了しても確実にアラートが届く設計になります。

rsyncの実行前後で set +e / set -e を使い、終了コード24を正常範囲として個別に処理しています。24以外の非ゼロ終了コードは exit "${RSYNC_EXIT}" で明示的に終了し、on_exitハンドラがアラートを送る流れです。on_exit内にloggerの呼び出しを追加したことで、成功・失敗の記録がsyslogにも残ります。

関連記事として、シェルスクリプトのエラー処理全般については「Linux 基本コマンドの解説」も合わせて参照してください。

シェルスクリプトでバックアップ自動化を作る実践|cronとログ設計まで - 解説3

ログファイルの設計と実サーバーでの出力例

ログがきちんと残っているか、実際のサーバーでの出力を見てみましょう。

# 実行後のログ出力例(実サーバー: 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週間分のログをいつでも参照できる」状態が、運用の最低ラインです。

syslogへの記録も合わせて活用すると、ログファイルが何らかの原因で消えた場合でもjournalctlで実行履歴を追跡できます。スクリプトのログとsyslogを両方確認する習慣を付けておくと、障害時の切り分けが格段に速くなります。

# ログファイルの一覧確認 ls -lh /var/log/backup/ # 出力例: # -rw-r--r-- 1 root root 1.2K Jun 23 02:00 backup_20260623_020001.log # -rw-r--r-- 1 root root 1.3K Jun 24 02:00 backup_20260624_020003.log # syslogでの実行履歴確認(journald環境) journalctl -t backup --since "2026-06-01 00:00:00" --no-pager

ディスク使用量の確認は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 "未インストール"

クラウド環境(AWS EC2等)ではデフォルトでSMTPポートがブロックされています。Amazon SESのSMTPリレー設定か、Webhook通知への切り替えを検討してください。

set -euo pipefailでスクリプトが即終了してアラートが届かない

set -euo pipefail を設定したスクリプトでは、rsyncが失敗した瞬間にスクリプトが終了します。その際、終了コードを確認してアラートを送る処理にたどり着く前に終了するため、アラートが届かないことがあります。

本記事の trap on_exit EXIT の設計を使えば解決できます。既存スクリプトに後から追加する場合は、まず動作確認をしてから組み込んでください。

# trapが機能していることを確認するテスト(一時ファイルで試す) #!/bin/bash set -euo pipefail log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] [$1] $2"; } on_exit() { local rc=$? [ "${rc}" -ne 0 ] && log "ERROR" "終了コード: ${rc}" || true } trap on_exit EXIT false # 意図的に失敗させる # 出力: [2026-06-24 02:00:10] [ERROR] 終了コード: 1

false は必ず終了コード1を返すコマンドです。上記のように false を入れて実行し、ERRORログが出ることを確認してから、スクリプト本体に on_exit 関数と trap on_exit EXIT を追加してください。

cronで二重起動が発生してバックアップが競合する

バックアップの転送量が多い日にrsyncが終わらないまま次のcronがトリガーされると、同じ転送先に2つのrsyncが同時に書き込む競合が起きます。ログに「sending」と「receiving」が混在していたり、バックアップファイルが壊れていた場合はこのケースを疑ってください。

# バックアッププロセスが複数起動していないか確認する ps aux | grep backup.sh | grep -v grep # flock のロック状態を確認する flock -n /var/lock/backup.lock echo "未実行(ロックなし)" || echo "実行中またはロック残留"

本記事の「7. flockで二重起動を防ぐ」セクションの設定を/etc/cron.dに組み込むことで、前回の実行が終わるまで次回の起動がスキップされ、競合を防げます。既存のcrontab設定と置き換えて動作確認を行ってください。

journalctlでbackupタグのログが表示されない

syslogへの記録にloggerコマンドを使っている場合、journaldの設定によってはUser Facilityのログが見えないことがあります。

# journaldの設定ファイルを確認する grep -i storage /etc/systemd/journald.conf # Storage=auto(デフォルト)または Storage=persistent であれば保存される # backupタグで確認(直近24時間) journalctl -t backup --since "yesterday" --no-pager # syslogファイルで確認(rsyslog使用環境) grep "backup" /var/log/messages # RHEL系 grep "backup" /var/log/syslog # Ubuntu系

Storage=none の環境ではjournaldがログを保存しません。その場合はrsyslogが別途動いていれば /var/log/messages や /var/log/syslog に記録されます。どちらでも確認できない場合は、スクリプトのログファイル(/var/log/backup/)を直接確認してください。

シェルスクリプトでバックアップ自動化を作る実践|cronとログ設計まで - まとめ

本記事のまとめ

シェルスクリプトでバックアップを自動化する手順と、cronで安定稼働させるポイントを解説しました。

やりたいこと 実装方法
ファイル一式をバックアップする rsync -av --delete SRC/ DST/
特定ディレクトリ・ファイルを除外する rsync -av --exclude-from=/etc/backup/exclude.conf SRC/ DST/
cronで毎日2時に実行する crontab -e で 0 2 * * * /path/to/backup.sh
cronで二重起動を防ぐ flock -n /var/lock/backup.lock /path/to/backup.sh を /etc/cron.d から実行する
タイムスタンプ付きでログに記録する log()関数で [YYYY-MM-DD HH:MM:SS] [LEVEL] 形式を追記する
エラーで即停止させる set -euo pipefail をスクリプト冒頭に記述
どの段階の失敗でもアラートを届ける on_exit関数を定義し trap on_exit EXIT で登録する
古いログを自動削除する find LOG_DIR -maxdepth 1 -name "*.log" -mtime +14 -delete
NFS等をまたがずにfindを実行する find LOG_DIR -xdev -maxdepth 1 -name "*.log" -mtime +14 -delete
失敗時にメールでアラートを送る send_alert()関数でmailxを呼び出す(終了コード判定後)
失敗時にSlack等へWebhook通知する send_alert()内で curl -s -X POST WEBHOOK_URL を実行する
失敗・成功をsyslogに記録する logger -t backup -p user.err "FAILED: ..." をon_exit内で呼び出す
syslogでの実行履歴を確認する journalctl -t backup --since "yesterday" --no-pager
cronでコマンドが見つからない場合 スクリプト内をフルパスで記述、またはcrontabでPATHを明示する
バックアップは「動けばOK」ではなく、障害発生時に確実に復旧できる設計が重要です。本記事で紹介したスクリプトをベースに、自分のサーバー環境に合わせてカスタマイズしてください。

シェルスクリプトを体系的に学びませんか?

バックアップ自動化のスクリプトをきちんと書けるようになるには、変数・条件分岐・エラー処理の基礎設計を体系的に習得することが近道です。
ネットの切れ端の情報をコピペするだけでなく、現場で通用する安全なLinuxサーバー構築の「型」を体系的に身につけたい方へ、『Linuxサーバー構築入門マニュアル(図解60P)』を完全無料でプレゼントしています。

※ 「独学の時間がもったいない」「プロから直接、現場の技術を最短で学びたい」という本気の方には、2日で実務レベルのスキルが身につく【初心者向けハンズオンセミナー】も開催しています。

無料メルマガで学習を続ける

Linuxの実践スキルをメールで毎週お届け。
登録は30秒、解除もいつでも可。

登録無料・いつでも解除できます

暗記不要・1時間後にはサーバーが動く

3,100名以上が実践した「型」を無料で公開中

プロのエンジニアはコマンドを暗記していません。
「現場で使える型」を効率よく使いこなしているだけです。
その「型」を図解60Pにまとめた入門マニュアルを、完全無料でプレゼントしています。

姓・名・メールの3つだけ/30秒/解除は3秒 / 詳細はこちら

Linux無料マニュアル(図解60P) 名前とメールで30秒登録
宮崎 智広

この記事を書いた人

宮崎 智広(みやざき ともひろ)

株式会社イーネットマーキュリー代表。現役のLinuxサーバー管理者として20年以上の実務経験を持ち、これまでに累計3,100名以上のエンジニアを指導してきたLinux教育のプロフェッショナル。「現場で本当に使える技術」を体系的に伝えることをモットーに、実践型のLinuxセミナーの開催や無料マニュアルの配布を通じてLinux人材の育成に取り組んでいる。

趣味は、キャンプにカメラ、トラウト釣り。好きな食べ物は、ラーメンにお酒。休肝日が作れない、酒量を減らせないのが悩み。最近、ドラマ「フライトエンジェル」を観て涙腺が崩壊しました。