borgbackup + USB SSDでLinuxの日次バックアップ環境を構築してみた

borgbackup + USB SSDでLinuxの日次バックアップ環境を構築してみた

Fedora Linux上でborgbackupとUSB SSDを使い、重複排除・圧縮・暗号化対応の日次バックアップ環境を構築しました。初回7.6GB・約20万ファイルが55%削減の3.42GBに収まり、2回目以降は既存チャンクを再利用した差分バックアップが可能です。
2026.07.20

はじめに

AIエージェントに開発作業を任せる機会が増え、意図しないファイル削除や上書きが起きるリスクも高まってきました。主要な成果物こそGitで管理していますが、ホームディレクトリ配下すべてのGit管理となると現実的ではありません。macOS環境ではTime MachineやGoogle Driveで対処してきたものの、Linux開発環境を使い始めたため同等のバックアップ環境が必要になりました。

borgbackupは可変長チャンク単位の重複排除・圧縮・暗号化・世代管理を備え、増分バックアップを効率的に保存できるツールです。

https://borgbackup.readthedocs.io/

本記事では、USB SSDの準備からborgリポジトリの初期化、バックアップスクリプトの作成、systemd user timerによる日次実行までを構築しました。初回バックアップの実測結果と復元パターンもあわせて確認しています。

検証内容

検証環境

項目 内容
OS Fedora Linux 43 (Asahi Remix / aarch64)
borg 1.4.4 (dnf)
USB SSD 500GB
バックアップ対象 ユーザーのホームディレクトリ(約7.6GB、約20万ファイル)

USB SSD の準備

まず接続したUSB SSDのデバイス名を確認します。容量と接続バス(TRAN 列)を見て、対象が目的のUSB SSDであることを特定します。

$ lsblk -o NAME,SIZE,TYPE,MODEL,TRAN
NAME          SIZE TYPE MODEL             TRAN
sda         465.8G disk SSD-PSTA/D        usb

SSD-PSTA/Dusb バスで接続された465.8GBのディスクとして見えています。このデバイスにGPTラベルとext4パーティションを作成し、フォーマットします。

$ sudo parted /dev/sda --script mklabel gpt
$ sudo parted /dev/sda --script mkpart primary ext4 0% 100%
$ sudo mkfs.ext4 -L borgbackup /dev/sda1

フォーマット後、/mnt/backup にマウントするようfstabへ登録します。

$ sudo mkdir -p /mnt/backup
$ sudo blkid /dev/sda1

取得したUUIDを使ってfstabに追記します。

# /etc/fstab
UUID=<your-uuid>  /mnt/backup  ext4  defaults,nofail,x-systemd.device-timeout=5s  0  2

nofail により、デバイスが存在しない場合でもマウント失敗をエラー扱いにせずブートを継続します。x-systemd.device-timeout=5s はデバイス待機のタイムアウトを短縮し、未接続時の起動遅延を防ぎます。

$ sudo mount /mnt/backup
$ sudo chown $USER:$USER /mnt/backup

borgbackup のインストール

borgbackupはFedoraの公式リポジトリからdnfで導入できます。

$ sudo dnf install -y borgbackup
$ borg --version
borg 1.4.4

borgリポジトリの初期化

暗号化に使うパスフレーズを生成します。ここではPythonの secrets モジュールを利用しました。

$ python3 -c "import secrets; print(secrets.token_urlsafe(24))"

生成したパスフレーズを環境変数に渡し、リポジトリを初期化します。暗号化方式には repokey-blake2 を指定しました。AES-CTR-256による暗号化と、BLAKE2b-256による認証・チャンクID生成を組み合わせる方式です。ローカルバックアップでは鍵管理が簡便なrepokey方式が適します。

$ export BORG_PASSPHRASE='Abc123xYz-your-passphrase-here_'
$ borg init --encryption=repokey-blake2 /mnt/backup/borg

初期化が完了したら unset BORG_PASSPHRASE で環境変数を削除します。定常運用ではsystemd serviceの EnvironmentFile 経由でパスフレーズを渡します。

repokey 方式では暗号鍵がリポジトリ内に格納されます。SSDの紛失・故障に備えて鍵をエクスポートします。

$ borg key export /mnt/backup/borg /path/to/borg-key-backup.txt

バックアップスクリプト

