SSD Nodes Learn Hosting plans →
คู่มือ Matt Connorโดย Matt Connor · อัปเดตเมื่อ 2026-09-04

ทำไม Cron Job ไม่ทำงาน? 5 สาเหตุที่พบบ่อยและวิธีแก้ไข

พบกับ 5 สาเหตุหลักที่ทำให้ Cron Job ไม่ทำงาน ตั้งแต่ปัญหา PATH, เครื่องหมายเปอร์เซ็นต์, การใช้ไฟล์ crontab ผิดพลาด, ผลลัพธ์ที่หายไปใน mail จนถึงสคริปต์ที่ต้องการ shell

เหตุใด cron job ของคุณจึงไม่ทำงาน

cron job ที่ "ไม่เคยทำงาน" แท้จริงแล้วเกือบทั้งหมดได้ทำงานไปแล้ว มันทำงานในสภาพแวดล้อมที่ไม่ใช่ shell ของคุณ มันล้มเหลวในวินาทีแรก และข้อความแจ้งเตือนถูกส่งไปยังที่ที่คุณไม่ได้ตรวจสอบ สาเหตุ 5 ประการอธิบายปัญหาที่พบได้เกือบทั้งหมด ได้แก่: search path, เครื่องหมายเปอร์เซ็นต์, การใช้ไฟล์ crontab ผิดไฟล์, ผลลัพธ์ที่ถูกส่งไปยัง mail และสคริปต์ที่คาดหวังว่าจะได้รับ login session

cron เป็น daemon (บริการเบื้องหลัง) ที่อ่านไฟล์ crontab และเริ่มคำสั่งตามกำหนดเวลา มันไม่ได้อ่าน .bashrc ของคุณ มันไม่เปิด terminal มันไม่เริ่ม login shell และมันไม่แจ้งให้คุณทราบเมื่อคำสั่งล้มเหลว ทุกสาเหตุที่ระบุด้านล่างนี้ล้วนเป็นผลมาจากข้อเท็จจริง 4 ประการนี้

ให้ตรวจสอบตามลำดับ และเริ่มจากคำถามที่เป็นพื้นฐานที่สุด: cron ได้เริ่มทำงานจริงหรือไม่? "cron ไม่เคยเริ่มงาน" และ "งานเริ่มแล้วตาย" เป็นปัญหาที่แตกต่างกันโดยสิ้นเชิงและไม่มีส่วนใดที่เหมือนกัน ดังนั้นให้ตอบคำถามนี้ก่อนเป็นอันดับแรก

cron ทำงานหรือไม่?

daemon มีชื่อ unit ที่แตกต่างกันไปตามตระกูลของ distribution ให้ตรวจสอบทั้งสองชื่อ แล้วจึงอ่าน log

systemctl status cron
systemctl status crond
journalctl -u cron --since "2 hours ago"
journalctl -u crond --since "2 hours ago"

Debian และ Ubuntu ใช้ชื่อ unit ว่า cron ส่วน Fedora, Rocky และ Alma ใช้ชื่อ crond บนเครื่องหนึ่งเครื่องจะมีเพียงชื่อเดียวเท่านั้น ดังนั้นการที่คำสั่งหนึ่งรายงานว่าไม่พบ unit จึงเป็นเรื่องปกติและไม่ใช่ความผิดพลาด

ให้อ่านรายการที่ระบบของคุณบันทึกไว้เอง อย่าพยายามค้นหาบรรทัดที่คัดลอกมาจากคู่มือ เนื่องจากถ้อยคำใน log จะแตกต่างกันไปตามการใช้งาน cron และการตั้งค่า logging ของแต่ละระบบ คุณต้องตรวจสอบเพียงสองสิ่งเท่านั้น คือ มีรายการบันทึกในช่วงนาทีที่คุณกำหนดไว้หรือไม่ และรายการนั้นระบุคำสั่งของคุณหรือไม่ หากมีรายการที่ระบุคำสั่งของคุณ แสดงว่า cron ทำงานตามหน้าที่แล้ว และความผิดพลาดเกิดขึ้นภายในตัวคำสั่งนั้นเอง หากไม่มีรายการใดเลย แสดงว่า cron ไม่ได้รับตารางเวลาของคุณ ซึ่งเป็นสาเหตุที่ 3 ด้านล่าง

image บางตัวส่งข้อความของ cron ผ่าน rsyslog ไปยังไฟล์แทนที่จะเป็น journal ให้ตรวจสอบใน /var/log เพื่อหาไฟล์ที่ตั้งชื่อตาม cron หรือ syslog จากนั้นให้อ่านส่วนท้ายของไฟล์ดังกล่าว

