คู่มือ

Crontab: ตัวอย่างการตั้งเวลางานใน Linux ไวยากรณ์และข้อผิดพลาดที่พบบ่อย

อัปเดต: · Skillaria.Top

Crontab คือตารางกำหนดเวลาที่ดีมอน cron ใช้รันคำสั่ง แต่ละบรรทัดประกอบด้วยห้าฟิลด์เวลา (นาที ชั่วโมง วันของเดือน เดือน วันของสัปดาห์) และคำสั่ง เช่น */5 * * * * /opt/backup.sh รันสคริปต์ทุก 5 นาที แก้ไขตารางด้วยคำสั่ง crontab -e ปัญหาส่วนใหญ่เกิดจากสภาพแวดล้อมของ cron ที่จำกัด จึงควรระบุพาธเต็มและส่งเอาต์พุตไปยังล็อก

Cron คือตัวตั้งเวลางานมาตรฐานของ Linux: ดีมอน cron อ่านตารางเวลา (crontab) ทุกนาทีและรันคำสั่งที่ถึงเวลา งานแบบนี้ใช้สำรองข้อมูลตอนกลางคืน ล้างไฟล์ชั่วคราว ต่ออายุใบรับรอง และส่งรายงาน ด้านล่างเป็นไวยากรณ์ ตัวอย่าง crontab สำหรับกรณีทั่วไป และการไล่ดูข้อผิดพลาดที่ทำให้งานหยุดทำงานโดยไม่มีสัญญาณใดๆ

วิธีเปิดและแก้ไข crontab

ผู้ใช้แต่ละคนมีตารางของตัวเอง จัดการผ่านคำสั่ง crontab ไม่ใช่แก้ไฟล์ใน /var/spool/cron โดยตรง:

คำสั่งทำอะไร
crontab -eเปิดตารางของตัวเองในเอดิเตอร์ (ครั้งแรกจะให้เลือกเอดิเตอร์)
crontab -lแสดงตารางปัจจุบัน
crontab -rลบตารางทั้งหมดโดยไม่ถามยืนยัน
crontab -i -rลบพร้อมถามยืนยัน (Debian/Ubuntu, RHEL)
sudo crontab -u alice -eแก้ไขตารางของผู้ใช้อื่น
crontab ไฟล์แทนที่ตารางด้วยเนื้อหาของไฟล์

หลังบันทึก crontab -e จะตรวจไวยากรณ์และแสดง crontab: installing new crontab หากมีข้อผิดพลาดในบรรทัด คุณจะเห็น errors in crontab file, can't install และได้รับตัวเลือกให้แก้ไขใหม่ ไม่ต้องรีสตาร์ตดีมอน การเปลี่ยนแปลงจะถูกนำไปใช้อัตโนมัติ

ระวัง -r: ตัวอักษร e และ r อยู่ติดกันบนคีย์บอร์ด และไม่มีสำเนาสำรองของตาราง ควรสร้างนิสัยเก็บสำเนาไว้:

crontab -l > ~/crontab.bak
# กู้คืน
crontab ~/crontab.bak

ไวยากรณ์ crontab: ห้าฟิลด์เวลา

แต่ละบรรทัดงานมีรูปแบบดังนี้:

# ┌───────── นาที (0–59)
# │ ┌─────── ชั่วโมง (0–23)
# │ │ ┌───── วันของเดือน (1–31)
# │ │ │ ┌─── เดือน (1–12 หรือ jan–dec)
# │ │ │ │ ┌─ วันของสัปดาห์ (0–7 หรือ sun–sat; 0 และ 7 คือวันอาทิตย์)
# │ │ │ │ │
  * * * * * คำสั่ง

ในแต่ละฟิลด์ใช้สัญลักษณ์พิเศษได้:

สัญลักษณ์ความหมายตัวอย่าง
*ค่าใดก็ได้* * * * * — ทุกนาที
,รายการ0 9,18 * * * — เวลา 9:00 และ 18:00
-ช่วง0 9-18 * * * — ทุกชั่วโมงตั้งแต่ 9 ถึง 18
/ขั้น*/15 * * * * — ทุก 15 นาที