圧縮方式は速度と圧縮率のバランスが良い zstd,3 を採用しました。開発環境で再生成可能なディレクトリ(キャッシュ、依存パッケージ、ビルド成果物など)は除外し、バックアップサイズと所要時間を抑えます。世代管理は日次7・週次4・月次3を保持し、直近1週間は日単位で戻せるようにしつつ、それ以前は週次・月次で段階的に間引く設計です。実際の保持期間はバックアップの実行頻度や欠損日により変動します。

#!/bin/bash
# ~/tool/backup/backup.sh
set -euo pipefail

REPO="/mnt/backup/borg"
SOURCE="$HOME"
export BORG_PASSPHRASE="${BORG_PASSPHRASE:?Set BORG_PASSPHRASE before running}"
export BORG_REPO="$REPO"

ARCHIVE_NAME="ws-{now:%Y-%m-%dT%H:%M:%S}"
LOG_FILE="$HOME/tool/backup/backup.log"

log() {
    echo "[$(date -Iseconds)] $*" | tee -a "$LOG_FILE"
}

# 前提チェック
if ! mountpoint -q /mnt/backup; then
    log "SKIP: /mnt/backup is not mounted."
    exit 0
fi

# バックアップ実行
log "Starting backup: $SOURCE -> $REPO"

borg create                             \
    --verbose                           \
    --filter AME                        \
    --stats                             \
    --show-rc                           \
    --compression zstd,3                \
    --exclude-caches                    \
    --exclude "sh:$SOURCE/.cache"          \
    --exclude "sh:$SOURCE/**/node_modules" \
    --exclude "sh:$SOURCE/**/__pycache__"  \
    --exclude "sh:$SOURCE/**/.venv"        \
    --exclude "sh:$SOURCE/**/.tox"         \
    --exclude "sh:$SOURCE/**/target"       \
    --exclude "sh:$SOURCE/**/.terraform"   \
    --exclude "sh:$SOURCE/**/dist"         \
    --exclude "sh:$SOURCE/**/.git/objects/pack" \
    ::${ARCHIVE_NAME}                   \
    "$SOURCE"                           \
    2>&1 | tee -a "$LOG_FILE"

# Prune(世代管理)
log "Pruning old archives..."
borg prune                              \
    --list                              \
    --show-rc                           \
    --keep-daily    7                   \
    --keep-weekly   4                   \
    --keep-monthly  3                   \
    2>&1 | tee -a "$LOG_FILE"

# コンパクション(回収対象セグメントがあれば領域を回収)
borg compact 2>&1 | tee -a "$LOG_FILE"

log "Backup finished."
borg info 2>&1 | tee -a "$LOG_FILE"

スクリプト冒頭の mountpoint -q チェックにより、USB SSDが未接続の場合はバックアップを実行せず正常終了します。パスフレーズはスクリプト実行前に export BORG_PASSPHRASE='...' で環境変数に設定しておく前提です。パスワードマネージャーからコピペで渡します。

systemd user timer で日次実行

スケジュール実行にはsystemd user timerを使います。まずservice定義です。ConditionPathIsMountPoint でSSDが未接続なら起動条件を満たさずスキップします。NiceIOSchedulingClass=idle でバックアップ処理を低優先度に抑え、他の作業への影響を減らします。

# ~/.config/systemd/user/ws-backup.service
[Unit]
Description=Borg backup home directory to USB SSD
ConditionPathIsMountPoint=/mnt/backup

[Service]
Type=oneshot
ExecStart=%h/tool/backup/backup.sh
EnvironmentFile=%h/tool/backup/.env
Nice=10
IOSchedulingClass=idle

EnvironmentFile でパスフレーズを記載したファイルを読み込みます。ファイルにはパスワードマネージャーからコピーした値を設定します。

# ~/tool/backup/.env (chmod 600)
BORG_PASSPHRASE=Abc123xYz-your-passphrase-here_

続いてtimer定義です。毎日03:00に起動し、RandomizedDelaySec=300 で最大5分のランダム遅延を加えます。Persistent=true により、起動予定時刻にマシンが停止していた場合は次回起動時に遅延実行されます。

# ~/.config/systemd/user/ws-backup.timer
[Unit]
Description=Daily borg backup timer

[Timer]
OnCalendar=*-*-* 03:00:00
RandomizedDelaySec=300
Persistent=true

[Install]
WantedBy=timers.target

timerを有効化して状態を確認します。