ls -l /var/log
sudo tail -n 50 /var/log/syslog

หากไม่พบทั้ง unit และ log อาจเป็นไปได้ว่าไม่ได้ติดตั้ง cron ไว้ image บน cloud และ container ขนาดเล็กมักจะตัดส่วนนี้ออก

dpkg -l cron
rpm -q cronie
sudo apt install cron
sudo dnf install cronie
sudo systemctl enable --now cron

สาเหตุที่ 1: cron ไม่มี PATH ของคุณ

เชลล์แบบโต้ตอบ (interactive shell) ของคุณสร้าง PATH จาก /etc/profile, ~/.profile, ~/.bashrc และทุกไฟล์ที่ไฟล์เหล่านั้นเรียกใช้ แต่ cron ไม่ได้เรียกใช้ไฟล์เหล่านี้เลย cron เริ่มต้นคำสั่งด้วยสภาพแวดล้อมที่จำกัดของตัวเอง ทำให้ไม่พบโปรแกรมที่อยู่นอกไดเรกทอรีมาตรฐานของระบบ สิ่งที่อยู่ใน /usr/local/bin, /opt, ตัวจัดการเวอร์ชันภาษา, Python virtual environment หรือ Go workspace มักจะประสบปัญหานี้ งานจะล้มเหลวตั้งแต่บรรทัดแรก และเชลล์จะเขียนข้อความแจ้งเตือนว่า "not found" ซึ่งรูปแบบข้อความจะขึ้นอยู่กับเชลล์ที่เรียกใช้งาน

ให้ค้นหาพาธจริง (absolute path) ของทุกคำสั่งที่งานของคุณใช้งาน

command -v docker
command -v node
readlink -f "$(command -v node)"

จากนั้นให้เขียนพาธเต็มเหล่านั้นลงในงาน หรือกำหนดค่า PATH ไว้ที่ด้านบนสุดของ crontab

PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
0 3 * * * /usr/local/bin/mytool run

คัดลอกรายการดังกล่าวจากเครื่องของคุณด้วยคำสั่ง echo "$PATH" และตัดสิ่งที่อยู่ในเซสชันแบบโต้ตอบออกไป มีกฎข้อหนึ่งที่สำคัญคือ cron จะไม่ขยายตัวแปรในบรรทัดการกำหนดค่าเหล่านี้ PATH=$PATH:/usr/local/bin จะเก็บข้อความตัวอักษร $PATH:/usr/local/bin ไว้ตรงๆ ทำให้งานของคุณมีพาธการค้นหาที่ไม่มีไดเรกทอรีที่ใช้งานได้จริง ดังนั้นคุณต้องเขียนรายการพาธทั้งหมดออกมาให้ครบถ้วน

ตัวจัดการเวอร์ชันต้องการมากกว่าแค่พาธ nvm, pyenv, rbenv และ asdf จะติดตั้งฟังก์ชันเชลล์หรือไดเรกทอรี shims จาก .bashrc ของคุณ ซึ่ง cron job จะไม่อ่านไฟล์นั้น ให้เรียกใช้ไฟล์ไบนารีที่ระบุเวอร์ชันด้วยพาธเต็ม หรือเรียกใช้ (source) สคริปต์เริ่มต้นของตัวจัดการเวอร์ชันเป็นบรรทัดแรกของสคริปต์ของคุณ

สาเหตุที่ 2: เครื่องหมายเปอร์เซ็นต์ทำให้คำสั่งของคุณสิ้นสุดลง

ในช่องคำสั่งของ crontab นั้น % ไม่ใช่ตัวอักษรทั่วไป เครื่องหมาย % ตัวแรกที่ไม่ได้ถูก escape จะถือเป็นการสิ้นสุดคำสั่ง ข้อมูลทั้งหมดหลังจากนั้นจะถูกส่งไปยังคำสั่งในรูปแบบ standard input และเครื่องหมาย % แต่ละตัวที่ตามมาจะกลายเป็นบรรทัดใหม่ นี่เป็นฟีเจอร์จริงของ cron สำหรับการป้อนข้อมูลสั้นๆ ให้กับโปรแกรม และยังเป็นเหตุผลว่าทำไมการตั้งชื่อไฟล์ที่มีวันที่จึงเป็นรายการ crontab ที่มักจะใช้งานไม่ได้