ขั้นใช้ร่วมกับช่วงได้: 0 8-20/2 * * * — ทุกสองชั่วโมงระหว่าง 8 ถึง 20 (8, 10, …, 20) เวลาอ้างอิงตามเขตเวลาของระบบ ตรวจด้วย timedatectl

ตัวอย่าง crontab สำหรับทุกวัน

ตารางเวลาบรรทัดใน crontab
ทุกนาที* * * * *
ทุก 5 นาที*/5 * * * *
ทุก 30 นาที*/30 * * * * หรือ 0,30 * * * *
ทุกชั่วโมงตรง0 * * * *
ทุก 6 ชั่วโมง0 */6 * * *
ทุกวันเวลา 3:000 3 * * *
ทุกวันเวลา 23:4545 23 * * *
วันทำงานเวลา 9:000 9 * * 1-5
วันหยุดสุดสัปดาห์เที่ยงวัน0 12 * * 6,0
วันจันทร์เวลา 8:3030 8 * * 1
วันที่ 1 ของทุกเดือนเวลา 00:000 0 1 * *
ทุกไตรมาส0 0 1 1,4,7,10 *
ปีละครั้ง วันที่ 1 มกราคม0 0 1 1 *
ทุก 10 นาทีในเวลาทำงาน*/10 9-18 * * 1-5

บรรทัดงานเต็มคือตารางเวลาบวกคำสั่ง:

# สำรองฐานข้อมูลทุกคืนเวลา 3:00
0 3 * * * /usr/local/bin/db-backup.sh >> /var/log/db-backup.log 2>&1

# ลบไฟล์ชั่วคราวที่เก่ากว่า 7 วันทุกวันอาทิตย์
0 4 * * 0 /usr/bin/find /tmp/app -type f -mtime +7 -delete

# ตรวจสอบเว็บไซต์ทุก 5 นาที
*/5 * * * * /usr/bin/curl -fsS https://example.com/health > /dev/null || /usr/local/bin/alert.sh

คำพิเศษ @reboot, @daily และอื่นๆ

แทนที่จะเขียนห้าฟิลด์ สามารถใช้ชื่อย่อได้:

ชื่อย่อเทียบเท่ากับ
@rebootรันหนึ่งครั้งเมื่อดีมอน cron เริ่มทำงาน (หลังบูตระบบ)
@hourly0 * * * *
@daily / @midnight0 0 * * *
@weekly0 0 * * 0
@monthly0 0 1 * *
@yearly / @annually0 0 1 1 *
@reboot /home/alice/bin/start-tunnel.sh >> /home/alice/tunnel.log 2>&1

สำหรับบริการที่ต้องทำงานตลอดเวลาและรีสตาร์ตเมื่อล่ม @reboot เป็นทางเลือกที่อ่อนแอ ควรเขียนยูนิต systemd ที่มี Restart=on-failure แทน

วิธีรันงานทุก N วินาที

หน่วยที่เล็กที่สุดของ cron คือหนึ่งนาที หากต้องการถี่กว่านั้นมีสองวิธี วิธีแรกคือหลายบรรทัดที่เลื่อนเวลาด้วย sleep:

* * * * * /opt/check.sh
* * * * * sleep 30; /opt/check.sh

วิธีที่สองถูกต้องกว่าคือตัวจับเวลาของ systemd ที่มี OnUnitActiveSec=30s หากงานต้องทำถี่กว่าหนึ่งครั้งต่อนาที มักเป็นงานของดีมอนหรือตัวจับเวลาไม่ใช่ของ cron

crontab ระบบ: /etc/crontab และ /etc/cron.d

นอกจากตารางของผู้ใช้แล้ว ยังมีตารางระบบ รูปแบบต่างกันเล็กน้อย: ระหว่างเวลาและคำสั่งจะมี ชื่อผู้ใช้ ที่ใช้รันคำสั่ง

