Sau SSH hardening và Fail2ban, VPS của bạn đã có lớp bảo vệ tốt hơn trước các truy cập trái phép và brute force. Nhưng bảo mật không bao giờ là tuyệt đối. Server vẫn có thể lỗi vì thao tác nhầm, nâng cấp package lỗi, disk đầy, database hỏng, plugin WordPress lỗi, bị xóa nhầm file, bị mã độc ghi đè dữ liệu hoặc đơn giản là nhà cung cấp VPS gặp sự cố.
Vì vậy, bài Backup chiến lược: snapshots, auto backup và Borg/Restic là một bước rất quan trọng trong nhóm Security + Ops. Mục tiêu của bài này không phải là chạy một lệnh backup cho có, mà là thiết kế một chiến lược backup nhiều lớp cho VPS Ubuntu 24.04 LTS: có snapshot để rollback nhanh, có backup file/database để restore chi tiết, có auto backup bằng systemd timer, có retention policy để không đầy disk, và có restore drill để kiểm chứng backup thật sự dùng được.
Nguyên tắc quan trọng nhất: backup không có giá trị nếu chưa từng restore thử. Một file backup nằm đâu đó trên server chưa đủ để gọi là an toàn. Bạn cần biết backup đó chứa gì, thiếu gì, khôi phục trong bao lâu, có bị lỗi quyền file không, database có import được không, và nếu VPS biến mất hoàn toàn thì bạn có còn lấy lại dữ liệu được không.
1. Hiểu đúng về backup VPS Ubuntu 24.04
1.1. Backup không phải là một file nén để yên tâm
Nhiều người mới vận hành VPS thường nghĩ backup là nén thư mục website thành file .tar.gz, tải về máy cá nhân, rồi xem như xong. Cách này tốt hơn là không có gì, nhưng chưa đủ cho production.
Một chiến lược backup tốt cần trả lời được các câu hỏi sau:
- Dữ liệu nào cần backup?
- Backup nằm ở đâu?
- Nếu VPS mất hoàn toàn, backup có còn không?
- Backup có được mã hóa không?
- Backup chạy tự động hay phụ thuộc vào trí nhớ?
- Giữ bao nhiêu bản daily, weekly, monthly?
- Làm sao biết backup hôm qua chạy thành công?
- Restore một file mất bao lâu?
- Restore toàn site mất bao lâu?
- Đã từng restore thử chưa?
Nếu chưa trả lời được những câu này, backup vẫn đang ở mức “hy vọng”, chưa phải là một kế hoạch vận hành.
1.2. Snapshot và backup không giống nhau
Snapshot VPS là ảnh chụp trạng thái disk hoặc máy ảo tại một thời điểm. Snapshot rất hữu ích trước khi thay đổi lớn như nâng cấp hệ điều hành, chỉnh SSH, migrate app, đổi cấu trúc Nginx hoặc cập nhật WordPress/plugin.
Nhưng snapshot không thay thế backup. Snapshot thường nằm cùng provider hoặc cùng vùng hạ tầng. Nếu tài khoản provider bị khóa, vùng lưu trữ snapshot lỗi, hoặc bạn xóa nhầm cả VPS lẫn snapshot, dữ liệu vẫn có thể mất. Snapshot cũng không phải lúc nào cũng tiện để restore một file riêng lẻ hoặc một bảng database cụ thể.
Backup đúng nghĩa nên có ít nhất một bản nằm ngoài VPS chính. Tốt hơn nữa là nằm ngoài provider chính, được mã hóa và có thể restore độc lập.
1.3. RPO và RTO: hai khái niệm nên biết
Trước khi viết script backup, bạn nên hiểu hai khái niệm rất thực tế:
- RPO: bạn chấp nhận mất tối đa bao nhiêu dữ liệu?
- RTO: bạn cần khôi phục dịch vụ trong bao lâu?
Ví dụ, nếu blog cá nhân backup mỗi ngày một lần, RPO là khoảng 24 giờ. Nếu hôm nay database hỏng, bạn có thể mất bài viết hoặc comment sau lần backup gần nhất. Nếu bạn cần dựng lại server trong 2 giờ, RTO của bạn là 2 giờ.
Không phải website nào cũng cần RPO 5 phút hoặc RTO 10 phút. Nhưng nếu bạn không đặt mục tiêu, bạn sẽ không biết backup hiện tại có đủ hay chưa.
1.4. Quy tắc 3-2-1 nên áp dụng linh hoạt
Quy tắc 3-2-1 thường được nhắc trong backup: có 3 bản dữ liệu, trên 2 loại lưu trữ khác nhau, trong đó 1 bản nằm ngoài site chính. Với VPS nhỏ, có thể diễn giải thực dụng như sau:
- Dữ liệu gốc nằm trên VPS production.
- Một lớp snapshot nằm ở provider để rollback nhanh.
- Một lớp backup mã hóa nằm ở backup server/object storage/máy khác.
Nếu làm được thêm một bản offline định kỳ về máy cá nhân hoặc ổ cứng rời, càng tốt. Nhưng ngay cả khi chưa đạt 3-2-1 hoàn hảo, bạn vẫn nên bắt đầu bằng việc đưa backup ra khỏi VPS chính.
2. Xác định dữ liệu cần backup trên VPS
2.1. Không cần backup toàn bộ hệ điều hành trong mọi trường hợp
Với VPS Ubuntu tự host website, không phải lúc nào cũng cần backup toàn bộ /. Nhiều thành phần có thể cài lại từ package hoặc tái tạo từ tài liệu cấu hình. Thứ cần bảo vệ nhất là dữ liệu không thể tải lại hoặc tái tạo dễ dàng.
Nhóm nên backup:
- Source code hoặc release hiện tại nếu không có đầy đủ trên Git.
- File upload của website.
- Database dump.
- Cấu hình Nginx, PHP-FPM, systemd unit, cron/timer tùy chỉnh.
- File môi trường
.envhoặc secret cần thiết để app chạy lại. - Script deploy, script backup, tài liệu vận hành nội bộ.
- Danh sách package đã cài nếu muốn rebuild nhanh.
Nhóm thường không cần backup nguyên trạng:
- Cache tạm.
- Log cũ quá lớn nếu đã có retention riêng.
- Thư mục build có thể tạo lại.
- Dependency có thể cài lại từ lockfile.
- File tạm trong
/tmp,/var/tmp.
2.2. Các đường dẫn thường cần backup với stack LEMP
Với series này, các đường dẫn đáng chú ý có thể gồm:
/srv/apps/
/var/www/
/etc/nginx/
/etc/php/
/etc/systemd/system/
/etc/letsencrypt/
/usr/local/sbin/
/root/
/home/
Tùy server thật, bạn có thể thêm hoặc bớt. Ví dụ nếu website dùng WordPress truyền thống, thư mục cần backup có thể là:
/var/www/devnook.net/
hoặc
/srv/apps/devnook.net/
Với Let’s Encrypt, backup /etc/letsencrypt hữu ích khi restore nhanh cấu hình certificate. Tuy nhiên, nếu mất thư mục này, bạn vẫn có thể cấp lại certificate nếu DNS và domain còn kiểm soát được. Không nên xem certificate là dữ liệu quan trọng hơn database và upload.
2.3. Database cần backup theo cách riêng
Không nên chỉ backup thư mục /var/lib/mysql bằng file-level backup trong khi MariaDB đang chạy, rồi hy vọng restore luôn ổn. Database cần phương pháp phù hợp để tạo trạng thái nhất quán.
Với website nhỏ hoặc database vừa phải, cách dễ hiểu nhất là tạo logical dump bằng mariadb-dump. Dump này tạo file SQL có thể import lại bằng client mariadb.
Với database lớn hoặc yêu cầu downtime thấp, bạn cần học thêm physical backup bằng mariadb-backup, replication hoặc backup theo kiến trúc riêng. Bài này tập trung vào hướng dễ áp dụng cho VPS nhỏ: dump database trước, sau đó đưa dump vào Borg/Restic.
3. Thiết kế chiến lược backup nhiều lớp
3.1. Lớp 1: Snapshot provider để rollback nhanh
Snapshot rất phù hợp trước các thay đổi lớn:
- Nâng cấp Ubuntu hoặc package quan trọng.
- Đổi cấu hình SSH/firewall.
- Migrate database.
- Cập nhật WordPress core/plugin/theme lớn.
- Đổi cấu trúc Nginx hoặc PHP-FPM.
- Triển khai bản release lớn.
Ưu điểm của snapshot là rollback nhanh, dễ thao tác từ panel provider. Nhược điểm là phụ thuộc provider, có thể tốn phí, không thay thế restore chi tiết, và không nên xem là bản backup duy nhất.
Quy tắc thực tế: snapshot trước thay đổi lớn, giữ trong thời gian ngắn, xóa khi đã chắc server ổn. Backup dài hạn nên nằm ở lớp khác.
3.2. Lớp 2: Database dump và file backup trên hệ điều hành
Trước khi backup file, hãy tạo dump database vào một thư mục staging. Ví dụ:
/var/backups/devnook/
Sau đó Borg hoặc Restic sẽ backup:
- Thư mục app.
- Thư mục upload.
- Database dump mới nhất.
- Cấu hình hệ thống quan trọng.
- Script vận hành.
Cách này giúp restore linh hoạt hơn snapshot. Bạn có thể lấy lại một file config, một thư mục upload, một database dump hoặc toàn bộ app tùy tình huống.
3.3. Lớp 3: Backup offsite bằng Borg hoặc Restic
Offsite backup là bản nằm ngoài VPS chính. Có thể là:
- Một backup server riêng qua SSH.
- Storage box của provider khác.
- Object storage S3-compatible.
- NAS hoặc máy chủ nội bộ có kết nối bảo mật.
- Ổ cứng rời đồng bộ định kỳ.
Borg và Restic đều phù hợp cho backup mã hóa, incremental/deduplicated và chạy tự động. Không cần dùng cả hai cùng lúc nếu bạn mới bắt đầu. Hãy chọn một công cụ chính, hiểu thật chắc, test restore, rồi mới mở rộng.
3.4. Borg hay Restic nên chọn cái nào?
BorgBackup rất mạnh khi backup qua SSH đến một máy Linux khác. Borg có deduplication, compression, encryption, prune/compact và được dùng rộng rãi cho server backup.
Restic rất tiện nếu bạn muốn backup đến nhiều backend như local, SFTP, S3-compatible storage hoặc dịch vụ object storage. Restic là single binary, dễ đưa vào script, workflow snapshot/forget/check rõ ràng.
Quy tắc chọn nhanh:
- Có backup server Linux qua SSH: dùng Borg hoặc Restic đều ổn.
- Muốn backup lên object storage S3-compatible: Restic thường dễ bắt đầu hơn.
- Muốn compression built-in linh hoạt: Borg rất đáng cân nhắc.
- Muốn một binary gọn, backend đa dạng: Restic rất hợp.
Bài này sẽ đưa cả hai hướng. Phần Restic sẽ được dùng làm ví dụ automation chính vì dễ mô phỏng với repository local hoặc SFTP. Phần Borg được đưa như lựa chọn thay thế tương đương cho người thích Borg.
4. Chuẩn bị môi trường backup
4.1. Tạo thư mục staging cho backup
Tạo thư mục chứa dump tạm và log backup:
sudo mkdir -p /var/backups/devnook/db
sudo mkdir -p /var/log/devnook-backup
sudo chmod 750 /var/backups/devnook
sudo chmod 750 /var/backups/devnook/db
sudo chmod 750 /var/log/devnook-backup
Kiểm tra:
ls -ld /var/backups/devnook /var/backups/devnook/db /var/log/devnook-backup
Không nên đặt database dump trong webroot. Dump database có thể chứa user, email, token, cấu hình plugin, session hoặc dữ liệu nhạy cảm.
4.2. Cài công cụ cần thiết
sudo apt update
sudo apt install -y mariadb-client gzip tar curl restic borgbackup
Nếu server đang dùng MySQL thay vì MariaDB, client tương ứng có thể khác. Với series này, các bài trước đã dùng MariaDB làm ví dụ chính, nên phần dump dùng mariadb-dump.
Kiểm tra version:
restic version
borg --version
mariadb-dump --version
4.3. Tạo file biến môi trường cho backup
Tạo thư mục cấu hình riêng:
sudo mkdir -p /etc/devnook-backup
sudo chmod 700 /etc/devnook-backup
Tạo file env:
sudo nano /etc/devnook-backup/backup.env
Nội dung mẫu:
APP_NAME=devnook
APP_PATH=/srv/apps/example.com
BACKUP_STAGING=/var/backups/devnook
DB_NAME=appdb
DB_USER=backupuser
DB_PASS=CHANGE_THIS_TO_BACKUP_USER_PASSWORD
RESTIC_REPOSITORY=/backup/restic-devnook
RESTIC_PASSWORD=CHANGE_THIS_TO_A_LONG_RANDOM_RESTIC_PASSWORD
Đặt quyền:
sudo chmod 600 /etc/devnook-backup/backup.env
sudo chown root:root /etc/devnook-backup/backup.env
File này chứa secret nên chỉ root được đọc. Không commit file này lên Git.
4.4. Tạo user database chỉ dùng cho backup
Đăng nhập MariaDB:
sudo mariadb
Tạo user backup:
CREATE USER 'backupuser'@'localhost'
IDENTIFIED BY 'CHANGE_THIS_TO_BACKUP_USER_PASSWORD';
GRANT SELECT, SHOW VIEW, TRIGGER, LOCK TABLES, EVENT
ON appdb.*
TO 'backupuser'@'localhost';
FLUSH PRIVILEGES;
SHOW GRANTS FOR 'backupuser'@'localhost';
EXIT;
Với một số cấu hình InnoDB và dump có --single-transaction, bạn có thể không cần lock table theo cách truyền thống. Tuy nhiên, quyền thực tế còn phụ thuộc database, engine và đối tượng cần dump. Hãy test lệnh dump thật thay vì chỉ nhìn câu lệnh SQL.
5. Tạo backup database bằng mariadb-dump
5.1. Tạo script dump database
Tạo script:
sudo nano /usr/local/sbin/devnook-db-dump.sh
Nội dung:
#!/usr/bin/env bash
set -euo pipefail
source /etc/devnook-backup/backup.env
DATE="$(date +%F-%H%M%S)"
OUT_DIR="${BACKUP_STAGING}/db"
OUT_FILE="${OUT_DIR}/${DB_NAME}-${DATE}.sql.gz"
LATEST_FILE="${OUT_DIR}/${DB_NAME}-latest.sql.gz"
mkdir -p "$OUT_DIR"
echo "[db-dump] Starting dump for ${DB_NAME}"
mariadb-dump \
--user="$DB_USER" \
--password="$DB_PASS" \
--single-transaction \
--quick \
--routines \
--events \
--triggers \
"$DB_NAME" | gzip -9 > "$OUT_FILE"
ln -sfn "$OUT_FILE" "$LATEST_FILE"
echo "[db-dump] Created: $OUT_FILE"
ls -lh "$OUT_FILE" "$LATEST_FILE"
Cấp quyền:
sudo chmod 700 /usr/local/sbin/devnook-db-dump.sh
sudo chown root:root /usr/local/sbin/devnook-db-dump.sh
5.2. Chạy thử database dump
sudo /usr/local/sbin/devnook-db-dump.sh
Kiểm tra file:
sudo ls -lh /var/backups/devnook/db/
sudo gzip -t /var/backups/devnook/db/appdb-latest.sql.gz
sudo zcat /var/backups/devnook/db/appdb-latest.sql.gz | head -n 30
Nếu gzip -t thành công và file SQL có nội dung hợp lý, bước dump database đã ổn ở mức cơ bản.
5.3. Dọn dump cũ trong staging
Thư mục staging không nên phình mãi. Có thể giữ dump 7 ngày gần nhất ở staging, còn lịch sử dài hạn để Borg/Restic quản lý.
Thêm cuối script devnook-db-dump.sh:
find "$OUT_DIR" -type f -name "${DB_NAME}-*.sql.gz" -mtime +7 -delete
Không dùng lệnh xóa nếu chưa chắc biến OUT_DIR đúng. Với script production, nên log kỹ và test bằng find ... -print trước khi thêm -delete.
6. Backup bằng Restic
6.1. Khởi tạo Restic repository
Ví dụ này dùng repository local để dễ kiểm thử:
sudo mkdir -p /backup/restic-devnook
sudo chmod 700 /backup/restic-devnook
Khởi tạo repository:
sudo env $(sudo cat /etc/devnook-backup/backup.env | xargs) restic init
Nếu cách trên không hoạt động do ký tự đặc biệt trong password, dùng shell root an toàn hơn:
sudo -i
set -a
source /etc/devnook-backup/backup.env
set +a
restic init
exit
Với production, repository local chỉ là ví dụ. Bạn nên đặt Restic repository ở nơi khác: backup server qua SFTP, object storage hoặc disk khác. Nếu repository nằm cùng disk với VPS, khi disk hỏng bạn vẫn mất cả dữ liệu gốc lẫn backup.
6.2. Tạo script backup Restic
Tạo script:
sudo nano /usr/local/sbin/devnook-restic-backup.sh
Nội dung:
#!/usr/bin/env bash
set -euo pipefail
source /etc/devnook-backup/backup.env
LOG_DIR="/var/log/devnook-backup"
LOG_FILE="${LOG_DIR}/restic-$(date +%F).log"
mkdir -p "$LOG_DIR"
exec > >(tee -a "$LOG_FILE") 2>&1
echo "[restic] Backup started at $(date -Is)"
echo "[restic] Creating database dump..."
/usr/local/sbin/devnook-db-dump.sh
echo "[restic] Running backup..."
restic backup \
"$APP_PATH" \
"$BACKUP_STAGING/db" \
/etc/nginx \
/etc/php \
/etc/systemd/system \
/etc/letsencrypt \
/usr/local/sbin \
/etc/devnook-backup \
--exclude-caches \
--exclude "$APP_PATH/current/var/cache" \
--exclude "$APP_PATH/current/storage/cache" \
--tag vps \
--tag lemp \
--tag "$APP_NAME"
echo "[restic] Applying retention policy..."
restic forget \
--keep-daily 7 \
--keep-weekly 4 \
--keep-monthly 6 \
--prune
echo "[restic] Checking repository..."
restic check
echo "[restic] Backup finished at $(date -Is)"
Cấp quyền:
sudo chmod 700 /usr/local/sbin/devnook-restic-backup.sh
sudo chown root:root /usr/local/sbin/devnook-restic-backup.sh
6.3. Chạy thử Restic backup
sudo /usr/local/sbin/devnook-restic-backup.sh
Kiểm tra snapshot:
sudo -i
set -a
source /etc/devnook-backup/backup.env
set +a
restic snapshots
restic stats
exit
Nếu snapshot xuất hiện, backup đã chạy được. Nhưng chưa dừng ở đây. Cần restore thử.
6.4. Restore thử một file bằng Restic
Tạo thư mục test:
sudo mkdir -p /tmp/restic-restore-test
Restore snapshot mới nhất vào thư mục test:
sudo -i
set -a
source /etc/devnook-backup/backup.env
set +a
restic restore latest --target /tmp/restic-restore-test
exit
Kiểm tra:
sudo find /tmp/restic-restore-test -maxdepth 4 -type f | head -n 50
sudo ls -lh /tmp/restic-restore-test/var/backups/devnook/db/ || true
Nếu bạn thấy database dump, cấu hình Nginx và thư mục app, restore cơ bản đã hoạt động.
6.5. Restore thử database vào database test
Tạo database test:
sudo mariadb -e "CREATE DATABASE IF NOT EXISTS restore_test CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
Import dump mới nhất:
sudo zcat /var/backups/devnook/db/appdb-latest.sql.gz | sudo mariadb restore_test
Kiểm tra table:
sudo mariadb -e "SHOW TABLES FROM restore_test;"
Nếu import thành công, bạn đã có bằng chứng database dump restore được. Sau khi test xong, có thể xóa database test:
sudo mariadb -e "DROP DATABASE restore_test;"
7. Tự động hóa backup bằng systemd timer
7.1. Vì sao dùng systemd timer thay vì cron?
Cron vẫn dùng được, nhưng systemd timer có một số lợi thế trong môi trường server hiện đại: log tập trung qua journal, dễ xem trạng thái lần chạy gần nhất, dễ gắn với service unit, có thể dùng Persistent=true để chạy bù nếu server tắt vào thời điểm lịch backup.
Với VPS Ubuntu 24.04, systemd timer là lựa chọn rất hợp lý cho backup tự động.
7.2. Tạo systemd service cho backup
Tạo file:
sudo nano /etc/systemd/system/devnook-restic-backup.service
Nội dung:
[Unit]
Description=DevNook Restic Backup
Wants=network-online.target
After=network-online.target mariadb.service
[Service]
Type=oneshot
ExecStart=/usr/local/sbin/devnook-restic-backup.sh
Nice=10
IOSchedulingClass=best-effort
IOSchedulingPriority=7
7.3. Tạo systemd timer chạy hằng ngày
Tạo file:
sudo nano /etc/systemd/system/devnook-restic-backup.timer
Nội dung:
[Unit]
Description=Run DevNook Restic Backup daily
[Timer]
OnCalendar=*-*-* 03:15:00
Persistent=true
RandomizedDelaySec=30m
Unit=devnook-restic-backup.service
[Install]
WantedBy=timers.target
Ý nghĩa:
OnCalendar: chạy lúc 03:15 mỗi ngày.Persistent=true: nếu server tắt vào thời điểm đó, timer có thể chạy bù sau khi bật lại.RandomizedDelaySec=30m: thêm độ trễ ngẫu nhiên để tránh nhiều job cùng chạy đúng một thời điểm.
7.4. Bật timer và kiểm tra
sudo systemctl daemon-reload
sudo systemctl enable --now devnook-restic-backup.timer
systemctl list-timers | grep devnook
sudo systemctl status devnook-restic-backup.timer --no-pager
Chạy thử service thủ công:
sudo systemctl start devnook-restic-backup.service
Xem log:
sudo systemctl status devnook-restic-backup.service --no-pager
sudo journalctl -u devnook-restic-backup.service --since "1 hour ago" --no-pager
sudo tail -n 120 /var/log/devnook-backup/restic-$(date +%F).log
Nếu service fail, đọc log trước khi chỉnh tiếp. Không nên để timer chạy âm thầm nhiều ngày trong trạng thái lỗi.
8. Phương án thay thế bằng BorgBackup
8.1. Khi nào nên dùng Borg?
Borg rất hợp khi bạn có một backup server Linux qua SSH. Borg hỗ trợ repository, archive, deduplication, encryption, compression, prune và compact. Nếu bạn đã quen SSH và muốn backup đến một máy Linux khác, Borg là lựa chọn rất tốt.
Ví dụ repository Borg remote:
ssh://[email protected]:22/~/repos/devnook
8.2. Tạo file env cho Borg
Thêm vào /etc/devnook-backup/backup.env:
BORG_REPO=ssh://[email protected]:22/~/repos/devnook
BORG_PASSPHRASE=CHANGE_THIS_TO_A_LONG_RANDOM_BORG_PASSPHRASE
Đảm bảo file vẫn có quyền:
sudo chmod 600 /etc/devnook-backup/backup.env
8.3. Khởi tạo Borg repository
sudo -i
source /etc/devnook-backup/backup.env
borg init --encryption=repokey "$BORG_REPO"
exit
Ghi lại passphrase và backup key Borg ở nơi an toàn. Nếu mất passphrase/key, backup mã hóa có thể không còn khả năng restore.
8.4. Tạo script backup Borg
Tạo file:
sudo nano /usr/local/sbin/devnook-borg-backup.sh
Nội dung:
#!/usr/bin/env bash
set -euo pipefail
source /etc/devnook-backup/backup.env
LOG_DIR="/var/log/devnook-backup"
LOG_FILE="${LOG_DIR}/borg-$(date +%F).log"
mkdir -p "$LOG_DIR"
exec > >(tee -a "$LOG_FILE") 2>&1
echo "[borg] Backup started at $(date -Is)"
echo "[borg] Creating database dump..."
/usr/local/sbin/devnook-db-dump.sh
borg create \
--verbose \
--filter AME \
--list \
--stats \
--compression lz4 \
--exclude-caches \
--exclude "$APP_PATH/current/var/cache" \
--exclude "$APP_PATH/current/storage/cache" \
"$BORG_REPO::{hostname}-{now:%Y-%m-%d-%H%M%S}" \
"$APP_PATH" \
"$BACKUP_STAGING/db" \
/etc/nginx \
/etc/php \
/etc/systemd/system \
/etc/letsencrypt \
/usr/local/sbin \
/etc/devnook-backup
echo "[borg] Pruning old archives..."
borg prune \
--list \
--glob-archives '{hostname}-*' \
--keep-daily 7 \
--keep-weekly 4 \
--keep-monthly 6
echo "[borg] Compacting repository..."
borg compact
echo "[borg] Backup finished at $(date -Is)"
Cấp quyền:
sudo chmod 700 /usr/local/sbin/devnook-borg-backup.sh
sudo chown root:root /usr/local/sbin/devnook-borg-backup.sh
8.5. Chạy thử Borg backup
sudo /usr/local/sbin/devnook-borg-backup.sh
Liệt kê archive:
sudo -i
source /etc/devnook-backup/backup.env
borg list "$BORG_REPO"
exit
8.6. Restore thử bằng Borg
Tạo thư mục test:
sudo mkdir -p /tmp/borg-restore-test
cd /tmp/borg-restore-test
Liệt kê archive:
sudo -i
source /etc/devnook-backup/backup.env
borg list "$BORG_REPO"
exit
Restore một archive cụ thể:
sudo -i
source /etc/devnook-backup/backup.env
cd /tmp/borg-restore-test
borg extract "$BORG_REPO::ARCHIVE_NAME"
exit
Thay ARCHIVE_NAME bằng tên archive thật từ borg list. Sau đó kiểm tra file đã restore.
9. Restore drill: bài kiểm tra quan trọng nhất
9.1. Restore drill là gì?
Restore drill là buổi diễn tập khôi phục dữ liệu. Bạn không đợi đến lúc production hỏng mới thử restore. Bạn chủ động chọn một bản backup, restore vào thư mục hoặc server test, rồi kiểm tra dữ liệu có dùng được không.
Restore drill nên kiểm tra ít nhất ba tình huống:
- Restore một file cấu hình.
- Restore thư mục upload hoặc source app.
- Restore database vào database test.
Nếu có điều kiện, hãy dựng một VPS test mới và thử khôi phục toàn bộ website. Đây là cách tốt nhất để biết RTO thật của bạn.
9.2. Checklist restore một file
- Chọn snapshot/archive mới nhất.
- Restore vào
/tmp/restore-test, không ghi đè production. - So sánh file được restore với file thật nếu còn tồn tại.
- Kiểm tra owner, permission và nội dung.
- Xóa thư mục test sau khi hoàn tất.
9.3. Checklist restore database
- Không restore đè database production nếu chỉ đang test.
- Tạo database test riêng.
- Import dump vào database test.
- Chạy
SHOW TABLESvà vài truy vấn cơ bản. - Kiểm tra dữ liệu quan trọng như user, bài viết, cấu hình app.
- Xóa database test sau khi hoàn tất.
9.4. Ghi lại kết quả restore drill
Sau mỗi lần restore drill, hãy ghi lại:
- Ngày thực hiện.
- Backup tool dùng: Restic hay Borg.
- Snapshot/archive ID.
- Dữ liệu đã restore.
- Thời gian restore.
- Lỗi gặp phải.
- Việc cần cải thiện.
Ghi chú này giúp bạn không phải học lại từ đầu khi sự cố thật xảy ra.
10. Theo dõi backup và cảnh báo lỗi
10.1. Kiểm tra log backup hằng ngày
sudo journalctl -u devnook-restic-backup.service --since "24 hours ago" --no-pager
sudo tail -n 120 /var/log/devnook-backup/restic-$(date +%F).log
Nếu dùng Borg:
sudo tail -n 120 /var/log/devnook-backup/borg-$(date +%F).log
10.2. Kiểm tra timer
systemctl list-timers | grep devnook
sudo systemctl status devnook-restic-backup.timer --no-pager
Nếu timer không xuất hiện, kiểm tra lại:
sudo systemctl daemon-reload
sudo systemctl enable --now devnook-restic-backup.timer
10.3. Tạo script kiểm tra backup nhanh
Tạo file:
sudo nano /usr/local/sbin/devnook-backup-status.sh
Nội dung:
#!/usr/bin/env bash
set -euo pipefail
echo "== Timer =="
systemctl list-timers | grep devnook || true
echo
echo "== Last service status =="
systemctl status devnook-restic-backup.service --no-pager || true
echo
echo "== Recent backup logs =="
journalctl -u devnook-restic-backup.service --since "24 hours ago" --no-pager || true
echo
echo "== Restic snapshots =="
set -a
source /etc/devnook-backup/backup.env
set +a
restic snapshots || true
Cấp quyền:
sudo chmod 700 /usr/local/sbin/devnook-backup-status.sh
Chạy:
sudo /usr/local/sbin/devnook-backup-status.sh
10.4. Cảnh báo khi backup fail
Bài này chưa cấu hình email/Telegram/Slack alert để giữ nội dung gọn. Nhưng trong production, bạn nên có ít nhất một cách nhận cảnh báo khi backup fail.
Các hướng phổ biến:
- Dùng monitoring ở bài C4 để theo dõi trạng thái backup.
- Dùng healthcheck endpoint bên ngoài, script backup gọi ping thành công/thất bại.
- Gửi email từ systemd service khi fail.
- Gửi webhook đến Telegram/Discord/Slack.
Điều quan trọng là không để backup fail âm thầm nhiều tuần.
11. Bảo mật backup
11.1. Backup chứa dữ liệu nhạy cảm
Backup thường nhạy cảm hơn cả source code vì nó có thể chứa database, user, email, token, secret, private config, file upload và lịch sử dữ liệu. Vì vậy, backup cần được bảo vệ nghiêm túc.
Nguyên tắc tối thiểu:
- Repository backup nên được mã hóa.
- Passphrase lưu ở nơi an toàn.
- File env chứa password chỉ root đọc được.
- Không để backup trong webroot.
- Không upload backup không mã hóa lên storage bên thứ ba.
- Không gửi backup qua chat/email nếu không mã hóa.
11.2. Backup passphrase cũng cần backup
Nếu bạn mất passphrase Restic/Borg, backup mã hóa có thể không restore được. Vì vậy, passphrase cần được lưu trong password manager, vault nội bộ hoặc nơi an toàn khác.
Không chỉ backup dữ liệu, hãy backup cả khả năng khôi phục dữ liệu.
11.3. Không để backup server có quyền phá production
Nếu dùng backup server riêng, hãy thiết kế quyền theo hướng production đẩy backup ra ngoài, hoặc backup server chỉ có quyền nhận dữ liệu. Tránh cấu hình khiến backup server có quyền SSH ngược vào production với root mà không cần thiết.
11.4. Cẩn thận với object storage public
Nếu dùng S3-compatible storage, hãy kiểm tra bucket không public. Backup mã hóa vẫn là lớp bảo vệ quan trọng, nhưng không nên dựa vào mã hóa để bỏ qua cấu hình quyền bucket.
12. Lỗi thường gặp khi backup VPS
12.1. Backup nằm cùng VPS và cùng disk
Đây là lỗi rất phổ biến. Nếu backup nằm trong /backup trên cùng disk với dữ liệu gốc, nó chỉ giúp chống xóa nhầm một phần. Nó không giúp khi disk hỏng, VPS bị xóa hoặc server không boot.
Repository local chỉ nên dùng để test hoặc làm lớp phụ. Backup quan trọng nên có bản offsite.
12.2. Backup database bằng cách copy /var/lib/mysql khi MariaDB đang chạy
Copy raw data directory của database đang chạy có thể tạo backup không nhất quán. Với website nhỏ, hãy dùng mariadb-dump. Với database lớn, học mariadb-backup hoặc giải pháp phù hợp hơn.
12.3. Không kiểm tra restore
Backup chạy thành công không có nghĩa restore thành công. File có thể thiếu, dump có thể lỗi, passphrase có thể mất, permission có thể sai, hoặc script có thể exclude nhầm thư mục quan trọng.
Restore drill định kỳ là cách duy nhất để phát hiện sớm các lỗi này.
12.4. Không có retention policy
Nếu backup chạy mỗi ngày nhưng không prune/forget, repository sẽ lớn dần đến khi đầy disk hoặc hết quota. Hãy có retention policy rõ: daily, weekly, monthly.
12.5. Xóa backup cũ nhưng không compact/prune đúng cách
Với các công cụ deduplicated backup, xóa snapshot/archive metadata chưa chắc đã giải phóng dung lượng ngay. Restic cần prune sau forget; Borg cần compact sau prune để giải phóng dung lượng repository. Nếu bỏ qua bước này, disk usage có thể không giảm như bạn nghĩ.
12.6. Đưa secret vào log
Script backup không nên in password, token hoặc env đầy đủ ra log. Hãy cẩn thận với set -x trong script shell, vì nó có thể in command kèm secret.
12.7. Không kiểm tra dung lượng trước khi backup
Backup có thể fail nếu disk hoặc repository hết chỗ. Kiểm tra định kỳ:
df -h
du -sh /var/backups/devnook
restic stats
Với Borg:
borg info "$BORG_REPO"
13. Checklist backup chiến lược
13.1. Checklist thiết kế
- Đã xác định RPO và RTO phù hợp.
- Có snapshot provider trước thay đổi lớn.
- Có backup file và database riêng.
- Có ít nhất một bản backup offsite.
- Backup được mã hóa.
- Passphrase được lưu an toàn.
- Có retention policy.
13.2. Checklist dữ liệu
- Backup thư mục app/source.
- Backup file upload.
- Backup database dump.
- Backup cấu hình Nginx/PHP-FPM/systemd.
- Backup script vận hành.
- Không backup cache/log tạm quá lớn nếu không cần.
13.3. Checklist automation
- Backup script chạy thủ công thành công.
- Systemd service chạy thành công.
- Systemd timer đã enabled.
- Log backup ghi rõ bắt đầu/kết thúc.
- Timer xuất hiện trong
systemctl list-timers. - Có cách phát hiện backup fail.
13.4. Checklist restore
- Restore thử một file cấu hình.
- Restore thử thư mục app/upload.
- Restore database dump vào database test.
- Ghi lại thời gian restore.
- Ghi lại lỗi gặp phải và cập nhật script nếu cần.
14. FAQ
14.1. Snapshot VPS có đủ để thay backup không?
Không. Snapshot rất hữu ích để rollback nhanh, nhưng thường phụ thuộc provider và không linh hoạt bằng backup file/database. Một chiến lược tốt nên có cả snapshot ngắn hạn và backup offsite dài hạn.
14.2. Nên dùng Borg hay Restic?
Cả hai đều tốt. Borg rất hợp nếu backup qua SSH đến một server Linux khác. Restic rất hợp nếu muốn backend đa dạng như local, SFTP hoặc S3-compatible object storage. Quan trọng nhất không phải chọn công cụ nào, mà là bạn có restore drill và retention policy rõ ràng.
14.3. Có nên backup toàn bộ thư mục / không?
Không bắt buộc. Với nhiều VPS nhỏ, backup dữ liệu quan trọng và cấu hình cần thiết là đủ để rebuild server. Backup toàn bộ hệ điều hành có thể tốn dung lượng, kéo theo cache/log/tmp và làm restore khó hơn. Snapshot provider phù hợp hơn cho rollback toàn máy.
14.4. Database nên backup bằng gì?
Với website nhỏ hoặc vừa, mariadb-dump là cách dễ hiểu và dễ restore. Với database lớn, cần cân nhắc mariadb-backup, replica hoặc giải pháp backup chuyên dụng để giảm downtime và thời gian restore.
14.5. Backup nên chạy bao lâu một lần?
Tùy RPO. Blog cá nhân có thể backup mỗi ngày. Website có đơn hàng hoặc dữ liệu thay đổi liên tục có thể cần backup thường xuyên hơn, thậm chí kết hợp dump, binary log, replica hoặc snapshot theo lịch. Đừng chọn lịch backup theo cảm tính; hãy chọn theo mức dữ liệu bạn chấp nhận mất.
14.6. Có cần mã hóa backup không?
Nên có, đặc biệt nếu backup nằm ở server khác hoặc object storage bên thứ ba. Backup thường chứa database và secret, nên nếu bị lộ sẽ rất nguy hiểm.
14.7. Bao lâu nên restore drill một lần?
Với VPS cá nhân, mỗi tháng hoặc sau mỗi thay đổi lớn là hợp lý. Với production quan trọng, nên restore drill thường xuyên hơn và ghi lại thời gian khôi phục thật.
14.8. Sau bài này nên học gì tiếp?
Sau khi có backup chiến lược, bước tiếp theo nên là monitoring nền với Prometheus node_exporter và Grafana. Backup giúp bạn có đường lui; monitoring giúp bạn phát hiện vấn đề sớm trước khi phải dùng đến đường lui đó.
15. Kết luận
Backup VPS Ubuntu 24.04 không nên được hiểu là nén vài file rồi để yên. Một chiến lược backup đúng cần có nhiều lớp: snapshot provider để rollback nhanh, database dump để khôi phục dữ liệu có cấu trúc, backup file/config bằng Borg hoặc Restic, auto backup bằng systemd timer, retention policy để không đầy disk, và restore drill để chứng minh backup thật sự dùng được.
Trong bài này, bạn đã đi qua workflow thực tế: xác định dữ liệu cần backup, tạo database dump bằng mariadb-dump, backup bằng Restic, tùy chọn thay thế bằng BorgBackup, tự động hóa bằng systemd timer, kiểm tra log, restore thử file/database, và rà các lỗi phổ biến như backup cùng disk, không có retention, không prune/compact hoặc chưa từng restore.
Khi backup đã được thiết kế đúng, bạn sẽ tự tin hơn rất nhiều khi nâng cấp hệ thống, chỉnh cấu hình, deploy app mới hoặc xử lý sự cố. Backup không làm server miễn nhiễm với lỗi, nhưng nó biến một sự cố nghiêm trọng thành một quy trình khôi phục có kiểm soát. Đây là nền tảng bắt buộc trước khi đi tiếp sang monitoring, logging, tuning hiệu năng và troubleshooting playbook.