หากเขียน 0 3 * * * /usr/bin/tar -czf /srv/backups/site-$(date +%F).tar.gz /srv/site โปรแกรม tar จะไม่ได้รับวันที่ที่จัดรูปแบบไว้ เพราะ cron ตัดบรรทัดที่เครื่องหมาย % ตัวแรก ทำให้ shell ได้รับคำสั่ง substitution ที่ไม่สมบูรณ์ และส่วนที่เหลือของบรรทัดจะถูกส่งไปเป็น standard input ให้คุณใช้ backslash นำหน้าเครื่องหมายเปอร์เซ็นต์ทุกตัวเพื่อทำการ escape

0 3 * * * /usr/bin/tar -czf /srv/backups/site-$(date +\%F).tar.gz /srv/site

มีสองเลเยอร์ที่อ่านบรรทัดนั้นตามลำดับ \% เป็นกฎของ cron ซึ่ง cron จะนำไปใช้ก่อนที่จะเริ่มทำงานใดๆ ส่วน $(date +\%F) คือ การแทนที่คำสั่ง (command substitution) ซึ่ง shell ที่ cron เรียกขึ้นมาจะนำไปใช้ในภายหลัง การเข้าใจว่าเลเยอร์ใดเป็นผู้จัดการตัวอักษรตัวไหนคือหัวใจสำคัญของเรื่องนี้

วิธีปฏิบัติที่ปลอดภัยกว่าคือการเก็บตรรกะการทำงานไว้นอก crontab ทั้งหมด ให้เขียนไว้ในสคริปต์แทน ซึ่งในสคริปต์นั้นเครื่องหมายเปอร์เซ็นต์จะไม่มีความหมายพิเศษใดๆ

#!/bin/bash
set -euo pipefail
stamp="$(date +%F)"
tar -czf "/srv/backups/site-${stamp}.tar.gz" /srv/site

บรรทัดใน crontab จะเหลือเพียงพาธและการ redirect เท่านั้น crontab ที่คุณสามารถอ่านเข้าใจได้ทันทีที่มอง คือ crontab ที่คุณสามารถแก้ไขจุดบกพร่องได้ง่าย

สาเหตุที่ 3: คุณแก้ไข crontab ไฟล์ไหน?

ไม่มี crontab เพียงไฟล์เดียวในระบบ แต่มีหลายไฟล์ซึ่งมีเจ้าของและจำนวนฟิลด์ที่แตกต่างกัน หากเขียนงานลงผิดไฟล์ งานนั้นจะไม่ทำงาน

  • crontab -e ใช้แก้ไข crontab ของผู้ใช้ที่รันคำสั่งนั้น ส่วน sudo crontab -e ใช้แก้ไขของ root บ่อยครั้งที่ผู้ดูแลระบบสองคนตรวจสอบเครื่องเดียวกันแต่กลับอ่านไฟล์คนละไฟล์
  • sudo crontab -l -u deploy ใช้แสดงรายการ crontab ของผู้ใช้อื่น ซึ่งเป็นวิธีตรวจสอบว่ามีการติดตั้งงานอะไรไว้จริงสำหรับบัญชีที่ควรจะรันงานนั้น
  • /etc/crontab และทุกไฟล์ใน /etc/cron.d จะมีฟิลด์เพิ่มขึ้นมาหนึ่งฟิลด์ระหว่างกำหนดการและคำสั่ง นั่นคือชื่อผู้ใช้ที่จะให้รันงาน หากคุณนำบรรทัด crontab แบบ 5 ฟิลด์ไปวางใน /etc/cron.d คำแรกของคำสั่งจะถูกอ่านเป็นชื่อผู้ใช้แทน
  • ไฟล์ใน /etc/cron.d ต้องตั้งชื่อด้วยตัวอักษร ตัวเลข ขีดล่าง และขีดกลางเท่านั้น ไฟล์ที่ชื่อ backup.sh หรือ site.conf จะถูกข้ามไปเพียงเพราะชื่อไฟล์ไม่ถูกต้อง ให้เปลี่ยนชื่อเป็น backup แล้วตรวจสอบ log อีกครั้ง
  • ไฟล์ใน /etc/cron.d ควรมี root เป็นเจ้าของ และต้องไม่เปิดสิทธิ์ให้ group หรือ others เขียนได้ ls -l /etc/cron.d จะแสดงข้อมูลทั้งสองอย่างนี้ให้คุณเห็น
  • สคริปต์ที่วางใน /etc/cron.daily และโฟลเดอร์ย่อยต้องปฏิบัติตามกฎการตั้งชื่อเดียวกันและต้องมีสิทธิ์ execute หากไม่มีสิทธิ์ execute ระบบจะข้ามไปโดยไม่แจ้งเตือน
  • /etc/cron.allow และ /etc/cron.deny เป็นตัวกำหนดว่าใครสามารถติดตั้ง crontab ได้บ้าง หากไฟล์เหล่านี้มีอยู่ในเครื่องของคุณ ให้อ่านเนื้อหาก่อนที่จะสรุปว่าผู้ใช้ของคุณได้รับอนุญาตให้ใช้งาน

