Đăng ngày 12 phút đọc
Tự Setup Hệ Thống Backup VPS Với Nextcloud Và Cloudflare R2
Sau khi mất toàn bộ dữ liệu vì hệ thống backup của provider bị lỗi âm thầm, tôi tự xây dựng pipeline backup tự động bằng Nextcloud và Cloudflare R2.
- VPS
- Docker
- Nextcloud
- Backup
Trong bài này
Nếu bạn đang chạy VPS và đang phụ thuộc vào hệ thống backup tự động của provider — bài này dành cho bạn.
Tôi đã học bài học này theo cách đau đớn nhất. VPS của tôi bị kernel panic sau khi chạy do-release-upgrade thất bại, và khi liên hệ provider để restore từ snapshot, câu trả lời tôi nhận được là:
"Cụm máy chủ chứa VPS của bạn trong thời gian gần đây đã gặp một sự cố treo tiến trình hệ thống cục bộ. Lỗi này khiến các tác vụ lưu trữ snapshot/backup tự động không thể hoàn tất nên hiện tại không có bản backup nguyên vẹn nào để khôi phục."
Tất cả mọi thứ đã mất. Toàn bộ project, config, SSL cert — không còn gì. Provider cài lại Ubuntu từ đầu và bồi thường 6 tháng miễn phí như một lời xin lỗi.
Chính trải nghiệm đó đã thúc đẩy tôi tự setup một hệ thống backup thực sự. Bài này ghi lại chính xác những gì tôi đã làm và những vấn đề gặp phải trong quá trình — để bạn không phải trải qua điều tương tự.
Nếu bạn gặp khó khăn khi làm theo hướng dẫn này, hãy liên hệ với tôi qua phần contact trên portfolio. Tôi sẵn lòng giúp đỡ.
Tôi Đã Xây Dựng Gì
Một pipeline backup tự động:
- Backup các path quan trọng nhất trên VPS
- Lưu mỗi bản backup vào một folder theo ngày (ví dụ
2026-03-16_02-00) - Giữ tối đa 30 ngày backup
- Tự động xóa bản cũ nhất khi đạt giới hạn
Data thực sự lưu trên Cloudflare R2 — object storage tương thích S3 với free tier hào phóng (10GB miễn phí, không tính phí egress). Nextcloud chạy trên VPS như một lớp GUI để tôi có thể duyệt và quản lý file backup từ trình duyệt.
/root, /etc/nginx, /etc/letsencrypt...
→ rclone (tự động, chạy lúc 2h sáng mỗi ngày)
→ Cloudflare R2 (storage thực sự)
→ Nextcloud GUI (giao diện trình duyệt để xem file)Bước 1 — Tạo Cloudflare R2 Bucket
Vào Cloudflare Dashboard → R2 Object Storage → Create bucket.
- Name:
vps-backup - Location: chọn region gần VPS nhất
Sau đó tạo API token:
R2 Object Storage → Manage R2 API Tokens → Create API Token
- Permissions: Object Read & Write
- Bucket:
vps-backup
Lưu lại 3 thứ này — sẽ cần dùng xuyên suốt bài:
- Access Key ID
- Secret Access Key ← chỉ hiện 1 lần, copy ngay!
- Endpoint URL: tìm trong tab Settings của bucket, phần S3 API, dạng
https://<account_id>.r2.cloudflarestorage.com
Bước 2 — Cài Docker
Nextcloud sẽ chạy trong Docker để giữ mọi thứ gọn gàng và tách biệt.
sudo apt install -y ca-certificates curl gnupg
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker.gpg
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
docker --versionBước 3 — Setup Nextcloud Với Docker Compose
mkdir ~/nextcloud && cd ~/nextcloud
nano docker-compose.ymlservices:
db:
image: mariadb:10.11
restart: always
environment:
MYSQL_ROOT_PASSWORD: your_root_password
MYSQL_DATABASE: nextcloud
MYSQL_USER: nextcloud
MYSQL_PASSWORD: your_db_password
volumes:
- db_data:/var/lib/mysql
nextcloud:
image: nextcloud:stable
restart: always
ports:
- "8080:80"
depends_on:
- db
environment:
MYSQL_HOST: db
MYSQL_DATABASE: nextcloud
MYSQL_USER: nextcloud
MYSQL_PASSWORD: your_db_password
NEXTCLOUD_ADMIN_USER: admin
NEXTCLOUD_ADMIN_PASSWORD: your_admin_password
volumes:
- nextcloud_data:/var/www/html
volumes:
db_data:
nextcloud_data:Thay
your_root_password,your_db_password, vàyour_admin_passwordbằng password mạnh.
your_root_passwordvàyour_db_passwordchỉ dùng nội bộ Docker — đặt phức tạp nhưng không cần nhớ.your_admin_passworddùng để đăng nhập Nextcloud GUI hàng ngày — nhớ cái này.
docker compose up -d
docker compose psCả db và nextcloud đều phải hiện Up.
Bước 4 — Expose Nextcloud Qua Nginx + SSL
Nextcloud chạy ở port 8080 nội bộ. Tôi expose nó qua Nginx với HTTPS.
sudo apt install nginx certbot python3-certbot-nginx -y
sudo nano /etc/nginx/sites-available/nextcloudserver {
listen 80;
server_name cloud.yourdomain.com;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_cache_bypass $http_upgrade;
client_max_body_size 10G;
}
}sudo ln -s /etc/nginx/sites-available/nextcloud /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl restart nginx
sudo certbot --nginx -d cloud.yourdomain.comThêm A record trong Cloudflare DNS trỏ cloud về IP VPS với Proxy: DNS Only (tắt icon cam).
Vấn Đề: "Access through untrusted domain"
Lần đầu truy cập https://cloud.yourdomain.com, Nextcloud sẽ chặn với lỗi này. Fix bằng:
docker exec -it nextcloud-nextcloud-1 bash -c "php occ config:system:set trusted_domains 1 --value=cloud.yourdomain.com"Reload lại trang là vào được.
Bước 5 — Kết Nối Nextcloud Với Cloudflare R2
Đây là lúc Nextcloud trở nên hữu ích — thay vì lưu file trên disk VPS, nó dùng R2 làm storage backend thực sự.
- Trong Nextcloud: Apps → tìm "External storage support" → Enable
- Administration Settings → External Storage → Add storage
Điền các thông tin:
- Folder name:
VPS Backup - External storage:
Amazon S3 - Authentication:
Access key - Bucket:
vps-backup - Hostname:
<account_id>.r2.cloudflarestorage.com - Region:
auto - Enable SSL: ✅
- Access key: Access Key ID của bạn
- Secret key: Secret Access Key của bạn
Nhấn ✓ để lưu. Vào Files — sẽ thấy folder VPS Backup. Folder này chính là R2 bucket — mọi thứ bạn thấy ở đây thực sự được lưu trên hạ tầng của Cloudflare, không phải trên VPS.
Bước 6 — Tự Động Backup Với rclone
Nextcloud cho tôi GUI để duyệt file, nhưng việc backup tự động thực sự được xử lý bởi rclone — công cụ command-line sync file trực tiếp lên R2.
Cài rclone
curl https://rclone.org/install.sh | sudo bashCấu Hình rclone
rclone configLàm theo từng bước:
n→ New remote- Name:
r2backup - Storage type:
s3 - Provider:
Cloudflare - Access Key ID: key của bạn
- Secret Access Key: key của bạn
- Endpoint:
https://<account_id>.r2.cloudflarestorage.com - Các câu hỏi còn lại: Enter để mặc định
y→ xác nhậnq→ thoát
Vấn Đề: CreateBucket 403 Access Denied
Khi test, rclone cố tạo bucket mới thay vì dùng bucket có sẵn và bị lỗi 403. Fix:
rclone config update r2backup no_check_bucket trueTest kết nối:
rclone ls r2backup:vps-backupKhông có output nghĩa là bucket trống — bình thường.
Backup Script
nano ~/backup.sh#!/bin/bash
DATE=$(date +%Y-%m-%d_%H-%M)
LOG=/var/log/rclone-backup.log
DEST="r2backup:vps-backup/$DATE"
MAX_BACKUPS=30
echo "[$DATE] Starting backup..." >> $LOG
# /root — toàn bộ project, script, và config trong home directory.
# Loại trừ node_modules và .git vì chúng lớn và có thể tạo lại được:
# node_modules → chạy lại npm install
# .git → clone lại từ GitHub
# .sock là Unix socket của PM2 dùng cho IPC — không phải file thật, rclone không copy được.
rclone sync /root $DEST/root \
--exclude ".npm/**" \
--exclude "*/node_modules/**" \
--exclude "*/.git/**" \
--exclude ".pm2/logs/**" \
--exclude "**.sock" \
-L >> $LOG 2>&1
# /etc/nginx — toàn bộ virtual host config (sites-available, sites-enabled).
# Không có file này, sau khi cài lại phải tự tạo lại từng config cho từng domain.
# Flag -L bắt buộc: sites-enabled/ chứa symlink đến sites-available/, không phải file thật.
# Không có -L, rclone bỏ qua symlink một cách im lặng.
rclone sync /etc/nginx $DEST/nginx -L >> $LOG 2>&1
# /etc/letsencrypt — SSL certificate do Certbot cấp.
# Mất cert này nghĩa là mọi domain sẽ báo lỗi bảo mật cho đến khi re-issue.
# Re-issue dễ nhưng có rate limit (5 cert mỗi domain mỗi tuần).
# Tương tự, -L bắt buộc vì thư mục live/ dùng symlink đến archive/.
rclone sync /etc/letsencrypt $DEST/letsencrypt -L >> $LOG 2>&1
# /root/.ssh — SSH private key, public key, authorized_keys, và config.
# authorized_keys kiểm soát ai được SSH vào server.
# config định nghĩa SSH alias và routing key theo host (ví dụ GitHub).
# Mất file này có nghĩa là bị khóa ngoài hoặc phải tạo lại key ở khắp nơi.
rclone sync /root/.ssh $DEST/ssh -L >> $LOG 2>&1
# /etc/ufw — firewall rules.
# Không có file này, cài mới sẽ không có firewall — tất cả port đều mở
# cho đến khi bạn nhớ ra và tạo lại rules thủ công.
rclone sync /etc/ufw $DEST/ufw -L >> $LOG 2>&1
# /etc/fstab — định nghĩa những gì được mount khi boot, bao gồm swap file.
# Không có file này, swap sẽ không tự mount sau reboot và VPS sẽ thiếu memory.
rclone copy /etc/fstab $DEST/system/ >> $LOG 2>&1
# /etc/sysctl.conf — kernel parameter overrides (ví dụ vm.swappiness=30).
# File nhỏ, dễ quên, khó chịu khi phải làm lại thủ công.
rclone copy /etc/sysctl.conf $DEST/system/ >> $LOG 2>&1
echo "[$DATE] Backup completed!" >> $LOG
# Xoay vòng: xóa folder backup cũ nhất khi vượt quá MAX_BACKUPS.
# Giữ 30 ngày lịch sử và tránh R2 storage tăng vô hạn.
BACKUP_COUNT=$(rclone lsd r2backup:vps-backup | wc -l)
if [ $BACKUP_COUNT -gt $MAX_BACKUPS ]; then
OLDEST=$(rclone lsd r2backup:vps-backup | sort | head -1 | awk '{print $NF}')
echo "[$DATE] Deleting oldest backup: $OLDEST" >> $LOG
rclone purge r2backup:vps-backup/$OLDEST >> $LOG 2>&1
fichmod +x ~/backup.sh
~/backup.sh
cat /var/log/rclone-backup.logĐặt Lịch Backup Tự Động Lúc 2h Sáng
crontab -eThêm:
0 2 * * * /root/backup.shBackup sẽ tự chạy mỗi đêm. Mở Nextcloud tại https://cloud.yourdomain.com và vào VPS Backup — sẽ thấy các folder backup theo ngày ngay trong GUI.
Những Gì Được Backup (và Tại Sao)
Đây là giải thích chi tiết từng path trong script và lý do mỗi thứ quan trọng.
/root — Toàn bộ project, script, và mọi thứ trong home directory. Đây là công việc thực sự của bạn. node_modules và .git được loại trừ có chủ đích vì chúng lớn và có thể tạo lại hoàn toàn — chỉ cần chạy lại npm install hoặc git clone sau khi restore.
/etc/nginx — Toàn bộ Nginx virtual host config. Không có file này, sau khi cài lại bạn phải tự tạo lại config reverse proxy cho từng domain một. Flag -L là bắt buộc vì sites-enabled/ chứa symlink trỏ đến sites-available/, không phải file thật — không có -L, rclone bỏ qua chúng một cách im lặng.
/etc/letsencrypt — SSL certificate do Certbot cấp. Mất cert này khiến mọi domain báo lỗi bảo mật cho đến khi re-issue. Re-issue không khó nhưng Certbot có rate limit 5 cert mỗi domain mỗi tuần. Tương tự cần -L vì thư mục live/ của Certbot dùng symlink đến archive/.
/root/.ssh — SSH private key, public key, authorized_keys, và file config. authorized_keys kiểm soát ai được SSH vào server. File config định nghĩa alias và routing key theo host — ví dụ bảo SSH dùng key nào khi kết nối GitHub. Mất file này nghĩa là bị khóa ngoài hoặc phải tạo lại và đăng ký lại key ở khắp nơi.
/etc/ufw — Firewall rules. Ubuntu mới cài không có firewall active — tất cả port đều mở mặc định. Không có backup này, sau khi cài lại bạn phải nhớ và tạo lại từng ufw allow rule thủ công.
/etc/fstab — Định nghĩa những gì được mount khi boot, bao gồm swap file. Không có file này, swap sẽ không tự load sau reboot và VPS sẽ chạy thiếu memory trong im lặng — thứ bạn có thể không nhận ra cho đến khi bị OOM dưới tải cao.
/etc/sysctl.conf — Kernel parameter overrides như vm.swappiness=30. File nhỏ, dễ quên, khó chịu khi phải tìm lại và làm thủ công.
Những gì cố tình không backup:
node_modules/— có thể tạo lại bằngnpm install.git/— code đã có trên GitHub, clone lại bình thường.sockfiles — Unix socket của PM2 dùng cho IPC, không phải file thật- Nextcloud MariaDB volume — data thực sự của Nextcloud (file của bạn) nằm trên R2, không phải trong DB
Kết
Setup này mất vài tiếng, nhưng đây là thứ bạn chỉ hối hận khi không làm sau khi đã xảy ra sự cố. Nếu hệ thống backup của provider hoạt động bình thường, tôi đã không mất gì. Nhưng đúng là phụ thuộc vào hệ thống của người khác mới là vấn đề.
Với setup này, tôi có 30 bản backup hàng ngày được lưu trên Cloudflare R2, xem được qua Nextcloud GUI, và hoàn toàn độc lập với provider VPS.
Nếu VPS của bạn sập ngày mai, bạn có thể spin up một VPS mới, restore từ R2, và online trở lại trong vòng chưa đầy một tiếng.
Nếu bạn gặp bất kỳ vấn đề nào khi setup, hãy liên hệ với tôi qua phần contact trên portfolio. Tôi đã trải qua những vấn đề tương tự và sẵn lòng giúp bạn giải quyết.