SSD Nodes Learn Hosting plans →
指南 Matt Connor作者: Matt Connor

Restic VPS 備份到另一台伺服器

使用 Restic 將 VPS 資料加密、去重並備份到另一台伺服器或物件儲存,透過 systemd 每晚執行,並以還原演練確認備份確實可用。

為什麼同一部伺服器上的備份不算備份

Restic 是免費的開放原始碼備份工具,可將檔案的加密、去重快照傳送至其他位置的儲存庫:第二部 VPS、家中的電腦,或與 S3 相容的物件儲存。本指南說明如何在 Ubuntu 24.04 上完成設定,包括安裝、透過 SFTP 建立儲存庫、執行第一次備份、設定每晚執行的 systemd 計時器、設定保留原則,以及進行可驗證整體運作正常的還原演練。目的地必須是另一部機器,因為與伺服器位於同一部機器上的複本會隨伺服器一同損毀。

伺服器上的 backup/ 目錄只能防止一種情況:意外刪除檔案。若磁碟故障,它無法保留,因為該目錄位於同一部磁碟上。若攻擊者取得 root 權限,它也無法保留,因為攻擊者會先刪除複本。若帳戶操作錯誤導致 VPS 本身遭到刪除,它同樣無法保留。全球效率最低的資料中心嘲諷名為 backup_final_v2_REAL 的 tarball 與資料位於同一個陣列上;這個笑話之所以成立,是因為許多人確實做過完全相同的事。備份必須位於伺服器之外,而 restic 是遵循這項原則時最不麻煩的方式。

Restic 的 4 個核心概念

Repository。 Restic 寫入資料的位置。這是一個使用 restic 自有格式的目錄,內含大量加密 blob,只有 restic 能讀取。請勿手動修改;應透過 restic 指令及 -r 位址操作。

Snapshot。 備份檔案在某個時間點的狀態。每次執行備份都會建立一個 snapshot,每個 snapshot 都能獨立還原,且都代表資料在該時間點的完整副本。

Deduplication。 Restic 會將檔案切分為內容定義的區塊,只上傳 repository 尚未見過的區塊。第一次備份會上傳所有資料;之後每次執行大致只會上傳變更的內容。若 20 GB 的資料在一夜之間變更 50 MB,該次 snapshot 約只需 50 MB,因此保留數十個 snapshot 的成本很低。

預設加密。 Restic repository 一律會加密(AES-256)。除非傳入 --insecure-no-password,否則 restic 拒絕使用空白密碼建立或開啟 repository(restic 0.17.0 及後續版本適用)。因此,依本指南建立的 repository 在每次執行指令時都需要密碼。備份主機或儲存服務供應商只會看到加密 blob。其嚴重後果是:遺失密碼後,資料將永久遺失,這是設計如此。請在不是這台伺服器的位置保留一份密碼副本。這件事非常重要,以下還會再提到 2 次。

在 Ubuntu 24.04 上安裝 restic

sudo apt update && sudo apt install -y restic
restic version

在 Ubuntu 24.04 上,這會安裝 restic 0.16.4;目前上游版本為 0.19.1。版本差距是因為 LTS(長期支援)版本會固定套件版本。不過這裡不受影響:0.16.4 已能完成本指南中的所有操作。如果您需要最新版以取得速度改進,請從 restic 專案的 GitHub releases 頁面下載官方單一二進位檔,使用 bunzip2 解壓縮,並將其安裝至 /usr/local/bin/restic;安裝 restic 不需要其他步驟。

透過 SFTP 在另一部伺服器上建立儲存庫

您需要一部目的地機器。通常會使用第二部小型 VPS,任何具備 SSH 伺服器和可用磁碟空間的機器也都可以。Restic 支援 SFTP(透過 SSH 傳輸檔案),因此備份主機完全不需要安裝任何軟體。本指南使用 10.0.0.12 作為備份主機,並使用名為 restic 的使用者。請勿將該使用者命名為 backup:Ubuntu 和 Debian 在每次安裝時都會提供名為 backup 的保留系統帳戶(uid 34,沒有登入 shell),因此 adduser backup 會失敗,而 ssh backup@... 會建立在 nologin。