ให้ติดตั้ง user crontab ด้วยคำสั่ง crontab แทนการแก้ไขไฟล์ใน spool โดยตรง เพราะ crontab จะตรวจสอบไวยากรณ์ไฟล์ก่อนติดตั้ง เมื่อบันทึกแล้วให้อ่านสิ่งที่คำสั่งแสดงผลกลับมา หากคำสั่งปฏิเสธไฟล์ เวอร์ชันเดิมจะยังคงทำงานอยู่และการเปลี่ยนแปลงของคุณจะไม่มีผล ซึ่งดูเหมือนกับว่า cron เพิกเฉยต่อคุณ

เจ้าของไฟล์ยังเป็นตัวกำหนดสิทธิ์การเข้าถึง งานที่อยู่ใน crontab ของ root จะสร้างไฟล์ที่เป็นของ root ซึ่งแอปพลิเคชันที่ต้องอ่านไฟล์เหล่านั้นอาจไม่มีสิทธิ์เขียน งานที่อยู่ใน crontab ของผู้ใช้ทั่วไปจะไม่สามารถอ่านไดเรกทอรีที่จำกัดสิทธิ์เฉพาะ root ได้ ควรเลือกเจ้าของงานให้เหมาะสมกับหน้าที่: การบำรุงรักษาแอปพลิเคชันควรทำโดยบัญชีผู้ใช้ของแอปพลิเคชันนั้น ซึ่งเป็นเหตุผลเบื้องหลังของ การเปลี่ยน wp-cron ของ WordPress มาใช้ system cron job โหมดของไฟล์ที่งานของคุณสร้างขึ้นจะมาจาก umask ที่ได้รับสืบทอดมา ซึ่งเป็นค่าที่อาจไม่เหมือนกับค่าใน shell ของคุณ ดังนั้น วิธีที่ umask กำหนดสิทธิ์ไฟล์ จึงเป็นเรื่องที่ควรศึกษาหากผลลัพธ์ของงานที่ได้ไม่สามารถอ่านได้

สาเหตุที่ 4: ผลลัพธ์ถูกส่งไปยังเมลที่ไม่มีใครอ่าน

cron จะรวบรวมทุกอย่างที่งานเขียนลงใน standard output และ standard error หากงานมีการเขียนข้อความใดๆ ออกมา cron จะส่งข้อความนั้นไปยังระบบเมลภายใน โดยระบุผู้รับเป็นเจ้าของ crontab หรือตามที่ MAILTO กำหนดไว้ บน VPS ที่มีการติดตั้งแบบจำกัด มักจะไม่มี MTA (mail transfer agent) ติดตั้งอยู่ ดังนั้นจึงไม่มีการส่งเมลเกิดขึ้น ข้อผิดพลาดของคุณจึงปรากฏขึ้นเพียงชั่วครู่แล้วหายไป นี่คือเหตุผลทั้งหมดที่ทำให้งานที่ล้มเหลวดูเหมือนเงียบเชียบ

ให้ส่งผลลัพธ์ไปยังไฟล์ที่คุณควบคุมแทน

0 3 * * * /usr/local/sbin/backup-site.sh >> /var/log/backup-site.log 2>&1

>> จะทำการต่อท้าย (append) standard output ลงในไฟล์ 2>&1 จะชี้ standard error ไปยังตำแหน่งเดียวกับที่ standard output ชี้อยู่ ณ ขณะนั้น ดังนั้นจึงต้องเขียนไว้หลังการ redirect เสมอ หากเขียนสลับกันเป็น 2>&1 >> file ตัว standard error จะยังคงส่งไปยังปลายทางเดิม และข้อผิดพลาดที่คุณกำลังตามหาจะเป็นส่วนที่ไม่เคยถูกบันทึกลงในไฟล์เลย

journal เป็นอีกหนึ่งปลายทางที่ดี logger จะเขียนข้อมูลลงใน syslog ภายใต้ tag ที่คุณเลือก

0 3 * * * /usr/local/sbin/backup-site.sh 2>&1 | logger -t backup-site

