Chuyển đến nội dung
Jok Holk
EN | VI

Đă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:

  1. Backup các path quan trọng nhất trên VPS
  2. Lưu mỗi bản backup vào một folder theo ngày (ví dụ 2026-03-16_02-00)
  3. Giữ tối đa 30 ngày backup
  4. 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.

bash
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 --version

Bước 3 — Setup Nextcloud Với Docker Compose

bash
mkdir ~/nextcloud && cd ~/nextcloud
nano docker-compose.yml
yaml
services:
  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_password bằng password mạnh.

  • your_root_password và your_db_password chỉ dùng nội bộ Docker — đặt phức tạp nhưng không cần nhớ.
  • your_admin_password dùng để đăng nhập Nextcloud GUI hàng ngày — nhớ cái này.
bash
docker compose up -d
docker compose ps

Cả 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.

bash
sudo apt install nginx certbot python3-certbot-nginx -y
sudo nano /etc/nginx/sites-available/nextcloud
nginx
server {
    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;
    }
}
bash
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.com

Thê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:

bash
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ự.

  1. Trong Nextcloud: Apps → tìm "External storage support" → Enable
  2. 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

bash
curl https://rclone.org/install.sh | sudo bash

Cấu Hình rclone

bash
rclone config

Là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ận
  • q → 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:

bash
rclone config update r2backup no_check_bucket true

Test kết nối:

bash
rclone ls r2backup:vps-backup

Không có output nghĩa là bucket trống — bình thường.

Backup Script

bash
nano ~/backup.sh
bash
#!/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
fi
bash
chmod +x ~/backup.sh
~/backup.sh
cat /var/log/rclone-backup.log

Đặt Lịch Backup Tự Động Lúc 2h Sáng

bash
crontab -e

Thêm:

0 2 * * * /root/backup.sh

Backup 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ằng npm install
  • .git/ — code đã có trên GitHub, clone lại bình thường
  • .sock files — 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.