เรื่องจริง: วันที่ลูกค้าสูญเสียข้อมูล 3 ปี เพราะไม่มี Backup
ปี 2022 ลูกค้าร้านค้าออนไลน์โทรมาร้องไห้ตอนตี 4 บอกว่าเว็บหายหมด ผมเข้าไปดู — SSD ของ server เสียทั้งลูก ข้อมูลสินค้า 15,000 รายการ ออเดอร์ 3 ปี รูปสินค้ากว่า 50,000 รูป หายหมด ไม่มี backup แม้แต่ชุดเดียว
ผมใช้เวลา 3 วันพยายามกู้ข้อมูลจาก SSD ที่เสีย กู้ได้แค่ 30% ลูกค้าต้องเริ่มใหม่เกือบทั้งหมด เสียเงินเสียเวลาหลายแสนบาท ทั้งหมดเพราะไม่มี backup
ตั้งแต่วันนั้น ผมทำ backup ให้ลูกค้าทุกราย ก่อนส่งมอบ server เสมอ ใช้ rsync + cron ที่ setup ง่าย ฟรี และเชื่อถือได้ วันนี้จะสอนทุกขั้นตอน
ทำไมต้อง rsync ไม่ใช้ cp หรือ tar
rsync เป็น tool สำหรับ sync ไฟล์ที่ฉลาดกว่า cp มาก เพราะ rsync จะเปรียบเทียบไฟล์ต้นทางกับปลายทาง แล้ว copy เฉพาะไฟล์ที่เปลี่ยนแปลง ทำให้ backup ครั้งแรกอาจใช้เวลานาน แต่ครั้งต่อๆ ไปเร็วมาก เพราะ copy แค่ไฟล์ที่เปลี่ยน
| เปรียบเทียบ | rsync | cp | tar | Veeam/Acronis |
|---|---|---|---|---|
| Incremental | ได้ (เร็วมาก) | ไม่ได้ (copy ทั้งหมด) | ไม่ได้ | ได้ |
| Remote backup | ได้ (ผ่าน SSH) | ไม่ได้ | ไม่ได้ | ได้ |
| Compression | ได้ (-z flag) | ไม่ได้ | ได้ | ได้ |
| Resume | ได้ (--partial) | ไม่ได้ | ไม่ได้ | ได้ |
| ราคา | ฟรี | ฟรี | ฟรี | $$$ |
| Bandwidth | ต่ำ (delta transfer) | สูง | สูง | ปานกลาง |
rsync พื้นฐาน: คำสั่งที่ต้องรู้
Syntax หลัก
# รูปแบบ
rsync [options] source destination
# ตัวอย่าง: backup โฟลเดอร์ local
rsync -avh /var/www/ /backup/www/
# Options ที่ใช้บ่อย:
# -a = archive mode (รักษา permissions, timestamps, symlinks ทุกอย่าง)
# -v = verbose (แสดงรายละเอียด)
# -h = human-readable (แสดงขนาดไฟล์อ่านง่าย)
# -z = compress ระหว่าง transfer (ประหยัด bandwidth)
# -P = --partial + --progress (resume ได้ + แสดง progress)
# --delete = ลบไฟล์ที่ปลายทางที่ต้นทางไม่มีแล้ว
Backup Local → Local
# Backup /var/www ไปยัง /backup
rsync -avh /var/www/ /backup/www/
# Backup พร้อมลบไฟล์เก่าที่ต้นทางลบไปแล้ว
rsync -avh --delete /var/www/ /backup/www/
# Backup เฉพาะไฟล์บางประเภท
rsync -avh --include="*.php" --include="*.html" --exclude="*" /var/www/ /backup/www/
# Exclude ไฟล์ที่ไม่ต้องการ
rsync -avh --exclude="node_modules" --exclude=".git" --exclude="*.log" /var/www/ /backup/www/
# Dry-run ดูก่อนว่าจะทำอะไร (ไม่ทำจริง)
rsync -avhn --delete /var/www/ /backup/www/
ข้อสำคัญ: trailing slash (/) ที่ท้าย source มีความหมาย /var/www/ หมายถึง copy เนื้อหาข้างใน /var/www หมายถึง copy ทั้งโฟลเดอร์ www ไปด้วย ผมเคยพลาดเรื่องนี้ทำให้ backup ผิดโครงสร้าง
Backup Local → Remote (ผ่าน SSH)
# Backup ไปยัง remote server
rsync -avhz -e "ssh -p 22" /var/www/ user@backup-server:/backup/www/
# ใช้ SSH key (ไม่ต้องใส่ password)
rsync -avhz -e "ssh -i /home/user/.ssh/id_ed25519 -p 2847" /var/www/ user@192.168.1.100:/backup/www/
# จำกัด bandwidth (ไม่ให้กิน bandwidth ทั้งหมด)
rsync -avhz --bwlimit=5000 /var/www/ user@backup-server:/backup/www/
# 5000 = 5 MB/s
Backup Remote → Local (ดึง backup กลับ)
# ดึง backup จาก remote มา local
rsync -avhz -e ssh user@remote-server:/var/www/ /local/backup/www/
# ดึงเฉพาะไฟล์ใหม่กว่า
rsync -avhz --update user@remote-server:/var/www/ /local/backup/www/
Backup Script สำหรับ Production Server
#!/bin/bash
# backup.sh — Full backup script for production server
# รันด้วย: sudo bash backup.sh
# === CONFIG ===
BACKUP_DIR="/backup"
REMOTE_USER="backup"
REMOTE_HOST="192.168.1.200"
REMOTE_DIR="/backup/server1"
SSH_KEY="/root/.ssh/backup_key"
SSH_PORT=2847
DATE=$(date +%Y-%m-%d)
LOG="/var/log/backup.log"
RETENTION=30 # เก็บ backup กี่วัน
# === FUNCTIONS ===
log() {
echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a $LOG
}
# === START ===
log "========== Backup Started =========="
# 1. Backup web files
log "1. Backing up /var/www..."
rsync -ah --delete --exclude="node_modules" --exclude=".git" --exclude="*.log" --exclude="cache/*" /var/www/ $BACKUP_DIR/www/
log " Web files: done"
# 2. Backup MySQL databases
log "2. Backing up databases..."
mkdir -p $BACKUP_DIR/mysql/$DATE
for DB in $(mysql -e "SHOW DATABASES" -s --skip-column-names | grep -v "^information_schema$\|^performance_schema$\|^mysql$\|^sys$"); do
mysqldump --single-transaction --routines --triggers $DB | gzip > $BACKUP_DIR/mysql/$DATE/${DB}.sql.gz
log " DB: $DB ($(du -sh $BACKUP_DIR/mysql/$DATE/${DB}.sql.gz | cut -f1))"
done
# 3. Backup config files
log "3. Backing up configs..."
rsync -ah /etc/nginx/ $BACKUP_DIR/config/nginx/
rsync -ah /etc/ssh/sshd_config $BACKUP_DIR/config/
rsync -ah /etc/fail2ban/ $BACKUP_DIR/config/fail2ban/
rsync -ah /etc/crontab $BACKUP_DIR/config/
log " Configs: done"
# 4. Sync to remote server
log "4. Syncing to remote..."
rsync -ahz --delete -e "ssh -i $SSH_KEY -p $SSH_PORT -o StrictHostKeyChecking=no" $BACKUP_DIR/ $REMOTE_USER@$REMOTE_HOST:$REMOTE_DIR/
log " Remote sync: done"
# 5. Cleanup old backups
log "5. Cleaning up old backups..."
find $BACKUP_DIR/mysql -type d -mtime +$RETENTION -exec rm -rf {} + 2>/dev/null
log " Cleanup: done (retention: ${RETENTION} days)"
# 6. Summary
TOTAL_SIZE=$(du -sh $BACKUP_DIR | cut -f1)
log "========== Backup Complete =========="
log "Total size: $TOTAL_SIZE"
log "Remote: $REMOTE_USER@$REMOTE_HOST:$REMOTE_DIR"
ตั้ง Cron Job สำหรับ Backup อัตโนมัติ
# แก้ crontab ของ root
sudo crontab -e
# Backup ทุกวันตอนตี 2
0 2 * * * /opt/scripts/backup.sh >> /var/log/backup_cron.log 2>&1
# Backup ทุก 6 ชั่วโมง (สำหรับ server สำคัญ)
0 */6 * * * /opt/scripts/backup.sh >> /var/log/backup_cron.log 2>&1
# Backup เฉพาะ database ทุก 1 ชั่วโมง
0 * * * * /opt/scripts/db_backup.sh >> /var/log/db_backup_cron.log 2>&1
# เช็คว่า cron ทำงานจริง
grep CRON /var/log/syslog | tail -5
# ดู crontab ที่ตั้งไว้
sudo crontab -l
เคล็ดลับ Cron ที่ต้องรู้
# Cron format: นาที ชั่วโมง วัน เดือน วันในสัปดาห์
# * * * * *
# | | | | |
# | | | | +--- วันในสัปดาห์ (0-7, 0=อาทิตย์)
# | | | +--------- เดือน (1-12)
# | | +--------------- วัน (1-31)
# | +--------------------- ชั่วโมง (0-23)
# +--------------------------- นาที (0-59)
# ตัวอย่าง:
# 0 2 * * * = ทุกวันตี 2
# 0 */6 * * * = ทุก 6 ชั่วโมง
# 0 2 * * 0 = ทุกวันอาทิตย์ตี 2
# 0 2 1 * * = วันที่ 1 ของทุกเดือน ตี 2
# */5 * * * * = ทุก 5 นาที
กลยุทธ์ Backup 3-2-1
กฎ 3-2-1 เป็น best practice ที่ทุกองค์กรควรทำ:
- 3 copies — เก็บข้อมูลอย่างน้อย 3 ชุด (ต้นฉบับ + backup 2 ชุด)
- 2 media types — เก็บบน storage อย่างน้อย 2 ประเภท (เช่น SSD + HDD, local + cloud)
- 1 offsite — อย่างน้อย 1 ชุดต้องอยู่คนละที่ (remote server, cloud, หรือ physical offsite)
# ตัวอย่าง 3-2-1 ด้วย rsync:
# Copy 1: ต้นฉบับบน production server
# /var/www, /var/lib/mysql
# Copy 2: backup server ใน network เดียวกัน (rsync ผ่าน LAN)
rsync -ahz --delete /var/www/ backup-server:/backup/www/
# Copy 3: cloud storage (rsync ไป VPS อีกที่ หรือ rclone ไป S3)
rsync -ahz --delete -e "ssh -p 2847" /backup/ cloud-vps:/offsite-backup/
# หรือใช้ rclone สำหรับ S3, Google Drive, Backblaze B2
rclone sync /backup/ b2:my-backup-bucket/server1/
Restore: กู้คืนข้อมูลจาก Backup
# กู้คืน web files
rsync -avh /backup/www/ /var/www/
# กู้คืน database
gunzip < /backup/mysql/2026-02-09/wordpress.sql.gz | mysql wordpress
# กู้คืนจาก remote backup
rsync -avhz -e "ssh -i /root/.ssh/backup_key -p 2847" backup@192.168.1.200:/backup/server1/www/ /var/www/
# กู้คืนเฉพาะไฟล์บางตัว
rsync -avh /backup/www/wp-content/uploads/2026/ /var/www/wp-content/uploads/2026/
สำคัญมาก: ทดสอบ restore เป็นประจำ backup ที่ restore ไม่ได้ก็เหมือนไม่มี backup ผมทดสอบ restore ทุกเดือน โดย restore ไปยัง test server แล้วเช็คว่าข้อมูลครบถ้วน
Monitoring Backup: แจ้งเตือนเมื่อ Backup ล้มเหลว
#!/bin/bash
# backup_monitor.sh — แจ้งเตือนถ้า backup ไม่ทำงาน
BACKUP_DIR="/backup"
MAX_AGE_HOURS=24 # backup ต้องไม่เก่ากว่า 24 ชั่วโมง
WEBHOOK_URL="https://hooks.slack.com/services/xxx/yyy/zzz"
# หา backup ล่าสุด
LATEST=$(find $BACKUP_DIR -name "*.sql.gz" -type f -printf '%T@ %p
' | sort -n | tail -1)
LATEST_TIME=$(echo $LATEST | cut -d' ' -f1 | cut -d'.' -f1)
NOW=$(date +%s)
AGE_HOURS=$(( (NOW - LATEST_TIME) / 3600 ))
if [ $AGE_HOURS -gt $MAX_AGE_HOURS ]; then
MSG="BACKUP ALERT: Last backup is ${AGE_HOURS} hours old! Server: $(hostname)"
echo $MSG
# ส่ง Slack
curl -X POST -H 'Content-type: application/json' --data "{"text":"$MSG"}" $WEBHOOK_URL
# ส่ง LINE Notify
# curl -X POST https://notify-api.line.me/api/notify # -H "Authorization: Bearer YOUR_TOKEN" # -d "message=$MSG"
else
echo "OK: Last backup ${AGE_HOURS} hours ago"
fi
FAQ คำถามที่พบบ่อย
Q: rsync กับ rclone ต่างกันยังไง?
rsync เหมาะกับ server-to-server ผ่าน SSH rclone เหมาะกับ sync ไป cloud storage (S3, Google Drive, Backblaze B2) ผมใช้ rsync สำหรับ backup ไป backup server ในเครือข่าย แล้วใช้ rclone sync ไป cloud เป็น offsite backup
Q: Backup ใช้ disk เยอะมาก ทำยังไงดี?
ใช้ --link-dest สร้าง incremental backup แบบ hard link ไฟล์ที่ไม่เปลี่ยนจะ link ไปยัง backup เก่า ไม่กิน disk เพิ่ม เฉพาะไฟล์ที่เปลี่ยนเท่านั้นที่ใช้ disk ใหม่
# Incremental backup ด้วย hard link
LATEST=$(ls -td /backup/daily/*/ | head -1)
rsync -ah --delete --link-dest=$LATEST /var/www/ /backup/daily/$(date +%Y-%m-%d)/
Q: Backup database ควรใช้ rsync หรือ mysqldump?
ใช้ mysqldump สำหรับ backup database เสมอ อย่า rsync ไฟล์ database โดยตรง เพราะอาจได้ไฟล์ที่ corrupt ถ้า database กำลังเขียนอยู่ mysqldump จะ lock table ให้อัตโนมัติ (ใช้ --single-transaction สำหรับ InnoDB)
Q: ถ้า server ถูก ransomware backup จะปลอดภัยไหม?
ถ้า backup อยู่บน server เดียวกัน ไม่ปลอดภัย ransomware จะเข้ารหัส backup ด้วย ต้องมี offsite backup ที่ ransomware เข้าถึงไม่ได้ เช่น backup server ที่ใช้ pull-based backup (backup server ดึงข้อมูลมาเอง ไม่ใช่ production push ไป) หรือ cloud storage ที่เปิด versioning + object lock
Q: ควร backup บ่อยแค่ไหน?
ขึ้นอยู่กับ RPO (Recovery Point Objective) ถ้ายอมเสียข้อมูลได้ 24 ชั่วโมง backup วันละครั้ง ถ้ายอมเสียได้แค่ 1 ชั่วโมง backup ทุกชั่วโมง สำหรับ database สำคัญ ผมใช้ binary log replication เป็น real-time backup เพิ่มจาก daily mysqldump
rsync Options ที่ควรรู้จักทั้งหมด
| Option | ความหมาย | ใช้เมื่อไหร่ |
|---|---|---|
| -a | Archive mode (รักษา permissions, timestamps, symlinks) | ใช้ทุกครั้ง |
| -v | Verbose แสดงรายละเอียด | ใช้ตอน debug |
| -h | Human-readable ขนาดไฟล์ | ใช้ทุกครั้ง |
| -z | Compress ระหว่าง transfer | ใช้กับ remote backup |
| -P | Progress + partial (resume ได้) | ใช้กับไฟล์ใหญ่ |
| --delete | ลบไฟล์ที่ปลายทางที่ต้นทางไม่มี | ใช้กับ mirror backup |
| --exclude | ไม่รวมไฟล์/โฟลเดอร์ | ใช้ exclude cache, logs |
| --bwlimit | จำกัด bandwidth (KB/s) | ใช้กับ production server |
| --link-dest | Hard link กับ backup เก่า | ใช้กับ incremental backup |
| -n / --dry-run | ทดสอบไม่ทำจริง | ใช้ก่อนรันจริงทุกครั้ง |
| --checksum | เปรียบเทียบด้วย checksum แทน timestamp | ใช้เมื่อต้องการความแม่นยำสูง |
| --backup --suffix | เก็บ version เก่าของไฟล์ที่ถูก overwrite | ใช้เมื่อต้องการ versioning |
Incremental Backup ด้วย Hard Link — ประหยัด Disk สุดๆ
วิธีนี้เป็นเทคนิคที่ดีที่สุดสำหรับ daily backup เก็บ backup ทุกวันแต่ใช้ disk เพิ่มแค่ส่วนที่เปลี่ยนแปลง เพราะไฟล์ที่ไม่เปลี่ยนจะ hard link ไปยัง backup วันก่อน ไม่กิน disk เพิ่ม
#!/bin/bash
# incremental-backup.sh — Incremental backup with hard links
DATE=$(date +%Y-%m-%d)
BACKUP_BASE="/backup/daily"
SOURCE="/var/www"
LATEST="$BACKUP_BASE/latest"
TARGET="$BACKUP_BASE/$DATE"
# สร้าง incremental backup
if [ -d "$LATEST" ]; then
rsync -ah --delete --link-dest="$LATEST" "$SOURCE/" "$TARGET/"
else
rsync -ah --delete "$SOURCE/" "$TARGET/"
fi
# อัพเดท symlink "latest"
rm -f "$LATEST"
ln -s "$TARGET" "$LATEST"
# ลบ backup เก่ากว่า 30 วัน
find "$BACKUP_BASE" -maxdepth 1 -type d -mtime +30 -exec rm -rf {} +
echo "Backup done: $TARGET"
du -sh "$TARGET"
ตัวอย่าง: ถ้ามีไฟล์ 10 GB แต่แต่ละวันเปลี่ยนแค่ 100 MB backup 30 วันจะใช้ disk แค่ 10 GB + (30 x 100 MB) = 13 GB แทนที่จะเป็น 30 x 10 GB = 300 GB ถ้าไม่ใช้ hard link ประหยัดได้มหาศาล
ทดสอบ Backup: สิ่งที่ต้องทำทุกเดือน
# 1. ทดสอบ restore ไปยัง temp directory
mkdir -p /tmp/restore_test
rsync -avh /backup/www/ /tmp/restore_test/www/
# 2. เปรียบเทียบกับต้นฉบับ
diff -rq /var/www/ /tmp/restore_test/www/
# 3. ทดสอบ restore database
gunzip < /backup/mysql/latest/wordpress.sql.gz | mysql -u root test_wordpress
# เช็คว่า table ครบ record ครบ
# 4. ลบ test data
rm -rf /tmp/restore_test
mysql -e "DROP DATABASE test_wordpress"
echo "Restore test: PASSED"
ผมตั้ง calendar reminder ทดสอบ restore ทุกเดือน ใช้เวลาแค่ 15 นาที แต่ช่วยให้มั่นใจว่า backup ใช้งานได้จริง เพราะ backup ที่ restore ไม่ได้ก็เหมือนไม่มี backup เลย
สรุป: Backup Checklist สำหรับทุก Server
ก่อนส่งมอบ server ให้ลูกค้า ผมเช็ค checklist นี้ทุกครั้ง ถ้าทำครบทุกข้อ มั่นใจได้ว่าข้อมูลปลอดภัย ไม่ว่าจะเกิดอะไรขึ้น disk เสีย โดน ransomware หรือแม้แต่ data center ไฟไหม้ ก็กู้คืนได้ภายในไม่กี่ชั่วโมง
- rsync script พร้อมใช้งาน — ทดสอบแล้วว่ารันได้ไม่มี error
- Cron job ตั้งแล้ว — เช็คด้วย crontab -l ว่ามี schedule ถูกต้อง
- Remote backup ทำงาน — SSH key ใช้ได้ rsync ไป remote สำเร็จ
- Monitoring alert ตั้งแล้ว — แจ้งเตือนถ้า backup ไม่ทำงานเกิน 24 ชั่วโมง
- Restore ทดสอบแล้ว — กู้คืนได้จริง ข้อมูลครบถ้วน
- 3-2-1 rule ครบ — มี backup อย่างน้อย 3 ชุด 2 media 1 offsite
🎬 ดูคลิปเพิ่มเติม: @icafefx