夜間工作會以 root 身分在要備份的伺服器上執行,因此 root 需要透過金鑰登入備份主機。請建立沒有密碼片語的專用金鑰,因為凌晨 3am 沒有人員可以輸入密碼片語,然後將金鑰複製過去:

sudo ssh-keygen -t ed25519 -f /root/.ssh/id_ed25519 -N "" -C "web1-restic"
sudo ssh-copy-id -i /root/.ssh/id_ed25519.pub restic@10.0.0.12
sudo ssh restic@10.0.0.12 true && echo key login works

如果您不熟悉金鑰,SSH 金鑰管理基礎說明了其運作方式、權限,以及日後如何撤銷金鑰。

接著處理儲存庫密碼。請將強式密碼產生至僅限 root 存取的檔案:

openssl rand -base64 32 | sudo tee /root/.restic-password
sudo chmod 600 /root/.restic-password

現在先將該密碼複製到密碼管理器,再繼續操作。如果此 VPS 故障,儲存庫和該密碼可以還原所有內容;沒有密碼的儲存庫則無法還原任何內容。

初始化儲存庫:

sudo restic -r sftp:restic@10.0.0.12:/srv/restic/web1 --password-file /root/.restic-password init
created restic repository 9f3c2a1b0d at sftp:restic@10.0.0.12:/srv/restic/web1

另一個目的地是相容於 S3 的物件儲存。如果您不想執行第二部機器,這是適合的選擇。任何相容於 S3 的儲存貯體都以相同方式運作;只有位址和 2 個認證變數需要變更:

export AWS_ACCESS_KEY_ID=your-key-id
export AWS_SECRET_ACCESS_KEY=your-secret-key
sudo -E restic -r s3:https://s3.example.com/web1-backups --password-file /root/.restic-password init

init 之後的所有內容對兩種目的地都相同。本指南其餘部分會顯示 SFTP 位址;請替換為您的位址。

第一次備份並排除項目

備份無法重新安裝取得的資料,不要備份整個檔案系統。重新安裝後,作業系統可以恢復;但您的組態和資料無法恢復。對一般 VPS 而言,這通常包括 /etc、/home,以及應用程式儲存狀態的位置,例如 /srv 或 /var/www。請排除快取,因為快取容量大、每天都會變動,而且可以自行重建:

sudo restic -r sftp:restic@10.0.0.12:/srv/restic/web1 --password-file /root/.restic-password backup /etc /home /srv --exclude '/home/*/.cache'
Files:        4181 new,     0 changed,     0 unmodified
Added to the repository: 731.204 MiB (312.418 MiB stored)
snapshot 5b8a3f2c saved

第一次執行會上傳所有內容,因此需要一些時間。再次執行相同的命令後,幾秒內即可完成,只會顯示少數檔案變更及新增幾 MiB,因為重複資料刪除只會上傳新的區塊。列出現有內容:

sudo restic -r sftp:restic@10.0.0.12:/srv/restic/web1 --password-file /root/.restic-password snapshots

每個快照都會顯示 ID、時間及其中包含的路徑。還原時會使用這些 ID。

使用 systemd 計時器執行每晚工作

每次執行命令都輸入儲存庫位址很快就會變得麻煩,而手動執行的備份通常不到一個月就會停止。這兩個問題都能透過一個指令碼和一個計時器解決。指令碼會設定 restic 讀取的兩個環境變數 RESTIC_REPOSITORY 和 RESTIC_PASSWORD_FILE,因此其中的每個命令都能保持簡短:

sudo nano /usr/local/bin/restic-backup.sh
#!/usr/bin/env bash
set -euo pipefail
export RESTIC_REPOSITORY='sftp:restic@10.0.0.12:/srv/restic/web1'
export RESTIC_PASSWORD_FILE=/root/.restic-password