$ systemctl --user daemon-reload
$ systemctl --user enable --now ws-backup.timer
$ systemctl --user status ws-backup.timer
● ws-backup.timer - Daily borg backup timer
     Active: active (waiting)
    Trigger: Mon 2026-07-21 03:02:22 JST; 23h left

Active: active (waiting) となり、次回トリガーが翌日03:02(ランダム遅延込み)に設定されました。

なお、この構成はデスクトップ利用でログインセッション中にtimerが動作する前提です。SSH接続のみでユーザーセッションが常駐しないサーバー環境では、loginctl enable-linger <user> を実行してください。ユーザーのログインがなくてもuserサービスが動作するようになります。

初回バックアップの実行と結果

手動でスクリプトを実行し、初回バックアップを取得しました。

$ ~/tool/backup/backup.sh

borg create --stats の出力結果です。

Archive name: ws-2026-07-20T00:22:52
Duration: 2 minutes 18.09 seconds
Number of files: 204,884
                       Original size      Compressed size    Deduplicated size
This archive:                7.58 GB              5.75 GB              3.42 GB

約20万ファイル・元サイズ7.58GBが、zstd,3 圧縮で5.75GB、さらに重複排除で3.42GB(約55%削減)まで縮小し、所要時間は2分18秒でした。

2回目(日次自動実行)の結果

systemd timerによる自動実行で、2回目のバックアップが取得されました。

Archive name: ws-2026-07-20T03:02:56
Duration: 40.07 seconds
Number of files: 204,922
                       Original size      Compressed size    Deduplicated size
This archive:                7.58 GB              5.75 GB            127.80 kB
All archives:               15.16 GB             11.51 GB              3.42 GB

重複排除後の差分はわずか127.80 kBで、所要時間は40秒でした。前回から大きな変更がなければ、既存チャンクの再利用により、リポジトリに新規追加されるデータ量を小さく抑えられます。

アーカイブの確認と活用

リポジトリ情報を確認します。

$ borg info /mnt/backup/borg
Repository ID: 2e7cb78e...
Encrypted: Yes (repokey BLAKE2b)
                       Original size      Compressed size    Deduplicated size
All archives:               15.16 GB             11.51 GB              3.42 GB
                       Unique chunks         Total chunks
Chunk index:                   75812               414144

Encrypted: Yes (repokey BLAKE2b) で暗号化が有効なことを確認できます。アーカイブ一覧は borg list で取得します。

$ borg list /mnt/backup/borg
ws-2026-07-20T00:22:52               Mon, 2026-07-20 00:22:52 [fa94ad80...]
ws-2026-07-20T03:02:56               Mon, 2026-07-20 03:02:56 [c3b17e22...]

アーカイブ名を指定してパスを渡すと、特定ディレクトリ配下のファイルリストを確認できます。

$ borg list /mnt/backup/borg::ws-2026-07-20T00:22:52 home/<user>/projects/my-project/ | head -15
ファイルリスト出力
drwxrwsr-x user   user          0 Sun, 2026-07-12 04:35:14 home/<user>/projects/my-project
-rw-rw-r-- user   user        139 Sun, 2026-06-07 18:01:59 home/<user>/projects/my-project/monitor.timer
-rwxrwxr-x user   user       4543 Mon, 2026-06-15 00:49:30 home/<user>/projects/my-project/monitor.sh
-rw-rw-r-- user   user        149 Sun, 2026-06-07 18:01:59 home/<user>/projects/my-project/monitor.service
drwxrwsr-x user   user          0 Mon, 2026-07-20 00:04:47 home/<user>/projects/my-project/data
-rw-rw-r-- user   user      21533 Sun, 2026-06-07 23:56:29 home/<user>/projects/my-project/data/2026-06-07.jsonl
-rw-rw-r-- user   user      92911 Mon, 2026-06-08 23:57:46 home/<user>/projects/my-project/data/2026-06-08.jsonl
-rw-rw-r-- user   user      92903 Tue, 2026-06-09 23:58:46 home/<user>/projects/my-project/data/2026-06-09.jsonl
-rw-rw-r-- user   user      93272 Wed, 2026-06-10 23:57:46 home/<user>/projects/my-project/data/2026-06-10.jsonl
-rw-rw-r-- user   user      93272 Thu, 2026-06-11 23:56:16 home/<user>/projects/my-project/data/2026-06-11.jsonl
-rw-rw-r-- user   user      93629 Fri, 2026-06-12 23:59:16 home/<user>/projects/my-project/data/2026-06-12.jsonl
-rw-rw-r-- user   user      93272 Sat, 2026-06-13 23:57:46 home/<user>/projects/my-project/data/2026-06-13.jsonl
-rw-rw-r-- user   user      93272 Sun, 2026-06-14 23:58:16 home/<user>/projects/my-project/data/2026-06-14.jsonl
-rw-rw-r-- user   user     139081 Mon, 2026-06-15 23:57:17 home/<user>/projects/my-project/data/2026-06-15.jsonl
-rw-rw-r-- user   user     138860 Tue, 2026-06-16 23:55:17 home/<user>/projects/my-project/data/2026-06-16.jsonl

