Mình dựng một con Linux Server chạy Database (MySQL) và nhận yêu cầu setup quy trình backup tự động vào 3 giờ sáng hàng ngày.
Mình viết nhanh một Shell script backup_db.sh. Vì muốn bảo mật, mình không hardcode mật khẩu vào script mà gọi biến $DB_USER và $DB_PASS từ môi trường (được khai báo sẵn trong .bashrc).

Code trông gọn gàng thế này:
#!/bin/bash
# Biến DB_USER và DB_PASS lấy từ môi trường
mysqldump -u $DB_USER -p$DB_PASS my_database > /backup/db_$(date +%F).sql
Quy trình test diễn ra suôn sẻ: SSH vào server, chạy thử ./backup_db.sh, kết quả trả về file SQL đầy đủ dữ liệu. Tự tin script đã ngon, mình gán nó vào Crontab:
0 3 * * * /home/user/scripts/backup_db.sh
Cú lừa lúc 3 giờ sáng
Sáng hôm sau kiểm tra:
- Trong thư mục
/backup, file có được tạo ra nhưng dung lượng là 0 bytes. - Kiểm tra syslog, Cron daemon báo lệnh đã thực thi đúng giờ, không có báo lỗi.
Lúc này, phản xạ đầu tiên thường dẫn chúng ta đi sai hướng:
- Nghi do user chạy Cron không có quyền ghi vào thư mục backup? Mình thử
chmod 777nhưng file vẫn rỗng. - Nghi đầy ổ cứng? Check
df -hthấy vẫn còn trống cả trăm GB. - Nghi script logic sai? Chạy lại bằng tay thì vẫn thành công.
Debug và tìm ra nguyên nhân
Để biết chính xác chuyện gì đang xảy ra, mình sửa lại Crontab để bắn toàn bộ log ra file thay vì để nó chạy “mù”:
0 3 * * * /home/user/scripts/backup_db.sh > /tmp/backup_debug.log 2>&1
Đọc file log, nguyên nhân lộ diện ngay lập tức. Lỗi trả về là mysqldump: command not found hoặc Access denied for user...
Tại sao chạy tay thì được, Cron chạy thì lỗi?
Vấn đề nằm ở sự khác biệt môi trường:
- Thiếu biến PATH: Khi bạn SSH vào, bạn dùng Shell tương tác. Nó load đầy đủ file cấu hình nên biết lệnh mysqldump nằm ở đâu. Ngược lại, Cron chạy shell không tương tác với biến
PATHcực kỳ tối giản (thường chỉ có/usr/bin:/bin). Nếumysqldumpnằm ở/usr/local/bin, Cron sẽ không tìm thấy. - Mất biến môi trường: Cron không hề load file
.bashrchay.profile. Do đó, các biến$DB_USERvà$DB_PASShoàn toàn trống rỗng. Câu lệnh thực tế Cron chạy làmysqldump -u -p ...dẫn đến lỗi xác thực.
Tại sao file lại 0 bytes?
Đây là cơ chế của shell: Khi gặp dấu điều hướng >, hệ thống sẽ tạo file rỗng trước để chuẩn bị nhận dữ liệu. Sau đó lệnh mysqldump mới chạy. Do lệnh lỗi và thoát ngay lập tức, không có byte dữ liệu nào được ghi vào, để lại một xác file rỗng tuếch.
Giải pháp tối ưu
Để khắc phục triệt để, chúng ta cần làm theo trình tự 4 bước sau, đảm bảo 3 yếu tố: Đường dẫn tuyệt đối, Bảo mật và Cơ chế tự ngắt lỗi.
Bước 1: Tạo file chứa mật khẩu credentials
Để bảo mật, tránh lộ mật khẩu khi ai đó soi process trên server. Tạo file nano /home/user/.db-creds.cnf:
[client]
user=backup_user
password=secret_password
host=localhost
Bước 2: Viết lại Script hoàn chỉnh
Tạo file nano /home/user/scripts/backup_db.sh. Script này áp dụng đủ các nguyên tắc an toàn:
#!/bin/bash
# Dừng script ngay nếu có lỗi
set -e
# Quan trọng: Bắt lỗi ngay cả khi nó xảy ra trong pipeline
# Nếu mysqldump lỗi thì gzip cũng phải dừng theo
set -o pipefail
# Cấu hình đường dẫn
BACKUP_DIR="/backup"
DATE=$(date +%F)
CRED_FILE="/home/user/.db-creds.cnf"
# KHÔNG dùng lệnh tắt, hãy dùng đường dẫn tuyệt đối (check bằng lệnh `which mysqldump`)
MYSQLDUMP="/usr/bin/mysqldump"
GZIP="/bin/gzip"
# Đảm bảo thư mục tồn tại
mkdir -p "$BACKUP_DIR"
echo "Bắt đầu backup ngày $DATE..."
# Chạy backup kết hợp nén
# --defaults-extra-file giúp nạp user/pass an toàn
$MYSQLDUMP --defaults-extra-file=$CRED_FILE my_database | $GZIP > "$BACKUP_DIR/db_$DATE.sql.gz"
# Chính sách lưu trữ: Xóa file cũ hơn 30 ngày để tránh đầy ổ cứng
find "$BACKUP_DIR" -name "db_*.sql.gz" -mtime +30 -delete
echo "Backup thành công: $BACKUP_DIR/db_$DATE.sql.gz"
Bước 3: Phân quyền (Bước quan trọng hay bị quên)
Linux mặc định không cho phép chạy các file text mới tạo. Bạn cần cấp quyền thực thi cho script và khóa quyền đọc file mật khẩu.
# Chỉ cho chủ sở hữu đọc file mật khẩu (Bảo mật)
chmod 600 /home/user/.db-creds.cnf
# Cấp quyền thực thi cho script backup (Bắt buộc để Cron chạy được)
chmod +x /home/user/scripts/backup_db.sh
Bước 4: Cập nhật lại Crontab
Sửa lại Crontab (crontab -e) để lưu lại lịch sử chạy, giúp dễ dàng kiểm tra sau này.
# Chạy vào 03:00 sáng mỗi ngày, ghi nối log vào file
0 3 * * * /home/user/scripts/backup_db.sh >> /var/log/db_backup.log 2>&1
Lời kết
Việc chuyển đổi từ thao tác thủ công sang tự động hóa không đơn giản chỉ là copy-paste lệnh vào Crontab. Sự cố file rỗng này là lời nhắc nhở rằng Cronjob là một môi trường rất “khắt khe” và khác biệt hoàn toàn so với những gì chúng ta thao tác tay hàng ngày.
Để có những giấc ngủ ngon và không phải giật mình lúc nửa đêm, hãy đảm bảo mọi script tự động của bạn luôn tự chủ về đường dẫn, bảo mật về thông tin và không bao giờ im lặng khi gặp lỗi.