restic backup /etc /home /srv --exclude '/home/*/.cache'
restic forget --keep-daily 7 --keep-weekly 4 --keep-monthly 6 --prune
restic check
sudo chmod 700 /usr/local/bin/restic-backup.sh

forget 和 check 行會在接下來兩節說明。接著設定排程:建立一個執行該指令碼的 oneshot 服務,再建立一個每天 03:00 觸發該服務的計時器。這裡使用計時器優於 cron 行,因為執行記錄會寫入 journal,而且 Persistent=true 會在伺服器停機後恢復運作時,立即執行錯過的備份。

# /etc/systemd/system/restic-backup.service
[Unit]
Description=Nightly restic backup
Wants=network-online.target
After=network-online.target

[Service]
Type=oneshot
ExecStart=/usr/local/bin/restic-backup.sh
# /etc/systemd/system/restic-backup.timer
[Unit]
Description=Run the nightly restic backup

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

[Install]
WantedBy=timers.target

啟用計時器,然後手動執行一次服務並監看其運作情形:

sudo systemctl daemon-reload
sudo systemctl enable --now restic-backup.timer
sudo systemctl start restic-backup.service
sudo journalctl -u restic-backup.service -f

systemctl list-timers 會顯示下一次執行的時間。您也可以產生這兩個 unit 檔案,而不必手動輸入:

ToolGenerate the backup service and timer

這兩個檔案背後的完整模式,包括行事曆語法,以及服務可使用的強化安全性指令,請參閱在 VPS 上將程式作為 systemd 服務執行。

備份在還原成功前都只是傳聞

請將這句話視為必須遵守的原則。備份工作每晚都成功執行,只能證明工作有執行;不能證明資料可以還原。以下 2 項檢查可以補上這個缺口。

首先是 restic check,腳本已經每晚執行此指令。它會驗證 repository 結構與索引,因此備份主機上的無聲損毀能在隔天晚上被發現,而不是等到還原當天才發現。每月執行一次更深入的版本。該版本會下載並以密碼學方式驗證實際資料中隨機抽取的十分之一:

sudo -i
export RESTIC_REPOSITORY='sftp:restic@10.0.0.12:/srv/restic/web1'
export RESTIC_PASSWORD_FILE=/root/.restic-password
restic check --read-data-subset=10%

由於每次抽取的子集都是隨機選擇,restic 文件明確指出,無論執行多少次,都不保證能涵蓋每個 pack file。若希望每月執行時逐步檢查整個 repository,又不必支付完整下載的成本,請改用固定群組形式:某個月執行 --read-data-subset=1/10,下個月執行 2/10,依此類推直到 10/10。Restic 會將 pack file 分成 10 個大小大致相同的群組,只檢查第 n 個群組,因此執行 10 次即可完成一個週期,讀取每個群組各 1 次。

其次是還原演練。仍在上述的 root shell 中,將最新 snapshot 的一個實際目錄還原到暫存位置,再與目前的檔案比較:

restic restore latest --target /srv/restore-drill --include /etc/ssh
diff -r /etc/ssh /srv/restore-drill/etc/ssh

diff 沒有輸出,表示每個位元組都完全相同;這是唯一有意義的證據。完成後刪除 /srv/restore-drill。每月進行一次這項演練;每年再進行 1 或 2 次完整版本:將整個最新 snapshot 還原到暫存 VPS,並確認應用程式確實能從中啟動。當你在壓力下需要這項功能時,應該已經把它做成熟悉的例行程序。

保留:forget 加上 prune

如果沒有保留政策,快照會永久累積,儲存庫只會持續增長。指令碼中的 forget 行會在每晚套用政策:--keep-daily 7 會保留過去 7 天每天 1 個快照,--keep-weekly 4 會保留 4 週每週 1 個快照,--keep-monthly 6 會保留 6 個月每月 1 個快照。未受任何規則保護的快照都會被忘記。