อ่านข้อมูลกลับมาด้วย journalctl -t backup-site วิธีนี้ช่วยให้ผลลัพธ์ของงานอยู่คู่กับรายการ cron ทำให้ติดตามลำดับเวลาได้ง่าย หากคุณต้องการบันทึกด้วยว่าใครเป็นผู้รันคำสั่งใดบนเซิร์ฟเวอร์ นั่นเป็นระบบแยกต่างหาก ซึ่ง การตรวจสอบคำสั่งของผู้ใช้บนเซิร์ฟเวอร์ของคุณ ได้อธิบายไว้แล้ว

MAILTO="" ที่ส่วนบนของ crontab จะปิดการส่งเมลสำหรับงานทั้งหมดที่อยู่ด้านล่าง การตั้งค่า MAILTO ให้เป็นที่อยู่อีเมลจริงจะช่วยได้ก็ต่อเมื่อมี MTA ที่ใช้งานได้จริงเท่านั้น ดังนั้นควรตรวจสอบให้แน่ใจว่าเมลสามารถส่งออกจากเซิร์ฟเวอร์ได้ก่อนที่จะพึ่งพาวิธีนี้

กฎข้อหนึ่งระหว่างการดีบั๊ก: ห้ามต่อท้ายด้วย > /dev/null 2>&1 โดยเด็ดขาด นี่เป็นบรรทัดที่นิยมใช้มากที่สุดในทุก crontab แต่มันจะทำลายหลักฐานเดียวที่คุณมีทิ้งไป ให้ใส่กลับเข้าไปภายหลังเมื่อคุณต้องการ หลังจากที่งานทำงานได้ปกติแล้วเท่านั้น

สาเหตุที่ 5: สคริปต์คาดหวังสภาพแวดล้อมที่ cron ไม่ได้จัดเตรียมให้

เมื่อพบคำสั่งและบันทึกผลลัพธ์ได้แล้ว สิ่งที่เหลืออยู่คือสภาพแวดล้อมอื่นๆ ทั้งหมดที่เซสชันของคุณได้รับโดยอัตโนมัติ

  • Shell อาจไม่ใช่ bash ให้ตรวจสอบด้วย ls -l /bin/sh บน Debian และ Ubuntu ค่านี้จะชี้ไปที่ dash ดังนั้นการใช้การทดสอบด้วยวงเล็บคู่ (double bracket), อาร์เรย์ และ source จะล้มเหลวด้วยข้อผิดพลาดทางไวยากรณ์ ให้ระบุบรรทัด #!/bin/bash ไว้ที่ต้นสคริปต์แล้วเรียกใช้งาน หรือตั้งค่า SHELL ไว้ที่ส่วนบนสุดของ crontab
  • ไดเรกทอรีทำงาน (working directory) ไม่ใช่ไดเรกทอรีที่คุณใช้งานอยู่ ให้ใช้ absolute path ในทุกที่ หรือใช้ cd ไปยังไดเรกทอรีที่ต้องการในบรรทัดแรกของสคริปต์ การใช้ relative path เป็นสาเหตุที่พบบ่อยที่สุดที่ทำให้งาน "ทำงานได้เมื่อรันด้วยตัวเอง"
  • Locale ไม่ใช่ค่าเดียวกับเซสชันของคุณ สิ่งใดก็ตามที่จัดรูปแบบวันที่หรือตัวเลข หรือเรียงลำดับข้อความ อาจให้ผลลัพธ์ที่แตกต่างออกไปภายใต้ LANG ที่ต่างกัน หากขั้นตอนถัดไปต้องประมวลผลผลลัพธ์นั้น ให้ตั้งค่า locale ภายในสคริปต์แทนการคาดหวังค่าเริ่มต้น
  • ไม่มี TTY (terminal) คำสั่งที่ต้องการการยืนยัน เปิดโปรแกรมแก้ไขข้อความ หรือแสดงแถบความคืบหน้าอาจค้างหรือหยุดทำงาน ให้เพิ่ม flag สำหรับการทำงานแบบ non-interactive ที่เครื่องมือนั้นๆ รองรับ
  • ไม่มี SSH agent เนื่องจาก SSH_AUTH_SOCK ไม่อยู่ในสภาพแวดล้อมของ cron คำสั่งอย่าง ssh หรือ rsync ที่เคยทำงานได้เพราะโหลด agent ไว้แล้วจะล้มเหลวในการยืนยันตัวตน ให้สร้างกุญแจ (key) แยกต่างหากสำหรับงานนั้นๆ โดยให้ผู้ใช้ที่เป็นเจ้าของงานเป็นเจ้าของกุญแจด้วย
  • ไม่มี user session bus ดังนั้น systemctl --user จาก cron job จะล้มเหลวหากไม่ได้ตั้งค่า XDG_RUNTIME_DIR การใช้ system unit เป็นวิธีแก้ปัญหาที่ดีกว่า