# /etc/cron.d/certbot-renew
SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin

0 */12 * * * root certbot renew --quiet

ตำแหน่งของแต่ละส่วน:

  • /etc/crontab — ตารางระบบหลัก ใน Debian/Ubuntu ใช้รันไดเรกทอรีด้านล่าง
  • /etc/cron.d/ — ไฟล์งานแยกต่างหาก เหมาะกับการวางมาจากแพ็กเกจหรือผ่าน Ansible
  • /etc/cron.hourly/, cron.daily/, cron.weekly/, cron.monthly/ — วางสคริปต์ที่รันได้โดยไม่มีตารางเวลา ผู้รันคือ run-parts (หรือ anacron)

ข้อควรระวังของ run-parts: บน Debian และ Ubuntu มันข้ามไฟล์ที่มีจุดในชื่อ สคริปต์ backup.sh ในไดเรกทอรี /etc/cron.daily/ จะไม่ถูกรัน ให้ตั้งชื่อเป็น backup ตรวจสอบว่าจะรันอะไรบ้างได้ด้วย run-parts --test /etc/cron.daily

ไฟล์ใน /etc/cron.d/ ต้องเป็นของ root ไม่สามารถเขียนได้สำหรับกลุ่มและผู้ใช้อื่น และต้องลงท้ายด้วยการขึ้นบรรทัดใหม่ มิฉะนั้น cron จะเพิกเฉยต่อไฟล์นั้น

สภาพแวดล้อมของ cron: PATH, SHELL และตัวแปร

สาเหตุหลักของอาการ «ในเทอร์มินัลทำงาน แต่ใน cron ไม่ทำงาน» คือสภาพแวดล้อม cron รันคำสั่งผ่าน /bin/sh โดยไม่โหลด .bashrc หรือ .profile และมี PATH น้อยมาก (มักเป็น /usr/bin:/bin) ดังนั้น docker, node หรือ php ที่อยู่ใน /usr/local/bin อาจหาไม่พบ

ตั้งตัวแปรไว้ที่ต้นตารางได้:

SHELL=/bin/bash
PATH=/usr/local/bin:/usr/bin:/bin
MAILTO=admin@example.com
CRON_TZ=Europe/Moscow

0 6 * * * cd /srv/app && ./report.sh
  • MAILTO — ส่งเอาต์พุตของงานไปที่ใด MAILTO="" ปิดการส่งอีเมล
  • CRON_TZ — เขตเวลาสำหรับตารางเวลา (รองรับใน cronie บน RHEL/Fedora ส่วนเวอร์ชัน cron ของ Debian ไม่รองรับตัวแปรนี้)
  • ไม่มีการแทนค่าอย่าง $HOME/bin ในค่าตัวแปร ให้เขียนพาธเต็ม

วิธีที่เชื่อถือได้ที่สุดคือระบุพาธเต็มของโปรแกรมและไฟล์ทั้งหมดในงาน และใส่ shebang เป็นบรรทัดแรกของสคริปต์: #!/bin/bash

ดูล็อกของ cron ได้ที่ไหน

Cron บันทึกในจอร์นัลว่างานถูกเริ่มแล้ว แต่ไม่ได้บันทึกเอาต์พุตของงาน:

# Debian/Ubuntu
grep CRON /var/log/syslog
# ผ่าน systemd-journald
journalctl -u cron --since today      # Debian/Ubuntu
journalctl -u crond --since today     # RHEL, Fedora, Rocky
# RHEL ที่ไม่มี journald
tail -f /var/log/cron

บรรทัดการเริ่มงานจะหน้าตาประมาณนี้:

Oct 14 03:00:01 web1 CRON[21544]: (root) CMD (/usr/local/bin/db-backup.sh >> /var/log/db-backup.log 2>&1)

