前言:定時任務為什麼總是「不跑」?
Crontab 是 Linux 系統級排程器,但 PHP 腳本在 Web 環境正常、放入 crontab 卻靜默失效,是運維高頻痛點。本文系統梳理 **權限、路徑、環境變數** 三大核心成因,提供標準化排查流程與最佳實踐模板。
快速排查清單(按優先級)
- 執行權限:腳本檔案是否有 `x` 權限?Shebang 是否正確?
- 絕對路徑:crontab 中 PHP 二進制、腳本路徑、include/require 路徑是否全為絕對路徑?
- 環境變數:PATH、HOME、LD_LIBRARY_PATH、自定義變數是否缺失?
- 輸出導向:標準輸出/錯誤輸出是否重導向至日誌檔?
- 執行上下文:是否依賴 `$_SERVER`、`$_ENV`、`$_GET` 等 Web 專有變數?
核心問題深度解析與解決
1. 執行權限與 Shebang
# 檢查權限
ls -la /path/to/script.php
# -rwxr-xr-x 1 user group 1234 Jan 1 00:00 script.php ✅ 有 x 權限
# -rw-r--r-- 1 user group 1234 Jan 1 00:00 script.php ❌ 缺少 x 權限
# 添加執行權限
chmod +x /path/to/script.php
# 檢查 Shebang (第一行必須是)
head -1 /path/to/script.php
# #!/usr/bin/env php ✅ 推薦:自動尋找 php
# #!/usr/local/php/bin/php ✅ 可行但硬編碼路徑
2. 絕對路徑陷阱(最常見)
錯誤範例:
# crontab -e
* * * * * php script.php # ❌ php 不在 PATH,script.php 相對路徑
* * * * * /usr/bin/php script.php # ❌ script.php 相對路徑,cwd 不一定是腳本目錄
正確範例:
# 方式一:cd 進入目錄再執行(推薦)
* * * * * cd /var/www/project && /usr/local/php/bin/php script.php >> /var/log/cron_script.log 2>&1
# 方式二:全絕對路徑
* * * * * /usr/local/php/bin/php /var/www/project/script.php >> /var/log/cron_script.log 2>&1
PHP 內部 include/require 也必須用絕對路徑:
// ❌ 相對路徑,cron 執行時 cwd 不確定
require_once '../config.php';
require_once './lib/db.php';
// ✅ 使用 __DIR__ 魔術常數
require_once __DIR__ . '/../config.php';
require_once __DIR__ . '/lib/db.php';
// ✅ 或定義專案根目錄常數(入口檔案)
define('APP_ROOT', __DIR__ . '/..');
require_once APP_ROOT . '/config.php';
3. 環境變數缺失
Cron 執行環境極度精簡,常見缺失:
| 變數 | 影響 | 解決 |
| PATH | 系統命令 (mysqldump, git, composer) 找不到 | crontab 開頭定義 `PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/php/bin` |
| HOME | Composer、SSH 金鑰、~/.bashrc 失效 | 設定 `HOME=/home/wwwuser` |
| LD_LIBRARY_PATH | PHP 擴展依賴的共享庫找不到 (如 libxml2, openssl) | 設定 `LD_LIBRARY_PATH=/usr/local/lib:/usr/lib` |
| 自定義變數 | 應用讀取 `getenv('DB_PASSWORD')` 失敗 | crontab 定義或腳本內 `putenv()` |
完整 crontab 範本
# 環境變數定義(放在 crontab 最前面)
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/php/bin
HOME=/home/wwwuser
LD_LIBRARY_PATH=/usr/local/lib:/usr/lib
APP_ENV=production
# 日誌目錄確保存在
# * * * * * mkdir -p /var/log/cron
# 任務定義
# 每分鐘執行一次(測試用)
* * * * * cd /var/www/project && /usr/local/php/bin/php artisan schedule:run >> /var/log/cron_schedule.log 2>&1
# 每天凌晨 2:30 備份資料庫
30 2 * * * cd /var/www/project && /usr/local/php/bin/php script/backup_db.php >> /var/log/cron_backup.log 2>&1
# 每小時清理快取
0 * * * * cd /var/www/project && /usr/local/php/bin/php script/clear_cache.php >> /var/log/cron_cache.log 2>&1
4. 依賴 Web 專有超全域變數
問題代碼:
// ❌ Cron 環境下不存在
$host = $_SERVER['HTTP_HOST'];
$uri = $_SERVER['REQUEST_URI'];
$ip = $_SERVER['REMOTE_ADDR'];
$docRoot = $_SERVER['DOCUMENT_ROOT'];
解決方案:
// ✅ 使用配置檔或環境變數
$host = getenv('APP_HOST') ?: 'cli.local';
$baseUrl = getenv('APP_BASE_URL') ?: 'https://cli.local';
$docRoot = getenv('APP_DOC_ROOT') ?: __DIR__ . '/../public';
// ✅ 或判斷 SAPI 分流
if (php_sapi_name() === 'cli') {
// CLI 專用配置
$config = require __DIR__ . '/config/cli.php';
} else {
// Web 專用配置
$config = require __DIR__ . '/config/web.php';
}
調試與驗證技巧
1. 手動模擬 Cron 環境執行
# 切換到執行用戶(通常是 www-data、nginx、wwwuser)
sudo -u wwwuser -H /bin/bash -c "cd /var/www/project && /usr/local/php/bin/php script.php"
# 或直接用 root 測試(注意權限差異)
cd /var/www/project && /usr/local/php/bin/php script.php
2. 捕獲錯誤輸出
# 2>&1 將 stderr 合併到 stdout,統一寫入日誌
* * * * * /usr/bin/php /path/script.php >> /var/log/script.log 2>&1
# 分離輸出
* * * * * /usr/bin/php /path/script.php >> /var/log/script_out.log 2>> /var/log/script_err.log
3. 在腳本內記錄執行軌跡
4. 驗證 Crontab 服務狀態
systemctl status cron # systemd 系統
service cron status # SysVinit 系統
crontab -l # 查看當前用戶任務
crontab -u wwwuser -l # 查看指定用戶任務(需 root)
進階:系統級定時器替代方案
| 方案 | 優點 | 缺點 | 適用場景 |
| systemd Timer | 精確到秒、支援依賴、日誌整合、隨機延遲 | 配置較複雜、需 root | 生產環境核心任務 |
| Supervisor + 長運行腳本 | 進程管理、自動重啟、日誌輪轉 | 需額外元件 | 隊列消費者、WebSocket 服務 |
| Laravel Scheduler / Symfony Scheduler | PHP 定義排程、可測試、避免重疊 | 框架綁定 | 框架專案 |
| Jenkins / GitLab CI 排程 | 可視化、歷史記錄、通知整合 | 較重、延遲較大 | 部署、構建、報表任務 |
結語
Crontab 執行 PHP 失敗 90% 歸因於 **路徑不絕對、環境變數缺失、權限不足**。建立標準化部署模板、強制絕對路徑、完善日誌記錄,能徹底消除此類問題。現代專案建議遷移至 systemd Timer 或框架內建調度器,獲得更好的可觀測性與維護性。
- 上一篇文章: PHP CLI 模式下傳遞與接收命令列參數完整教學
- 下一篇文章: Windows 環境安裝 Yii 2 框架完整指南:Composer 方式與常見問題解決
Copyright ©
程式碼之家 All Rights Reserved