これらの操作にはパスフレーズが必要です。borgへのパスフレーズの渡し方にはいくつかの方法があり、用途に応じて使い分けます。

方法 用途 留意点
BORG_PASSPHRASE (export) 対話セッション、スクリプト プロセス環境変数に残る
BORG_PASSCOMMAND 外部コマンドで都度取得 パスワードマネージャー等と連携可能。安全性はコマンド実装と秘密情報の保管方法に依存

本記事ではパスワードマネージャーからコピペした値を BORG_PASSPHRASE に渡す方式を採用しました。systemd timerからの自動実行では、前述の EnvironmentFile 経由でスクリプトに渡しています。

$ export BORG_PASSPHRASE='Abc123xYz-your-passphrase-here_'
$ borg list /mnt/backup/borg

BORG_PASSCOMMAND を使えば外部コマンドの標準出力からパスフレーズを取得できます。パスワードマネージャーのCLIと組み合わせることで、平文ファイルを保持しない構成も実現できます。

1ファイル復元のパターン

1ファイルだけ取り出す代表的な3パターンを確認しました。

1つ目は、ルートに移動して元のパスへそのまま上書き復元する方法です。誤削除したファイルを即座に元の場所へ戻したいときに使います。

$ cd /
$ borg extract /mnt/backup/borg::ws-2026-07-20T00:22:52 \
    home/<user>/projects/my-project/SPEC.md
-rw-rw-r--. 1 user user 5575  6月 15 00:50 /home/<user>/projects/my-project/SPEC.md

2つ目は、作業用ディレクトリへ展開する方法です。アーカイブ内のパス構造を保ったまま展開されるため、現在のファイルと差分を確認してから反映したい運用に向きます。

$ mkdir -p /tmp/borg-restore
$ cd /tmp/borg-restore
$ borg extract /mnt/backup/borg::ws-2026-07-20T00:22:52 \
    home/<user>/projects/my-project/SPEC.md

展開先は /tmp/borg-restore/home/<user>/projects/my-project/SPEC.md となります。

3つ目は、--strip-components でパスの先頭階層を削り、ファイルをフラットに展開する方法です。ディレクトリ構造は不要でファイル単体だけほしいときに便利です。

$ mkdir -p /tmp/borg-restore-flat
$ cd /tmp/borg-restore-flat
$ borg extract --strip-components 4 /mnt/backup/borg::ws-2026-07-20T00:22:52 \
    home/<user>/projects/my-project/SPEC.md

--strip-components 4home/<user>/projects/my-project/ の4階層が除かれます。展開先は /tmp/borg-restore-flat/SPEC.md になります。

3パターンの使い分けは次のとおりです。

パターン コマンド 展開先 用途
元パスに上書き cd / && borg extract ... /home/<user>/.../file 即座に復元したい時
別ディレクトリ cd /tmp/restore && borg extract ... /tmp/restore/home/<user>/.../file 差分確認してから反映
フラット展開 cd /tmp/flat && borg extract --strip-components N ... /tmp/flat/file ファイル単体だけほしい時

まとめ

初回7.58GBが重複排除後3.42GBに収まり、2回目の日次実行では差分127 kB・40秒で完了しました。以降の追加容量は日々の変更量に依存しますが、500GBのSSDであれば当面の運用には十分な余裕があります。borgbackupとUSB SSD、systemd user timerの組み合わせで、設定ファイル数個の手軽さで日次バックアップ環境を構築できました。

なお、Amazon Linux 2023でもSPALリポジトリ経由でborgbackupパッケージが提供されています。今回はパッケージの存在までの確認ですが、EC2上で特定パス単位のバックアップや世代管理を手元で完結させたい場面では選択肢になりそうです。

https://dev.classmethod.jp/articles/amazon-linux-2023-spal-repository/

この記事をシェアする

関連記事