หากต้องการเห็นว่างานพิมพ์อะไรออกมาและล้มเหลวด้วยข้อผิดพลาดใด ให้ส่งทั้ง stdout และ stderr ไปยังไฟล์: >> /var/log/job.log 2>&1 หากไม่ทำเช่นนี้ เอาต์พุตจะถูกส่งเป็นอีเมลผ่านเมลท้องถิ่น และหากไม่มีเอเจนต์อีเมลติดตั้ง ก็จะหายไป (จะปรากฏ No MTA installed, discarding output ในจอร์นัล)

ทำไม cron ไม่รันงาน: ข้อผิดพลาดที่พบบ่อย

ไม่พบคำสั่ง

อาการ: ในล็อกของงานมี command not found สาเหตุคือ PATH ที่สั้นเกินไป วิธีแก้: ใช้พาธเต็มของโปรแกรม (which docker จะบอกพาธให้) หรือใส่บรรทัด PATH= ที่ต้นตาราง

เครื่องหมายเปอร์เซ็นต์ในคำสั่ง

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

# ผิด — คำสั่งจะถูกตัดที่ % ตัวแรก
0 2 * * * tar czf /backup/site-$(date +%F).tar.gz /var/www
# ถูก — ใส่เครื่องหมาย escape
0 2 * * * tar czf /backup/site-$(date +\%F).tar.gz /var/www

ง่ายกว่านั้นคือย้ายตรรกะไปไว้ในสคริปต์ ซึ่งไม่ต้อง escape %

สคริปต์ไม่สามารถรันได้หรือขึ้นบรรทัดผิดรูปแบบ

ข้อผิดพลาด Permission denied หมายความว่าไฟล์ไม่มีบิต x ให้รัน chmod +x script.sh หรือเรียกตรงๆ ด้วย /bin/bash /opt/script.sh หากสคริปต์ถูกเขียนบน Windows การขึ้นบรรทัดแบบ CRLF จะทำให้เกิดข้อผิดพลาดแปลกๆ เช่น /bin/bash^M: bad interpreter แก้ได้ด้วย sed -i 's/\r$//' script.sh หรือ dos2unix

พาธสัมพัทธ์และไดเรกทอรีทำงาน

งานเริ่มในโฮมไดเรกทอรีของผู้ใช้ หากสคริปต์อ่าน ./config.ini มันจะหาไฟล์ใน ~ ให้ใส่ cd /srv/app && ก่อนคำสั่ง หรือใช้พาธเต็มในสคริปต์

วันของเดือนและวันของสัปดาห์พร้อมกัน

หากกำหนดทั้งสองฟิลด์ cron จะรันเมื่อ ฟิลด์ใดฟิลด์หนึ่ง ตรงกัน ไม่ใช่ทั้งสองพร้อมกัน บรรทัด 0 9 13 * 5 จะรันวันที่ 13 ของทุกเดือน และ ทุกวันศุกร์ ไม่ใช่เฉพาะวันศุกร์ที่ 13 หากต้องการเงื่อนไขนี้ให้ตรวจในคำสั่ง:

0 9 13 * * [ "$(date +\%u)" = 5 ] && /opt/friday13.sh

งานทับซ้อนกัน

หากการสำรองข้อมูลใช้เวลานานกว่าช่วงเวลาระหว่างงาน การรันครั้งถัดไปจะเริ่มทับครั้งก่อน วิธีป้องกันคือการล็อกด้วย flock:

*/10 * * * * /usr/bin/flock -n /tmp/sync.lock /opt/sync.sh

ด้วยแฟลก -n อินสแตนซ์ที่สองจะจบทันทีหากอินสแตนซ์แรกยังทำงานอยู่

ดีมอน cron ไม่ทำงาน

ตรวจด้วย systemctl status cron (ใน RHEL คือ crond) ในอิมเมจขั้นต่ำ เช่นคอนเทนเนอร์ Docker อาจไม่มีดีมอน cron เลย งานใน crontab จึงไม่มีใครรัน

วิธีดีบักงาน cron

