8 phút đọc
Insights / debugging

Script chạy tay trên terminal thì ngon lành nhưng ném vào crontab lại không chịu chạy

Nguyễn Trung Hưng
Đăng ngày 10/09/2026 Senior DevOps Engineer
Script chạy tay trên terminal thì ngon lành nhưng ném vào crontab lại không chịu chạy

Nay mình chia sẻ một kiến thức/kinh nghiệm mà có thể những anh em mới dùng script và crontab của Linux chưa biết hay muốn hiểu sâu hơn để quản trị hạ tầng.

Ví dụ khi anh em viết xong một script backup database hoặc dọn log, gõ ./backup.sh trên terminal để test thì chạy ngon, file sinh ra đầy đủ. lúc này chỉ cần thêm vào crontab -e và đặt hẹn 2h sáng chạy tự động chẳng hạn:

0 2 * * * /home/ubuntu/scripts/backup.sh

Rồi sáng hôm sau vào kiểm tra, không có file backup nào được tạo ra, log không thấy đâu, và khi chạy lại lệnh đó bằng tay trên terminal thì nó… vẫn chạy bình thường 😀

Lúc này anh em sẽ có thể thắc mắc là tại sao cùng một script, gõ tay trên terminal thì chạy ngon lành nhưng đưa vào crontab lại không chịu chạy?

Vấn đề cốt lõi nằm ở chỗ: Khi chạy qua Cron, script thực thi trong một subshell tách biệt, không kế thừa $PATH và biến môi trường của user, khác thư mục làm việc, và không có màn hình terminal để hiển thị log lỗi.

Mình bóc tách cơ chế bên dưới và cách khắc phục chuẩn xác mong rằng giúp cho anh em chưa biết tiết kiệm thời gian chatgpt nhé.

Cơ chế thực thi: Sự khác biệt giữa Terminal Session và Cron Daemon

Khi một câu lệnh được thực thi trên Linux, hành vi của nó phụ thuộc vào môi trường shell bao quanh:

  • Khi anh em SSH và chạy trên terminal: Hệ điều hành nạp đầy đủ các file cấu hình như /etc/profile, ~/.bashrc, ~/.bash_profile. Biến môi trường $PATH lúc này chứa đầy đủ các đường dẫn tới các phần mềm đã cài đặt (node, python, docker, aws-cli). Đồng thời, lệnh có màn hình hiển thị (TTY) để in log và thông báo lỗi.
  • Khi Cron daemon chạy tác vụ: Cron khởi tạo một subshell riêng biệt, không nạp ~/.bashrc, không có TTY gắn kèm, và chỉ cung cấp một tập hợp biến môi trường tối giản.

Để thấy rõ sự khác biệt này, anh em có thể làm một test nhỏ bằng cách cho Cron xuất toàn bộ biến môi trường ra một file tạm:

# Thêm dòng này vào crontab -e để test:
* * * * * env > /tmp/cron-env.txt

Sau đó, so sánh số lượng biến môi trường giữa hai bên:

# Đếm số biến môi trường khi anh em đăng nhập terminal
$ env | wc -l
42          # terminal nạp sẵn hơn 40 biến môi trường (USER, SHELL, PATH, NVM, SSH...)

# Đếm số biến môi trường mà Cron thực sự nhận được
$ cat /tmp/cron-env.txt | wc -l
5           # Cron chỉ có vỏn vẹn 5 biến tối giản!

Nội dung thực tế bên trong file /tmp/cron-env.txt thường chỉ gồm:

HOME=/home/ubuntu
LOGNAME=ubuntu
PATH=/usr/bin:/bin
SHELL=/bin/sh
PWD=/home/ubuntu

Nhìn vào đây, anh em sẽ thấy ngay gốc rễ là không có bất kỳ biến môi trường tùy chỉnh nào, $PATH bị rút gọn chỉ còn /usr/bin:/bin, và shell mặc định là /bin/sh chứ không phải /bin/bash mà anh em thường dùng.

