Cron Job دستوری است که سرویس cron در زمان مشخص و بهصورت خودکار اجرا میکند. در این آموزش Cron Job در لینوکس، زمانبندی را با crontab -e تعریف میکنید، خروجی را در فایل لاگ ذخیره میکنید و با flock جلوی اجرای همزمان را میگیرید. بیشتر خطاهای کرون از تفاوت محیط اجرای آن با ترمینال شما میآید: PATH کوتاهتر، shell متفاوت و نبودن متغیرهای محیطی.
برای دنبال کردن مثالها باید با ترمینال، مسیر فایلها و دسترسیها آشنا باشید؛ اگر نیستید، ابتدا دستورات پایه لینوکس و مدیریت دسترسی در لینوکس را ببینید. دستورها روی Ubuntu و Debian (بسته cron) و روی RHEL، Rocky Linux و AlmaLinux (بسته cronie) قابل اجرا هستند و هر جا رفتار دو خانواده متفاوت است، جداگانه ذکر شده است. بخش آخر systemd timer را بهعنوان جایگزین معرفی میکند.
آموزش Cron Job در لینوکس: ساختار crontab
هر خط crontab پنج فیلد زمان دارد و بعد از آن دستوری که باید اجرا شود. خط زیر اسکریپت بکاپ را هر شب ساعت ۲:۳۰ اجرا میکند:
# minute hour day-of-month month day-of-week command
30 2 * * * /usr/local/bin/backup-db.sh| فیلد | مقادیر مجاز |
|---|---|
| دقیقه | 0 تا 59 |
| ساعت | 0 تا 23 |
| روز ماه | 1 تا 31 |
| ماه | 1 تا 12 یا نام کوتاه انگلیسی (jan، feb و …) |
| روز هفته | 0 تا 7 (0 و 7 هر دو یکشنبه، 6 شنبه) یا نام کوتاه (sun، mon و …) |
در هر فیلد، ستاره یعنی «همه مقادیر»، ویرگول فهرست میسازد (1,15)، خط تیره بازه تعریف میکند (9-17) و اسلش گام را مشخص میکند (*/5). چند نمونه پرکاربرد:
| عبارت | زمان اجرا |
|---|---|
*/5 * * * * | هر ۵ دقیقه |
0 * * * * | ابتدای هر ساعت |
30 2 * * * | هر روز ساعت ۲:۳۰ بامداد |
0 9 * * 6,0-3 | شنبه تا چهارشنبه ساعت ۹ صبح |
0 3 * * 5 | جمعهها ساعت ۳ بامداد |
0 0 1 * * | اولین روز هر ماه میلادی ساعت ۰۰:۰۰ |
دو رفتار که غافلگیر میکند
- اگر هم روز ماه و هم روز هفته محدود شده باشند (هیچکدام ستاره نباشد)، دستور وقتی اجرا میشود که یکی از آنها برقرار باشد. خط
0 4 1 * 5هم روز اول ماه و هم هر جمعه اجرا میشود و به جمعههایی که روز اول ماه هستند محدود نمیماند. - گام در هر بازه از ابتدای همان بازه شروع میشود. */23 در فیلد ساعت یعنی ساعت 0 و 23 هر روز، نه «هر ۲۳ ساعت یک بار».
به جای پنج فیلد میتوانید رشتههای ویژه را هم به کار ببرید: @reboot پس از هر راهاندازی سیستم، @hourly معادل 0 * * * *، @daily معادل 0 0 * * *، @weekly معادل 0 0 * * 0، و @monthly و @yearly.
دستورات crontab و محل فایلهای کرون
crontab -e # edit the current user's crontab
crontab -l # list jobs
sudo crontab -u www-data -l # list another user's jobs
crontab -l > ~/crontab-backup-$(date +%F).txt
crontab -r # removes ALL jobs of the usercrontab -r بدون پرسش همه کارهای کاربر را پاک میکند و روی صفحهکلید کنار e قرار دارد؛ پیش از ویرایشهای مهم با crontab -l نسخه پشتیبان بگیرید. پس از ذخیره فایل در crontab -e نیازی به ریاستارت سرویس نیست؛ cron تغییر فایلها را خودش تشخیص میدهد و آنها را دوباره میخواند. اگر نحو خطی اشتباه باشد، crontab هنگام ذخیره خطا میدهد و فایل را نصب نمیکند.
| محل | فرمت خط | کاربرد |
|---|---|---|
| crontab کاربر (crontab -e) | پنج فیلد و دستور | کارهای یک کاربر مشخص |
| /etc/crontab | پنج فیلد، نام کاربر، دستور | کرون سیستمی |
| /etc/cron.d/ | مانند /etc/crontab | کارهای سیستمی هر برنامه در فایل جداگانه |
| /etc/cron.daily، weekly و monthly | اسکریپت اجرایی بدون خط زمان | اجرا با run-parts یا anacron |
در فایلهای /etc/crontab و /etc/cron.d ستون ششم نام کاربری است که دستور با آن اجرا میشود و فراموش کردن آن رایجترین خطاست. در Debian و Ubuntu نام فایلهای /etc/cron.d فقط میتواند حروف، عدد، خط تیره و زیرخط داشته باشد؛ فایلی به نام backup.cron یا db.backup به دلیل داشتن نقطه نادیده گرفته میشود. نمونه یک فایل درست:
# /etc/cron.d/db-backup
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
MAILTO=""
30 2 * * * root /usr/local/bin/backup-db.sh >> /var/log/backup-db.log 2>&1محیط اجرای Cron: چرا اسکریپت در ترمینال کار میکند و در کرون نه؟
cron دستور را در یک محیط حداقلی اجرا میکند: SHELL برابر /bin/sh است، HOME و LOGNAME از /etc/passwd خوانده میشوند و فایلهایی مثل .bashrc و .profile بارگذاری نمیشوند. PATH هم کوتاه است؛ در cron دبیان و اوبونتو /usr/bin:/bin و در cronie خانواده RHEL /usr/bin:/bin:/usr/sbin:/sbin. به همین دلیل دستوری که در /usr/local/bin یا /usr/sbin است در ترمینال اجرا میشود و در کرون خطای command not found میدهد.
- مسیر کامل برنامهها را بنویسید. مسیر هر دستور را با
command -v phpپیدا کنید. - یا PATH را در ابتدای crontab تعریف کنید، مانند نمونه فایل /etc/cron.d بالا.
- اگر اسکریپت از امکانات bash مثل آرایه یا
[[ ]]استفاده میکند، آن را باbash /usr/local/bin/script.shصدا بزنید یا SHELL=/bin/bash را در crontab بگذارید. - پوشه کاری کرون، پوشه خانه کاربر است. در اسکریپت از مسیر مطلق استفاده کنید یا دستور را با
cd /var/www/app &&شروع کنید. - متغیرهایی که برنامه لازم دارد، مثل رشته اتصال دیتابیس، را داخل خود اسکریپت از فایل مشخصی بخوانید.
کاراکتر درصد در خط crontab معنای خاص دارد: cron آن را به خط جدید تبدیل میکند و هر چه بعد از اولین % بیاید به ورودی دستور فرستاده میشود. نتیجه، دستوری ناقص است که بیصدا شکست میخورد. آن را با بکاسلش escape کنید:
# Wrong: % becomes a newline
0 1 * * * tar czf /backup/www-$(date +%F).tar.gz /var/www
# Right
0 1 * * * tar czf /backup/www-$(date +\%F).tar.gz /var/www
# Reproduce cron's minimal environment when testing a script
env -i SHELL=/bin/sh PATH=/usr/bin:/bin HOME="$HOME" /bin/sh -c '/usr/local/bin/backup-db.sh'لاگ و خروجی Cron Job
cron خروجی هر دستور را برای صاحب crontab ایمیل میکند؛ اگر MAILTO خالی باشد ایمیلی ارسال نمیشود و روی سروری که سرویس ایمیل محلی ندارد، این خروجی معمولاً از دست میرود. پس خروجی و خطا را خودتان به فایل یا ژورنال سیستم بفرستید:
# Append stdout and stderr to a log file
*/10 * * * * /usr/local/bin/sync.sh >> /var/log/sync.log 2>&1
# Or send output to the system journal with a tag
*/10 * * * * /usr/local/bin/sync.sh 2>&1 | /usr/bin/logger -t sync-job
# Did cron start the job?
journalctl -u cron --since today # Debian / Ubuntu
journalctl -u crond --since today # RHEL / Rocky / AlmaLinux
journalctl -t sync-job --since today # output sent with loggerترتیب هدایت خروجی مهم است: >> file 2>&1 هر دو خروجی را در فایل مینویسد، اما 2>&1 >> file خطاها را همچنان به خروجی اصلی میفرستد. لاگ سرویس cron فقط نشان میدهد دستور شروع شده است، نه اینکه موفق بوده؛ موفقیت را باید از خروجی اسکریپت یا کد خروج آن فهمید. برای فایلهای لاگی که خودتان میسازید، یک فایل در /etc/logrotate.d تعریف کنید تا حجمشان کنترل شود.
جلوگیری از اجرای همزمان با flock و timeout
اگر کاری که هر ۵ دقیقه اجرا میشود گاهی بیشتر از ۵ دقیقه طول بکشد، اجرای بعدی روی اجرای قبلی میافتد: بار سرور دو برابر میشود و خروجی مشترک، مثلاً فایل بکاپ، ممکن است خراب شود. flock از بسته util-linux این مشکل را حل میکند:
*/5 * * * * /usr/bin/flock -n /run/lock/sync.lock /usr/bin/timeout 50m /usr/local/bin/sync.sh >> /var/log/sync.log 2>&1با -n، اگر قفل در دست اجرای قبلی باشد، flock بلافاصله خارج میشود و اجرای جدید انجام نمیشود. timeout اجرایی را که گیر کرده پس از مدت مشخص متوقف میکند تا قفل برای همیشه نگه داشته نشود. برای کرون کاربر غیر root، فایل قفل را در پوشهای بگذارید که آن کاربر اجازه نوشتن در آن را دارد. اگر چند سرور کار مشابهی دارند، همه را روی دقیقه صفر تنظیم نکنید تا بار روی مقصد مشترک، مثل سرور بکاپ، یکجا وارد نشود.
خطاهای رایج Cron Job و راهحل آنها
| نشانه | علت محتمل | راهحل |
|---|---|---|
| command not found در خروجی | PATH کرون کوتاهتر از ترمینال است | مسیر کامل دستور یا تعریف PATH در ابتدای crontab |
| فایل /etc/cron.d اجرا نمیشود | نام فایل نقطه دارد یا ستون نام کاربر نوشته نشده | تغییر نام فایل و افزودن نام کاربر پیش از دستور |
| دستور حاوی date ناقص اجرا میشود | کاراکتر % escape نشده | نوشتن \% به جای % |
| خطای [[: not found | اسکریپت bash با sh اجرا شده | SHELL=/bin/bash یا اجرای اسکریپت با bash |
| اجرا در ساعت اشتباه | منطقه زمانی سیستم متفاوت است یا پس از تغییر آن cron ریاستارت نشده | بررسی با timedatectl و ریاستارت سرویس cron یا crond |
| Permission denied | اسکریپت اجرایی نیست یا کاربر کرون به فایل دسترسی ندارد | chmod +x و اجرای کرون با کاربر درست |
| پیام not allowed to use this program | کاربر در /etc/cron.deny است یا در /etc/cron.allow نیست | اصلاح فایلهای cron.allow و cron.deny |
| بار سرور هر چند دقیقه یک بار بالا میرود | اجرای قبلی تمام نشده و اجرای جدید شروع شده | flock -n و timeout |
systemd timer: جایگزین Cron در توزیعهای جدید
روی توزیعهایی که systemd دارند، هر کار زمانبندیشده را میتوان با یک فایل service و یک فایل timer تعریف کرد. خروجی بهطور خودکار در ژورنال همان unit ثبت میشود، اجرای جاافتاده در زمان خاموشی سرور قابل جبران است و محدودیت منابع و وابستگی به سرویسهای دیگر را در همان فایل service تنظیم میکنید. همان بکاپ شبانه با systemd timer:
# /etc/systemd/system/db-backup.service
[Unit]
Description=Nightly database backup
[Service]
Type=oneshot
ExecStart=/usr/local/bin/backup-db.sh
# /etc/systemd/system/db-backup.timer
[Unit]
Description=Run db-backup daily at 02:30
[Timer]
OnCalendar=*-*-* 02:30:00
Persistent=true
RandomizedDelaySec=5m
[Install]
WantedBy=timers.targetsudo systemctl daemon-reload
sudo systemctl enable --now db-backup.timer
systemctl list-timers --all
systemd-analyze calendar "*-*-* 02:30:00" # check the schedule and next run
sudo systemctl start db-backup.service # run once now for testing
journalctl -u db-backup.service --since todayاگر در بخش Unit فایل timer نام سرویس را ننویسید، systemd سرویسی با همان نام و پسوند service را اجرا میکند. Persistent=true زمان آخرین اجرا را روی دیسک نگه میدارد و اگر سرور در زمان مقرر خاموش بوده، پس از روشن شدن کار را اجرا میکند؛ این تنظیم تنها روی تایمرهای OnCalendar اثر دارد. RandomizedDelaySec زمان اجرا را بهصورت تصادفی تا مقدار تعیینشده عقب میاندازد.
| ویژگی | Cron | systemd timer |
|---|---|---|
| تعریف | یک خط | دو فایل unit |
| ثبت خروجی | باید دستی به فایل یا logger هدایت شود | خودکار در ژورنال |
| اجرای جاافتاده پس از خاموشی | ندارد (برای کارهای روزانه anacron) | Persistent=true |
| تست زمانبندی | ابزار داخلی ندارد | systemd-analyze calendar |
| محدودیت منابع و وابستگی | ندارد | با تنظیمات فایل service |
| قابلیت انتقال | روی هر سیستم یونیکسمانند | فقط سیستمهای دارای systemd |
برای یک دستور ساده که روی سرورهای مختلف تکرار میشود، cron هنوز سریعترین راه است. برای کارهای مهمی مثل بکاپ که باید ردشان در لاگ بماند و اجرای جاافتادهشان جبران شود، systemd timer انتخاب منطقیتری است. هر روشی که انتخاب میکنید، شکست بکاپ را فقط به لاگ نسپارید و نتیجه را با مانیتورینگ سرور یا دستکم یک هشدار ساده پیگیری کنید.
سوالات متداول
چطور مطمئن شوم Cron Job اجرا شده است؟
ابتدا لاگ سرویس را با journalctl -u cron (یا crond) ببینید تا معلوم شود دستور شروع شده است. سپس فایل لاگی را که خروجی را در آن نوشتهاید بخوانید. اسکریپتهای مهم باید در پایان کار یک خط موفقیت با تاریخ بنویسند تا نبودن آن خط نشانه مشکل باشد.
آیا بعد از تغییر crontab باید cron را ریاستارت کنم؟
نه. cron تغییر فایلهای crontab را تشخیص میدهد و آنها را دوباره میخواند. ریاستارت فقط پس از تغییر منطقه زمانی سیستم لازم است.
میتوان کاری را هر چند ثانیه یک بار با کرون اجرا کرد؟
کوچکترین واحد cron یک دقیقه است. برای بازههای کوتاهتر از systemd timer با OnUnitActiveSec استفاده کنید و AccuracySec را کم کنید، چون دقت پیشفرض تایمرها یک دقیقه است. اگر کاری واقعاً هر چند ثانیه لازم است، شاید باید سرویسی دائمی باشد، نه کار زمانبندیشده.
crontab -e بهتر است یا فایل در /etc/cron.d؟
برای کارهای شخصی یک کاربر crontab -e مناسب است و هنگام ذخیره نحو را بررسی میکند. برای کارهای سیستمی یا کارهایی که با ابزار اتوماسیون مثل Ansible روی چند سرور پخش میشوند، فایل جداگانه در /etc/cron.d مدیریت و بازبینی را سادهتر میکند.