รันคำสั่งในสภาพแวดล้อมที่ใกล้เคียง cron มากที่สุด:

env -i SHELL=/bin/sh PATH=/usr/bin:/bin HOME=$HOME /bin/sh -c '/opt/backup.sh'

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

ใครใช้ cron ได้: cron.allow และ cron.deny

การเข้าถึงจำกัดด้วยไฟล์ /etc/cron.allow และ /etc/cron.deny หาก cron.allow มีอยู่ crontab จะใช้ได้เฉพาะผู้ใช้ที่ระบุในไฟล์ หากไม่มี แต่มี cron.deny ทุกคนจะใช้ได้ ยกเว้นผู้ที่ระบุไว้ ผู้ใช้ที่ถูกปฏิเสธจะเห็น You (bob) are not allowed to use this program เมื่อรัน crontab -e

สรุป

Crontab คือบรรทัดที่มีห้าฟิลด์เวลาและคำสั่ง: */5 * * * * คือทุก 5 นาที, 0 3 * * * คือทุกคืน, 0 9 * * 1-5 คือวันทำงาน แก้ไขตารางด้วย crontab -e ใช้พาธเต็ม escape % ส่งเอาต์พุตไปล็อก และป้องกันงานยาวด้วย flock ตัวอย่าง crontab เพียงพอสำหรับงานเซิร์ฟเวอร์ส่วนใหญ่ ส่วนการอัตโนมัติและการจัดตารางงานถูกอธิบายพร้อมตัวอย่างจริงในคอร์ส Linux

คำถามที่พบบ่อย

cron ต่างจาก anacron อย่างไร?

Cron รันงานตามเวลาที่ระบุเป๊ะๆ และจะข้ามหากเครื่องปิดอยู่ตอนนั้น ส่วน anacron รับประกันว่างานรายวัน รายสัปดาห์ และรายเดือนจะรันหลังเปิดเครื่อง จึงเหมาะกับแล็ปท็อปและเดสก์ท็อป

cron หรือตัวจับเวลาของ systemd ดีกว่ากัน?

Cron เรียบง่ายและมีแทบทุกที่ ตัวจับเวลาของ systemd ให้ล็อกใน journalctl มีการจัดการ dependency การจำกัดทรัพยากร และพารามิเตอร์ Persistent=true สำหรับการรันที่พลาดไป จึงถูกเลือกใช้มากขึ้นบนเซิร์ฟเวอร์รุ่นใหม่สำหรับงานสำคัญ

crontab ของผู้ใช้เก็บไว้ที่ไหนจริงๆ?

ใน Debian และ Ubuntu อยู่ที่ /var/spool/cron/crontabs/ชื่อผู้ใช้ ส่วนใน RHEL และ Fedora อยู่ที่ /var/spool/cron/ชื่อผู้ใช้ ไม่ควรแก้ไขไฟล์เหล่านี้โดยตรง ให้ใช้ crontab -e

จะปิดงานใน crontab ชั่วคราวได้อย่างไร?

ใส่เครื่องหมาย # ไว้หน้าบรรทัดผ่าน crontab -e หากต้องการปิดงานทั้งหมดของผู้ใช้ ให้บันทึกตารางเป็นไฟล์ด้วย crontab -l แล้วลบด้วย crontab -r

จะรันงานในวันสุดท้ายของเดือนได้อย่างไร?

ไม่มีฟิลด์สำหรับเรื่องนี้โดยตรง ให้รันงานในวันที่ 28–31 แล้วตรวจว่าพรุ่งนี้เป็นวันที่ 1 หรือไม่: 0 23 28-31 * * [ "$(date -d tomorrow +\%d)" = 01 ] && /opt/job.sh

เรียนหัวข้อนี้ผ่านวิดีโอ

หัวข้อนี้สอนอย่างละเอียดพร้อมฝึกปฏิบัติในคอร์ส “Linux: с нуля до уверенного администрирования”

ไปที่คอร์ส