บน Fedora, Rocky และ Alma ยังมีผู้ต้องสงสัยอีกหนึ่งประการ คือ SELinux จะจำกัดการทำงานของ cron job ดังนั้นงานที่เข้าถึง path ที่มี label ไม่คาดคิดจะถูกปฏิเสธแม้ว่าสิทธิ์ของไฟล์จะดูถูกต้องก็ตาม ให้ตรวจสอบการปฏิเสธการเข้าถึงด้วย sudo ausearch -m avc -ts recent และอ่าน พื้นฐาน SELinux สำหรับเซิร์ฟเวอร์ ก่อนที่คุณจะปิดการทำงานใดๆ

การตรวจสอบสภาพแวดล้อมของ cron ภายในหนึ่งนาที

หยุดคาดเดาสภาพแวดล้อมของ cron และตรวจสอบด้วยตัวเอง ให้เขียนสคริปต์เพื่อดัมพ์ข้อมูลทั้งหมด ตั้งเวลาให้ทำงานทุกนาที รอสักครู่ แล้วจึงอ่านไฟล์ที่ได้

cat > /home/deploy/cron-probe.sh <<'EOF'
#!/bin/bash
echo "=== probe ==="
date -Is
pwd
id
echo "SHELL=$SHELL"
echo "LANG=$LANG"
command -v node || echo "node is not on this PATH"
env | sort
EOF
chmod +x /home/deploy/cron-probe.sh

เพิ่มบรรทัดคำสั่งลงใน crontab ของผู้ใช้ที่รันงานจริง โดยระบุพาธแบบสมบูรณ์ (absolute path) ทั้งสองฝั่ง

* * * * * /home/deploy/cron-probe.sh >> /home/deploy/cron-probe.log 2>&1

รอหนึ่งนาที จากนั้นอ่านไฟล์ /home/deploy/cron-probe.log และเปรียบเทียบกับผลลัพธ์ของคำสั่งเดียวกันเมื่อรันในเชลล์ของคุณเอง บรรทัด PATH, ไดเรกทอรีที่ทำงานอยู่ (working directory) และ locale มักเป็นสาเหตุที่อธิบายความล้มเหลวได้ด้วยตัวมันเอง โปรดสังเกตรายละเอียดสองประการของการตั้งค่านี้: เครื่องหมายเปอร์เซ็นต์ที่อยู่ภายในสคริปต์จะไม่ถูกจำกัดโดยกฎของ cron และพาธของไฟล์ log ต้องเป็นตำแหน่งที่ผู้ใช้ของงานนั้นสามารถเขียนไฟล์ได้

ลบบรรทัดใน crontab นั้นทิ้งทันทีที่คุณได้คำตอบ งานที่รันทุกนาทีและเขียนข้อมูลต่อท้ายไฟล์จะทำให้ดิสก์ขนาดเล็กเต็มได้อย่างรวดเร็วและเงียบเชียบ

กำหนดการที่คุณตั้งไว้ถูกต้องหรือไม่?

บรรทัดใน crontab ของผู้ใช้ประกอบด้วย 5 ฟิลด์ ได้แก่ นาที, ชั่วโมง, วันที่ในเดือน, เดือน และวันในสัปดาห์ มีสองฟิลด์ที่ทำงานสัมพันธ์กันจนมักทำให้ผู้ใช้ประหลาดใจ

เมื่อมีการจำกัดทั้งฟิลด์วันที่ในเดือนและวันในสัปดาห์ (หมายถึงทั้งสองฟิลด์ไม่ใช่ *) cron จะรันงานเมื่อฟิลด์ใดฟิลด์หนึ่งตรงกับเงื่อนไข 0 0 13 * 5 ไม่ได้หมายถึง "วันศุกร์ที่ 13" แต่มันจะรันงานตอนเที่ยงคืนของวันที่ 13 ของทุกเดือน และรันตอนเที่ยงคืนของทุกวันศุกร์ หากต้องการระบุวันใดวันหนึ่งโดยเฉพาะ ให้ปล่อยฟิลด์ใดฟิลด์หนึ่งเป็น * แล้วเขียนเงื่อนไขตรวจสอบอีกฟิลด์หนึ่งไว้ภายในสคริปต์แทน