4 nguyên nhân phổ biến khiến cronjob không hoạt động

1. Biến $PATH của cron bị thiếu đường dẫn binary

Khi chạy trên terminal, lệnh which cho thấy binary có thể nằm ở các folder như /usr/local/bin hoặc trong folder riêng của user:

echo $PATH
# /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/home/ubuntu/.nvm/versions/node/v18.16.0/bin

Tuy nhiên, giá trị $PATH mặc định do cron daemon cấp thường chỉ có:

PATH=/usr/bin:/bin

Nếu trong script anh em gọi trực tiếp các lệnh như docker-compose, mysqldump, aws, hay node (vốn thường nằm ở /usr/local/bin hoặc thư mục quản lý version), cron sẽ báo lỗi:

backup.sh: line 5: mysqldump: command not found

Do không tìm thấy lệnh, script sẽ dừng thực thi ngay tại dòng đó.

2. Sử dụng đường dẫn tương đối

Giả sử script được đặt tại /opt/apps/cleaner.sh với nội dung:

#!/bin/bash
source ./config.env
rm -rf ./temp_logs/*.log

Khi anh em đứng tại /opt/apps và gõ ./cleaner.sh, script hiểu ./config.env/opt/apps/config.env.

Nhưng khi Cron thực thi dòng lệnh 0 * * * * /opt/apps/cleaner.sh, thư mục làm việc hiện tại của tiến trình là thư mục Home của user chạy cron (ví dụ /home/ubuntu hoặc /root), chứ không phải /opt/apps.

Kết quả:

  • Script tìm file cấu hình tại /home/ubuntu/config.env -> Báo lỗi không tìm thấy file (No such file or directory).
  • Các thao tác đọc/ghi file theo đường dẫn tương đối đều bị trỏ sai vị trí.

3. Thiếu biến môi trường ứng dụng

Một số ứng dụng hoặc script cần các env để xác thực hoặc cấu hình kết nối, ví dụ:

export DB_PASSWORD="my-password"
export AWS_DEFAULT_REGION="ap-southeast-1"

Nếu các biến này chỉ được khai báo trong ~/.bashrc hoặc ~/.bash_profile, Cron sẽ không nạp chúng. Khi script chạy, các biến này sẽ mang giá trị rỗng (empty), dẫn đến việc script không thể kết nối database hoặc gọi API bên ngoài.

4. Không cấu hình chuyển hướng log

Khi chạy script trên terminal, mọi thông tin in ra từ stdoutstderr đều hiển thị.

Với Cron, do là tiến trình chạy ngầm không có màn hình console, mặc định hệ thống sẽ cố gắng gửi output qua dịch vụ mail nội bộ. Nếu server không cấu hình mail service, toàn bộ thông báo lỗi sẽ không được lưu lại ở đâu, khiến việc xác định nguyên nhân trở nên khó khăn.

Runbook 3 bước: Kiểm tra và tái hiện lỗi nhanh chóng

Để xác định chính xác lỗi mà không cần phỏng đoán, anh em có thể áp dụng 3 bước sau:

Bước 1: Chuyển hướng toàn bộ output và error vào file log

Cập nhật dòng cấu hình trong crontab -e, chuyển cả luồng stdout (kênh 1) và stderr (kênh 2) vào một file log cụ thể:

0 2 * * * /home/ubuntu/scripts/backup.sh >> /var/log/backup_cron.log 2>&1
  • >>: Ghi nối tiếp vào file log, không ghi đè làm mất lịch sử cũ.
  • 2>&1: Gom toàn bộ thông báo lỗi ở kênh stderr sang kênh stdout để cùng ghi vào file log.

Bước 2: Tạo môi trường Cron trên terminal bằng lệnh env -i

Không cần chờ đến thời điểm cron kích hoạt, anh em có thể dùng lệnh env -i để xóa sạch môi trường shell hiện tại và chạy script với đúng biến $PATH tối giản của Cron:

env -i PATH="/usr/bin:/bin" /bin/bash /home/ubuntu/scripts/backup.sh

Nếu script có lỗi thiếu đường dẫn lệnh hoặc sai thư mục làm việc, thông báo lỗi sẽ in ra ngay trên terminal:

/home/ubuntu/scripts/backup.sh: line 4: /usr/local/bin/aws: No such file or directory
/home/ubuntu/scripts/backup.sh: line 7: ./config.env: No such file or directory

Bước 3: Xác định đường dẫn tuyệt đối của các công cụ bằng lệnh which

Kiểm tra vị trí chính xác của từng binary trên server:

which python3
# Output: /usr/bin/python3

which docker
# Output: /usr/bin/docker

which mysqldump
# Output: /usr/local/bin/mysqldump   # Nằm ngoài /usr/bin và /bin mặc định của Cron

Giải pháp khắc phục triệt để

1. Chuẩn hóa Script tự xác định thư mục và nạp môi trường

Viết script theo hướng self-contained, tự động lấy đường dẫn tuyệt đối của thư mục chứa nó và định nghĩa rõ $PATH:

#!/usr/bin/env bash

# Dừng script ngay khi có lệnh lỗi hoặc biến chưa được gán giá trị
set -euo pipefail

# Xác định đường dẫn tuyệt đối của thư mục chứa script
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"
cd "$SCRIPT_DIR" || exit 1

# Bổ sung các đường dẫn cần thiết vào biến PATH
export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:$PATH"

# Nạp file biến môi trường nếu có
if [ -f "$SCRIPT_DIR/.env" ]; then
    # shellcheck source=/dev/null
    source "$SCRIPT_DIR/.env"
fi

# Thực thi tác vụ chính với đường dẫn rõ ràng
echo "[$(date '+%Y-%m-%d %H:%M:%S')] Bắt đầu chạy backup..."

/usr/local/bin/mysqldump -u root -p"${DB_PASSWORD}" my_database > "$SCRIPT_DIR/dumps/backup_$(date +%F).sql"

echo "[$(date '+%Y-%m-%d %H:%M:%S')] Hoàn tất backup thành công!"

2. Khai báo SHELL và PATH trực tiếp ở đầu Crontab

Nếu trong crontab có nhiều tác vụ, anh em có thể định nghĩa sẵn biến SHELLPATH ở các dòng đầu tiên của file cấu hình crontab -e:

# Chỉ định shell thực thi mặc định
SHELL=/bin/bash

# Khai báo PATH đầy đủ cho toàn bộ các job bên dưới
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/home/ubuntu/.nvm/versions/node/v18.16.0/bin

# Danh sách tác vụ kèm log output rõ ràng
0 2 * * * /home/ubuntu/scripts/backup.sh >> /var/log/backup.log 2>&1
30 3 * * * /home/ubuntu/scripts/cleanup.sh >> /var/log/cleanup.log 2>&1

Kết luận

Trên là những phần kiến thức và kinh nghiệm cùng với các sample rất cụ thể mình làm, việc script chạy được trên terminal nhưng không chạy được qua crontab là vấn đề hoàn toàn mang tính quy luật về môi trường thực thi trong hệ điều hành Linux.

Khi thiết lập các tác vụ tự động hóa, quy tắc quan trọng là không giả định cronjob sẽ tự có môi trường giống như terminal của người dùng. Luôn chủ động kiểm soát đường dẫn tuyệt đối, biến $PATH, biến môi trường và cơ chế ghi log để đảm bảo hệ thống vận hành tin cậy và dễ bảo trì.

Vì thấy phần này hiểu sâu cũng rất tốt nên mình làm bài chia sẻ này mong sẽ hữu ích được cho những bạn chưa biết nhé.

Chia sẻ bài viết

Theo dõi
Thông báo của
0 Góp ý
Được bỏ phiếu nhiều nhất
Mới nhất Cũ nhất
Đã sao chép liên kết vào bộ nhớ tạm!