單獨執行 forget 只會移除快照記錄;資料區塊仍會留在儲存庫中,直到其他程序將其刪除。--prune 就是用來執行這項工作:它會找出沒有任何現存快照參照的區塊並刪除,磁碟空間也會在此時實際釋出。Prune 會執行實際的儲存庫作業,因此對大型儲存庫而言,有些人會每晚執行 forget,每週執行 --prune;以一般 VPS 的大小而言,每晚執行即可。

資料庫:先傾印,再備份傾印檔

Restic 會在讀取檔案時複製檔案,而資料庫會持續寫入檔案。擷取正在寫入中的資料庫檔案,還原後會變成損毀的資料庫,因為複本混合了寫入前後的頁面。標準作法是:讓資料庫引擎將一致的匯出內容產生至檔案,再讓 restic 備份該檔案。

對 PostgreSQL 而言,請在 restic-backup.sh 的頂端、restic backup 指令之前加入傾印指令,並將傾印目錄加入備份路徑:

mkdir -p /var/backups/db
sudo -u postgres pg_dump myapp | gzip > /var/backups/db/myapp.sql.gz

mysqldump 在 MariaDB 和 MySQL 中扮演相同角色。如需完整模式的實作範例,Nextcloud 備份區段會啟用維護模式、傾印 Postgres,並將檔案作為一致的集合複製;這正是 restic 每晚應從伺服器備份出去的內容。SQLite 的概念相同,但作法更簡單:Vaultwarden 指南會停止容器幾秒,以建立 db.sqlite3 的冷複本;restic 會將該封存檔備份至伺服器外部。

FAQ

restic 備份是否已加密?

是,一律加密。每個 restic repository 都使用 AES-256 加密,沒有未加密模式。除非傳入 --insecure-no-password,否則 restic 也會拒絕空白密碼(自 restic 0.17.0 起提供此選項)。因此,依本指南建立的 repository 在每次執行指令時都需要密碼。儲存 repository 的機器或服務供應商只會持有加密 blob,因此備份主機遭入侵時,不會直接暴露你的檔案。這項取捨是絕對的:沒有密碼,任何人都無法復原資料,因此請在遠離伺服器的位置保留一份密碼副本。

restic 是否支援增量備份?

每個 restic snapshot 的行為都像完整備份,但儲存成本是增量的。Restic 會將檔案切分為區塊,只上傳 repository 尚未儲存的區塊,因此每晚執行時大致只會傳輸當天變更的內容。不同於傳統的增量備份機制,這裡沒有需要重播的鏈結:任何 snapshot 都能直接還原,刪除舊 snapshot 也不會影響較新的 snapshot。

如何從 restic 備份還原檔案?

執行 restic snapshots 找出 snapshot ID,再執行 restic restore <id> --target /some/empty/dir 進行還原;加入 --include /path 可只還原其中一部分。latest 可取代 ID。Restic 會在目標位置下重建原始目錄結構,因此還原 /etc/ssh 會將內容放到 /some/empty/dir/etc/ssh。請在真正需要前先演練,因為未經測試的備份只是傳聞。

應多久執行一次 restic backup?

對伺服器而言,每晚執行是合理的最低頻率,而去重功能讓成本很低:每次執行只會上傳自上次備份後變更的區塊。變更快速的資料,或遺失 1 天資料就會造成重大影響的資料,可以使用相同的 timer 模式每隔幾小時執行一次。頻率只是其中一半;也應定期執行 restic check,並每月進行還原演練,因為只有排程而沒有驗證,只會帶來虛假的安心感。

如果遺失 restic repository 密碼,會發生什麼事?

備份將無法復原。Restic 加密沒有後門,也沒有重設機制,因此密碼與備份本身同樣重要。請在密碼管理器中保留一份,也在不是備份伺服器的其他耐久位置保留一份。你仍可存取 repository 時,restic key add 能為相同的 repository 註冊第 2 組密碼,作為備用密碼。