cron ใช้เขตเวลาของระบบ VPS หลายแห่งตั้งค่าเริ่มต้นเป็น UTC (Coordinated Universal Time) ดังนั้นงานที่คุณตั้งไว้ตอน 03:00 จะรันที่เวลา 03:00 UTC ซึ่งอาจตรงกับช่วงบ่ายของคุณ timedatectl จะแสดงเขตเวลาที่เซิร์ฟเวอร์ของคุณใช้งานจริง ควรตรวจสอบค่านี้แทนการคาดเดาว่าตรงกับเวลาบนแล็ปท็อปของคุณ

ยังมีกับดักเรื่องกำหนดการอีกสองประการที่ควรทราบ @reboot จะทำงานเมื่อ cron เริ่มต้นขึ้น ซึ่งไม่ใช่เวลาเดียวกับที่เครือข่ายพร้อมใช้งาน ดังนั้นงานที่ต้องพึ่งพา DNS หรือโฮสต์ระยะไกลอาจล้มเหลวในช่วงบูต แต่กลับทำงานได้สำเร็จเมื่อรันด้วยตนเองในภายหลัง นอกจากนี้ ไม่มีกลไกใดหยุดงานที่ทำงานช้าไม่ให้เริ่มทำงานซ้ำในขณะที่สำเนาเดิมยังคงทำงานอยู่ คุณควรใช้ lock ครอบงานนั้นไว้

*/5 * * * * /usr/bin/flock -n /tmp/backup-site.lock /usr/local/sbin/backup-site.sh >> /var/log/backup-site.log 2>&1

flock -n จะยุติการทำงานทันทีหากพบว่ามีการถือ lock อยู่แล้ว เพื่อป้องกันไม่ให้งานที่ซ้อนทับกันทำงานพร้อมกันจนเกิดปัญหา

เมื่อ systemd timer เป็นเครื่องมือที่เหมาะสมกว่า

cron ทำงานได้ดีในเรื่องเดียวคือการรันคำสั่งตามเวลาที่กำหนด แต่ในด้านอื่นถือว่ามีข้อจำกัดมาก การใช้ timer ช่วยให้คุณดู log ผ่าน journal ได้โดยไม่ต้องทำ redirect, ตรวจสอบ exit status ย้อนหลังได้, กำหนดลำดับการทำงานเทียบกับ network-online.target ได้ และยังใส่การหน่วงเวลาแบบสุ่มเพื่อป้องกันไม่ให้เซิร์ฟเวอร์นับร้อยเครื่องเริ่มทำงานพร้อมกันในวินาทีเดียว เมื่อใดที่งานของคุณต้องการคุณสมบัติเหล่านี้ การใช้ systemd service และ timer บน VPS จะช่วยลดภาระงานได้มากกว่าการจัดการบรรทัดคำสั่งใน crontab การเขียนส่วนที่เป็น service จะทำให้คุณต้องพิจารณาประเด็นหนึ่งที่ cron ไม่เคยถาม นั่นคือ unit จะทราบได้อย่างไรว่างานได้เริ่มขึ้นจริงแล้ว ดังนั้นโปรดอ่าน ความหมายของ Type= สำหรับ unit แบบ simple, forking และ notify ก่อน เพราะสคริปต์ที่ทำ daemonize ตัวเองภายใต้ type เริ่มต้นจะทำให้ unit แสดงสถานะว่า active ทั้งที่ไม่มีกระบวนการทำงานอยู่จริง พฤติกรรมการลองใหม่ (retry) ก็เป็นส่วนสำคัญเช่นกัน เพราะ นโยบายการ restart ของ systemd จะเป็นตัวกำหนดว่าจะเกิดอะไรขึ้นหลังจากเกิดความล้มเหลว ซึ่ง cron ไม่มีคำตอบสำหรับเรื่องนี้เลย

ให้คง cron ไว้สำหรับงานขนาดเล็ก ส่วนงานใดที่มี dependency หรือต้องการนโยบายการลองใหม่ ให้ย้ายไปใช้ timer ทั้งสองระบบสามารถทำงานบนเซิร์ฟเวอร์เดียวกันได้ ดังนั้นคุณไม่จำเป็นต้องย้ายงานทั้งหมดให้เสร็จสิ้นในคราวเดียว

FAQ

ทำไม cron job ของฉันทำงานได้เมื่อรันด้วยตนเอง แต่ล้มเหลวเมื่อรันผ่าน cron?

เพราะ shell ของคุณกับสภาพแวดล้อมของ cron นั้นแตกต่างกัน shell ที่คุณใช้ล็อกอินจะอ่านไฟล์ /etc/profile และ ~/.bashrc ซึ่งเป็นตัวกำหนด PATH, locale และตัวแปร agent ต่างๆ แต่ cron จะเริ่มรันคำสั่งโดยไม่มีการตั้งค่าเหล่านี้ อีกทั้งยังรันจากไดเรกทอรีทำงานที่ต่างออกไป และบางครั้งก็ใช้ shell ที่ต่างกัน ให้ใช้ absolute path สำหรับทุกคำสั่ง ตั้งค่าสิ่งที่จำเป็นไว้ที่ส่วนบนของ crontab หรือภายในสคริปต์ และกำหนดงานตรวจสอบที่รันทุกหนึ่งนาทีเพื่อส่งผลลัพธ์ของ env | sort, pwd และ id ลงในไฟล์ log เพื่อให้คุณสามารถอ่านสภาพแวดล้อมจริงของ cron ได้แทนที่จะใช้วิธีคาดเดา

ฉันจะตรวจสอบได้อย่างไรว่า cron ได้รันงานของฉันจริงหรือไม่?

ให้อ่าน log ของ daemon ให้ใช้ journalctl -u cron บน Debian และ Ubuntu หรือใช้ journalctl -u crond บน Fedora, Rocky และ Alma ในบาง image อาจส่งข้อความผ่าน rsyslog ไปยังไฟล์ภายใต้ /var/log แทน ให้มองหารายการที่ตรงกับนาทีที่คุณกำหนดไว้ในตารางเวลาและตรวจสอบว่ามีการระบุคำสั่งของคุณ หากไม่มีรายการแสดงว่า cron ไม่ได้รับตารางเวลานั้น ให้ตรวจสอบว่าคุณแก้ไข crontab ถูกไฟล์หรือไม่ หากมีรายการแต่ไม่มีผลลัพธ์ แสดงว่าคำสั่งเริ่มทำงานแล้วหยุดไป ให้ดักจับ output ด้วยการทำ redirect

ทำไม date +%Y ถึงทำงานผิดพลาดเมื่ออยู่ใน crontab?

cron ถือว่า % เป็นอักขระพิเศษในช่องคำสั่ง โดย % ตัวแรกที่ไม่ได้ถูก escape จะถือเป็นการสิ้นสุดคำสั่ง และทุกอย่างหลังจากนั้นจะถูกส่งไปยังคำสั่งนั้นในฐานะ standard input และ % แต่ละตัวที่ตามมาจะกลายเป็นบรรทัดใหม่ ดังนั้นชื่อไฟล์ที่จัดรูปแบบด้วยวันที่จึงไม่ถูกส่งไปยังโปรแกรมที่คุณต้องการ ให้ทำการ escape เครื่องหมายเปอร์เซ็นต์แต่ละตัวด้วย \% หรือย้ายคำสั่งไปไว้ในสคริปต์แล้วเรียกใช้สคริปต์จาก cron แทน เนื่องจากภายในสคริปต์เครื่องหมายเปอร์เซ็นต์ไม่มีความหมายพิเศษ

output ของ cron job ของฉันหายไปไหน?

output จะถูกส่งไปยังระบบเมลภายในเครื่อง โดยจ่าหน้าถึงเจ้าของ crontab หรือตามที่ระบุไว้ใน MAILTO VPS ส่วนใหญ่ไม่ได้ติดตั้ง mail transfer agent ไว้ ข้อความจึงถูกทิ้งไปและทำให้งานดูเหมือนเงียบหายไป ให้ redirect output ไปยังไฟล์ด้วย >> /path/to/log 2>&1 โดยเรียงลำดับให้ standard error ตามหลัง standard output หรือส่งผ่าน logger -t myjob แล้วอ่านกลับด้วย journalctl -t myjob อย่าใช้ > /dev/null 2>&1 ในขณะที่คุณยังอยู่ในขั้นตอนการดีบั๊ก

ฉันควรใช้ cron หรือ systemd timer?

ให้ใช้ cron สำหรับคำสั่งง่ายๆ ที่รันตามเวลาที่กำหนด โดยเฉพาะงานที่คุณอาจต้องย้ายไปยังเครื่องที่ไม่ได้รัน systemd ให้ใช้ timer เมื่อคุณต้องการให้ output ไปอยู่ใน journal โดยไม่ต้องทำ redirect, ต้องการสถานะ exit status ที่ตรวจสอบได้, ต้องการให้รันหลังจาก network พร้อมใช้งาน, ต้องการหน่วงเวลาเริ่มรันแบบสุ่ม หรือต้องการนโยบายการลองใหม่เมื่อเกิดความล้มเหลว ทั้งสองอย่างสามารถรันบนเซิร์ฟเวอร์เดียวกันได้ คุณจึงสามารถทยอยย้ายงานทีละรายการได้ตามความเหมาะสม