مدیریت سرور

آموزش کرون جاب (Cron Job) در لینوکس — زمان‌بندی خودکار کارها روی سرور

نمای شماتیک زمان‌بندی کرون جاب در لینوکس با ساعت و جدول crontab روی سرور

کرون جاب (Cron Job) سرور را از دستگاهی که باید مدام بالای سرش باشید، به کارمندی تبدیل می‌کند که ۳:۳۰ بامداد بکاپ می‌گیرد، هر ۵ دقیقه مصرف را ثبت می‌کند و اول هر ماه گزارش می‌فرستد — بی‌آنکه شما بیدار باشید. در این آموزش crontab را از پایه می‌بینید: نحو پنج‌فیلدی با جدول مثال‌های واقعی، رشته‌های @daily و @reboot، و مهم‌تر از همه این‌که چرا دستوری که در ترمینال بی‌نقص کار می‌کند، در cron بی‌صدا شکست می‌خورد.

کرون جاب چیست و چه کارهایی را باید به آن سپرد؟

cron یک سرویس (daemon) قدیمی و به‌شدت پایدار در لینوکس است که هر دقیقه جدول‌های زمان‌بندی را نگاه می‌کند و هر خطی را که وقتش رسیده باشد اجرا می‌کند. به هر یک از این خط‌ها — یک زمان‌بندی به‌علاوه‌ی یک فرمان — «کرون جاب» می‌گویند، و مجموعه‌شان در فایلی به‌نام crontab (کوتاه‌شده‌ی cron table) نگه‌داری می‌شود.

کارهایی که به‌طور کلاسیک به cron سپرده می‌شوند یک ویژگی مشترک دارند: دوره‌ای‌اند و شروع و پایانِ مشخص دارند:

  • بکاپ شبانه از فایل‌ها و دیتابیس، در ساعتی که سرور خلوت است.
  • پاک‌سازی دوره‌ای: فایل‌های موقت، لاگ‌های قدیمی، کش‌های منقضی — قبل از این‌که دیسک پر شود.
  • گزارش و پایش: ثبت مصرف منابع هر چند دقیقه، گزارش هفتگی، چک سلامت سرویس‌ها.
  • نگه‌داری: تمدید گواهی SSL، همگام‌سازی فایل‌ها، به‌روزرسانی فهرست‌ها.

و یک کاربرد رایج که عمداً جدایش می‌کنیم: «زنده نگه‌داشتن» ربات تلگرام یا هر پروسه‌ی دائمی — در انتهای مقاله می‌بینید چرا این یکی کار cron نیست. اگر هم هنوز با ترمینال دست‌به‌عصا هستید، اول نگاهی به دستورات پرکاربرد لینوکس بیندازید.

cron تقریباً روی هر توزیعی از پیش نصب است؛ اگر روی ایمیج مینیمال نبود:

sudo apt install cron  && sudo systemctl enable --now cron     # Debian / Ubuntu
sudo dnf install cronie && sudo systemctl enable --now crond   # AlmaLinux / Rocky

آموزش crontab: ویرایش، مشاهده و پشتیبان‌گیری از جدول

هر کاربر لینوکس crontab شخصی خودش را دارد و کارهای داخل آن با دسترسی همان کاربر اجرا می‌شوند؛ پس کاری که root لازم ندارد را در crontab کاربر عادی بگذارید.

crontab -e                        # edit your crontab
crontab -l                        # list current jobs
crontab -l > ~/cron-backup.txt    # save a plain-text backup
sudo crontab -u www-data -l       # view another user's table (root only)

در دبیان و اوبونتو بار اول که crontab -e را می‌زنید ویرایشگر را می‌پرسد؛ اگر به‌اشتباه vi را انتخاب کردید با دستور select-editor عوضش کنید. در آلمالینوکس بی‌پرسش vi باز می‌شود — با EDITOR=nano crontab -e ویرایشگر را انتخاب کنید. بعد از ذخیره، تغییرات بلافاصله اعمال می‌شوند — نه ری‌استارتی لازم است، نه reload.

⚠️ crontab -r کل جدول را بدون حتی یک سؤال حذف می‌کند. حرف r روی صفحه‌کلید دقیقاً کنار e است و یک خطای تایپی یعنی همه‌ی زمان‌بندی‌ها ناپدید شده‌اند — بدون تأیید و بدون راه برگشت. عادت کنید بعد از هر تغییر مهم، خروجی crontab -l را در فایلی ذخیره کنید.

نحو پنج‌فیلدی cron — یک‌بار برای همیشه

هر خط crontab از پنج فیلد زمان و سپس فرمان تشکیل می‌شود. فیلدها با فاصله جدا می‌شوند و ترتیبشان هرگز عوض نمی‌شود:

┌───────────── minute (0-59)
│ ┌─────────── hour (0-23)
│ │ ┌───────── day of month (1-31)
│ │ │ ┌─────── month (1-12)
│ │ │ │ ┌───── day of week (0-7, 0 or 7 = Sunday)
│ │ │ │ │
* * * * *   command-to-run

چهار عملگر همه‌ی حالت‌ها را می‌سازند: ستاره یعنی «هر مقدار»، کاما فهرست می‌سازد (1,15)، خط تیره بازه می‌سازد (9-17) و اسلش گام تعریف می‌کند (*/5 یعنی هر ۵ واحد). با همین چهار عملگر، جدول زیر رایج‌ترین زمان‌بندی‌های دنیای واقعی را پوشش می‌دهد:

عبارت cronچه زمانی اجرا می‌شودکاربرد نمونه
*/5 * * * *هر ۵ دقیقهاسنپ‌شات مصرف، چک سلامت
30 3 * * *هر شب ساعت ۳:۳۰ بامدادبکاپ شبانه
0 0 1 * *نیمه‌شبِ اول هر ماهگزارش ماهانه، آرشیو لاگ
0 9 * * 1هر دوشنبه ساعت ۹ صبحگزارش هفتگی
0 4 * * 5هر جمعه ساعت ۴ بامدادکارهای سنگین آخر هفته
0 */6 * * *هر ۶ ساعت، سرِ ساعتهمگام‌سازی دوره‌ای
*/10 9-17 * * *هر ۱۰ دقیقه، فقط ۹ تا ۱۷کارهای ساعات کاری

یک تله‌ی قدیمی: اگر هم «روز ماه» و هم «روز هفته» را محدود کنید، cron بین آن‌ها OR می‌گذارد، نه AND. عبارت 0 3 13 * 5 هم سیزدهمِ هر ماه اجرا می‌شود و هم تمامِ جمعه‌ها — نه فقط جمعه‌ی سیزدهم. برای شرط‌های ترکیبی، منطق تاریخ را داخل اسکریپت ببرید.

محدودیت دوم را هم همین‌جا بدانید: کوچک‌ترین واحد cron یک دقیقه است و «هر ۳۰ ثانیه» در نحو آن وجود ندارد. راه‌حل مرسوم دو خط است که یکی‌شان ۳۰ ثانیه صبر می‌کند — ولی نیاز واقعی به فاصله‌ی زیر یک دقیقه معمولاً یعنی کار باید سرویس دائمی باشد، نه کرون جاب:

* * * * * /usr/local/bin/poll.sh
* * * * * sleep 30; /usr/local/bin/poll.sh

رشته‌های ویژه: @daily و @reboot و بقیه

برای زمان‌بندی‌های خیلی رایج، میان‌برهای خواناتری هم وجود دارد:

@reboot    /usr/local/bin/warmup.sh     # once, right after boot
@hourly    /usr/local/bin/sync.sh       # minute 0 of every hour
@daily     /usr/local/bin/cleanup.sh    # every day at 00:00
@weekly    /usr/local/bin/report.sh     # Sunday at 00:00
@monthly   /usr/local/bin/archive.sh    # 1st of month at 00:00

@reboot برای کارهایی مثل گرم‌کردن کش عالی است، اما «هنگام شروع سرویس cron» اجرا می‌شود، نه لزوماً وقتی شبکه آماده است. و @daily یعنی رأس نیمه‌شب؛ ده کار @daily یعنی ده اجرای هم‌زمان و یک جهش بار — زمان‌ها را عمداً پخش کنید: ۳:۱۰، ۳:۳۰، ۳:۵۰.

بقیه‌ی این مقاله درباره‌ی چیزی است که هیچ جدول مرجعی به شما نمی‌گوید: چرا کارِ درستِ زمان‌بندی‌شده، اجرا نمی‌شود.

ساختار یک خط کرون جاب — پنج فیلد crontab یعنی دقیقه، ساعت، روز ماه، ماه و روز هفته به‌همراه بخش دستور

چرا کرون جاب بی‌صدا شکست می‌خورد؟

پرتکرارترین جمله‌ی دنیای cron این است: «دستور در ترمینال کار می‌کند، ولی در cron نه.» دلیلش تقریباً همیشه یکی است: cron فرمان شما را در محیطی بسیار حداقلی‌تر از شلِ شما اجرا می‌کند و خطایش را هم جایی نشان نمی‌دهد.

محیط حداقلی و مسئله‌ی PATH

وقتی وارد سرور می‌شوید، bash فایل‌های پروفایل (.bashrc و .profile) را می‌خواند و یک PATH بلندبالا می‌سازد. cron هیچ‌کدام را نمی‌خواند: PATH آن معمولاً فقط /usr/bin:/bin است و شل پیش‌فرضش /bin/sh است، نه bash. نتیجه: فرمانی مثل certbot که در ترمینال پیدا می‌شود، در cron خطای «command not found» می‌گیرد — و شما آن خطا را هرگز نمی‌بینید.

# put these at the top of `crontab -e` once
SHELL=/bin/bash
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin

# or find the absolute path once, and always use it
which certbot
# /usr/local/bin/certbot      (snap install: a symlink to /snap/bin/certbot)

همین مثال گویاست: لینکی که دستورالعمل رسمی Certbot برای نصب snap می‌سازد در /usr/local/bin است — خارج از PATH پیش‌فرض cron. البته برای تمدید گواهی کرون جاب دستی ننویسید؛ بسته‌ی snap تایمر snap.certbot.renew.timer و بسته‌ی apt اوبونتو certbot.timer را نصب می‌کند که دو بار در روز اجرا می‌شود — با systemctl list-timers | grep certbot ببینیدش. جزئیاتش در آموزش نصب گواهی رایگان با Certbot آمده است.

قانون طلایی: در crontab همیشه مسیر مطلق بنویسید — هم برای فرمان، هم برای فایل‌ها؛ همین یک عادت، نیمی از مشکلات cron را از ریشه حذف می‌کند.

علامت ٪ معنای ویژه دارد

در خط crontab، کاراکتر درصد به خط جدید تبدیل می‌شود و هرچه بعد از اولین درصد بیاید، ورودی استانداردِ فرمان می‌شود. قربانی همیشگی این قاعده: date.

# BROKEN: cron cuts this line at the first %
0 2 * * * tar -czf /backup/www-$(date +%F).tar.gz -C /var/www .

# CORRECT: escape % with a backslash inside a crontab line
0 2 * * * tar -czf /backup/www-$(date +\%F).tar.gz -C /var/www .

نه ترمینالی هست، نه سؤال‌وجوابی

cron فرمان را بدون TTY تعاملی اجرا می‌کند: sudoای که رمز بخواهد، sshای که تأیید yes/no بخواهد و هر ابزارِ منتظرِ پاسخ، یا می‌میرد یا آویزان می‌ماند — همه‌چیز باید غیرتعاملی باشد (سوییچ -y، حالت batch، احراز هویت کلیدمحور). متغیر HOME از /etc/passwd ست می‌شود، اما دایرکتوری کاری هم همان HOME است نه پوشه‌ی پروژه؛ پس مسیرهای نسبی می‌شکنند — در ابتدای فرمان cd /opt/app بگذارید یا مسیرها را در اسکریپت مطلق کنید.

💡 نکته: در دبیان و اوبونتو، خودِ اجرای هر کرون جاب در syslog ثبت می‌شود: grep CRON /var/log/syslog (در خانواده‌ی RHEL: /var/log/cron). روی ایمیج‌های مینیمال اوبونتو ۲۴.۰۴ ممکن است rsyslog نصب نباشد و اصلاً فایل syslog وجود نداشته باشد؛ راه همه‌جایی، ژورنال systemd است: journalctl -u cron در اوبونتو و journalctl -u crond در آلمالینوکس. این لاگ فقط می‌گوید cron فرمان را «صدا زده»، نه این‌که فرمان موفق بوده — برای دیدن خطاها به بخش بعد نیاز دارید.

خروجی را نگه دارید: ری‌دایرکت، MAILTO و قفل flock

رفتار پیش‌فرض cron این است که هر خروجی (stdout و stderr) را با ایمیلِ محلی برای صاحب crontab بفرستد. اما روی اغلب سرورهای مجازی هیچ MTA (مثل Postfix) نصب نیست؛ آن ایمیل هیچ‌جا نمی‌رود و پیام خطا عملاً دود می‌شود — cron دبیان این را صادقانه در لاگ می‌نویسد: (CRON) info (No MTA installed, discarding output). راه‌حل ساده و همیشگی، ری‌دایرکت به فایل لاگ است:

30 3 * * * /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1

>> یعنی «به انتهای فایل اضافه کن» و 2>&1 یعنی «خطاها هم به همان‌جا». اگر شب بد پیش رفت، صبح مدرک دارید؛ فقط فایل‌های لاگِ رشدکننده را به logrotate بسپارید.

اگر سرورتان MTA یا relay ایمیل دارد، گزارش را با ایمیل هم می‌توانید بگیرید: متغیر MAILTO را بالای crontab ست کنید؛ مقدار خالی (MAILTO="") ارسال را کلاً خاموش می‌کند:

MAILTO=admin@example.com
0 4 * * 1 /usr/local/bin/weekly-report.sh

flock: نگذارید اجراها روی هم سوار شوند

کاری که هر ۵ دقیقه اجرا می‌شود ولی گاهی ۷ دقیقه طول می‌کشد، کم‌کم چند نسخه‌ی هم‌زمان از خودش را روی سرور جمع می‌کند: دو rsync موازی، دو dump هم‌زمان از یک دیتابیس، و باری که هر اجرا برای اجراهای بعدی سنگین‌ترش می‌کند. راه‌حل استاندارد flock است؛ سوییچ -n می‌گوید اگر قفل هنوز دست نسخه‌ی قبلی است، این اجرا بی‌سروصدا صرف‌نظر شود:

*/5 * * * * flock -n /var/lock/bwsnap.lock /usr/local/bin/bwsnap.sh >> /var/log/bwsnap.log 2>&1

crontab کاربر، /etc/crontab و cron.d — فیلد ششمی که همه را غافلگیر می‌کند

کرون جاب‌ها سه خانه‌ی اصلی دارند و فرقشان دقیقاً یک فیلد است. crontab شخصی پنج‌فیلدی است و با دسترسی صاحبش اجرا می‌شود؛ اما /etc/crontab و فایل‌های /etc/cron.d/ سیستمی‌اند و یک فیلد اضافه دارند: نام کاربری که فرمان با آن اجرا می‌شود، بین زمان‌بندی و فرمان:

# /etc/crontab and /etc/cron.d/* use SIX fields: user before command
30 3 * * * root /usr/local/bin/backup.sh

# a personal crontab (crontab -e) uses FIVE fields, no user column
30 3 * * * /usr/local/bin/backup.sh

گافِ کلاسیک دو طرف دارد. خطی را از /etc/crontab به crontab شخصی کپی می‌کنید و cron سعی می‌کند فرمانی به‌نام root اجرا کند («root: command not found»)؛ یا برعکس، خط پنج‌فیلدی در cron.d می‌گذارید و cron اولین کلمه‌ی فرمان را نام کاربر فرض می‌کند — کاربری که وجود ندارد و کار هرگز اجرا نمی‌شود. نکته‌ی ریزتر: در دبیان و اوبونتو نام فایل در /etc/cron.d فقط می‌تواند حرف، رقم، خط زیر و خط تیره داشته باشد — مستندات cron(8) صریحاً می‌گوید نقطه ممنوع است؛ فایلی به‌اسم backup.sh آن‌جا بی‌صدا نادیده گرفته می‌شود. cronie در آلمالینوکس این محدودیت را مستند نکرده، اما همان قاعده را رعایت کنید.

راه سوم پوشه‌های /etc/cron.daily و cron.weekly و cron.monthly است: اسکریپت اجرایی را داخلشان بگذارید تا run-parts اجرایش کند — اغلب با کمک anacron، که اجرای عقب‌افتاده‌ی سرورِ خاموش را بعد از روشن‌شدن جبران می‌کند (روی اوبونتو سرور anacron به‌طور پیش‌فرض نصب نیست و /etc/crontab خودش این پوشه‌ها را در ساعت ثابتی اجرا می‌کند). در دبیان و اوبونتو همان قاعده‌ی نام‌گذاری این‌جا هم برقرار است (run-parts آلمالینوکس نقطه را می‌پذیرد و فقط پسوندهای .rpmsave و .rpmnew را رد می‌کند) و run-parts --test بدون اجرا نشان می‌دهد کدام فایل‌ها واقعاً دیده می‌شوند:

run-parts --test /etc/cron.daily
# /etc/cron.daily/apt-compat
# /etc/cron.daily/dpkg
# /etc/cron.daily/logrotate
# /etc/cron.daily/man-db

منطقه‌ی زمانی: ۳:۳۰ شما، ۳:۳۰ سرور نیست

cron با ساعت محلی سیستم کار می‌کند و اغلب ایمیج‌های ابری روی UTC تحویل داده می‌شوند. تهران UTC+3:30 است؛ یعنی بکاپِ «۳:۳۰ بامدادِ» شما در واقع ساعت ۷ صبح — درست ابتدای ساعت شلوغی — اجرا می‌شود. همین اختلاف مرز «روز» را در گزارش‌های روزانه‌ی vnstat هم ۳٫۵ ساعت جابه‌جا می‌کند.

timedatectl | grep "Time zone"
# Time zone: Etc/UTC (UTC, +0000)

sudo timedatectl set-timezone Asia/Tehran
sudo systemctl restart cron     # cron reads the timezone at startup

ری‌استارت آخر مهم است: cron منطقه‌ی زمانی را هنگام شروع می‌خواند و تا ری‌استارت نشود، با ساعت قبلی ادامه می‌دهد (در آلمالینوکس نام سرویس crond است). یک تفاوت بین دو خانواده را هم بدانید: cronie می‌گذارد با متغیر CRON_TZ=Asia/Tehran بالای crontab فقط همان جدول را در منطقه‌ی دیگری اجرا کنید، اما cron دبیان و اوبونتو چنین چیزی ندارد و همه‌ی کارها را در یک منطقه‌ی زمانی — همان منطقه‌ی سیستم — اجرا می‌کند.

تست همین حالا، اطمینان از فردا

اجرای فرمان در محیطِ شبیهِ cron

به‌جای این‌که تا نیمه‌شب صبر کنید، همان فرمان را همین حالا در محیطی حداقلی اجرا کنید. env -i همه‌ی متغیرهای شل را دور می‌ریزد و فقط چیزی می‌ماند که خودتان بدهید — دقیقاً مثل cron:

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

اگر این‌جا کار کرد، در cron هم کار می‌کند؛ اگر شکست، همان خطایی را جلوی چشمتان می‌بینید که cron بی‌صدا قورت می‌داد.

💡 نکته: برای تست خودِ زمان‌بندی، کار را موقتاً روی * * * * * (هر دقیقه) بگذارید و لاگ را با tail -f تماشا کنید؛ بعد از یکی دو اجرای موفق، زمان‌بندی واقعی را برگردانید. این دو دقیقه صبر، از هر حدس‌زدنی سریع‌تر است.

مانیتورینگ: سکوت یعنی خبر بد

فایل لاگ فقط به دردِ کسی می‌خورد که آن را بخواند — و هیچ‌کس هر روز لاگ بکاپ را نمی‌خواند. برای کارهای حیاتی منطق را برعکس کنید: اسکریپت در پایانِ اجرای موفق یک URL را صدا می‌زند و اگر این تماس در موعدش نیاید، سیستم پایش هشدار می‌دهد. به این الگو dead man's switch می‌گویند و سرویس‌هایی مثل Healthchecks برای همین ساخته شده‌اند:

# success ping: silence means trouble
30 3 * * * /usr/local/bin/backup.sh && curl -fsS -m 10 --retry 3 https://hc-ping.com/your-uuid-here >/dev/null

الگوی مکمل، هشدار فقط هنگام شکست است — مثلاً با پیام تلگرام. متغیرها را بالای crontab تعریف کنید تا خط اصلی خوانا بماند:

TOKEN=123456:AAErealtokenhere
CHAT=11111111
30 3 * * * /usr/local/bin/backup.sh || curl -s -X POST "https://api.telegram.org/bot$TOKEN/sendMessage" -d chat_id="$CHAT" -d text="backup FAILED on $(hostname)"

تفاوتشان مهم است: هشدارِ شکست فقط شکستِ خودِ فرمان را می‌گیرد، اما الگوی ping حتی خاموش‌بودن سرور یا ازکارافتادن خود cron را هم لو می‌دهد. ساخت کامل هشدار تلگرامی و پایش رم و CPU و ترافیک در آموزش مانیتورینگ سرور لینوکس آمده است.

کرون جاب یا تایمر systemd؟ مقایسه‌ی صادقانه

هر لینوکس مدرنی systemd دارد و systemd زمان‌بند خودش را: واحدهای timer. هیچ‌کدام «بهتر مطلق» نیست؛ ابزارها برای دو جور کار ساخته شده‌اند:

معیارcronتایمر systemd
راه‌اندازییک خط در crontabدو فایل (service + timer) و چند دستور
جبران اجرای ازدست‌رفته (سرور خاموش)ندارد (مگر با anacron)Persistent=true — در بوت بعدی اجرا می‌شود
لاگخودتان باید ری‌دایرکت کنیدخودکار در journalctl
جلوگیری از اجرای هم‌زماندستی با flockذاتی — تا سرویس فعال است دوباره شروع نمی‌شود
پخش‌کردن بارندارد (cronie: متغیر RANDOM_DELAY بالای crontab)RandomizedDelaySec
زنده نگه‌داشتن پروسه‌ی دائمیکار cron نیستRestart=always

جمع‌بندی بی‌تعارف: برای کار دوره‌ای ساده cron کاملاً کافی است و یک خط بیشتر نمی‌خواهد. سراغ تایمر systemd وقتی بروید که جبران اجرای ازدست‌رفته حیاتی است، لاگ ساخت‌یافته می‌خواهید یا کار به سرویس دیگری وابسته است. و یک قانون بی‌استثنا: پروسه‌ی دائمی را systemd زنده نگه می‌دارد، نه cron — نمونه‌اش همین پایین.

کرون جاب وردپرس و هاست اشتراکی: بدون ترمینال چه کنیم؟

وردپرس زمان‌بند داخلی خودش را دارد (WP-Cron) ولی این زمان‌بند با بازدید کاربر بیدار می‌شود: هر بار که کسی صفحه‌ای را باز می‌کند، وردپرس چک می‌کند کاری عقب افتاده یا نه. نتیجه دو مشکل هم‌زمان است — روی سایت کم‌بازدید، پست زمان‌بندی‌شده و بکاپ افزونه‌ای ساعت‌ها دیر اجرا می‌شود، و روی سایت پربازدید همان چک اضافه در هر درخواست تکرار می‌شود. راه استاندارد، خاموش‌کردن این رفتار و سپردنِ کار به cron واقعی است:

# wp-config.php
define( 'DISABLE_WP_CRON', true );

# crontab: fire WP-Cron every 5 minutes instead of on page views
*/5 * * * * /usr/bin/wget -q --delete-after https://example.com/wp-cron.php?doing_wp_cron >/dev/null 2>&1

# or, with WP-CLI installed, run only the events that are actually due
*/5 * * * * cd /var/www/example.com && /usr/local/bin/wp cron event run --due-now >/dev/null 2>&1

روی سرور مجازی یکی از این دو خط را در crontab -e کاربرِ وب‌سرور می‌گذارید؛ در راهنمای افزایش سرعت وردپرس این کار یکی از قدم‌های اول است. روی هاست اشتراکی ترمینال ندارید، اما همان cron پشت صحنه هست: در سی‌پنل صفحه‌ی Cron Jobs زیر بخش Advanced همان پنج فیلد را با یک منوی Common Settings پر می‌کند و در دایرکت‌ادمین همین صفحه زیر Advanced Features است. دو نکته که هر دو پنل تذکر می‌دهند: مسیر مطلق بدهید — مثلاً برای اسکریپت PHP از باینری نسخه‌دارِ سرور مثل /opt/cpanel/ea-php82/root/usr/bin/php استفاده کنید نه phpی خالی — و فیلد Cron Email را یا خالی بگذارید یا انتهای هر فرمان >/dev/null 2>&1 بنویسید، وگرنه هر ۵ دقیقه یک ایمیل می‌گیرید. مستندات سی‌پنل هشدار می‌دهد که کار خیلی پرتکرار می‌تواند پیش از پایان اجرای قبلی دوباره شروع شود؛ flock معمولاً هست (بخشی از util-linux): /usr/bin/flock -n $HOME/job.lock /opt/cpanel/ea-php82/root/usr/bin/php $HOME/script.php؛ اگر هاست اجازه نداد، فاصله‌ی معقول (۵ تا ۱۵ دقیقه) انتخاب کنید.

سه نمونه‌ی واقعی، آماده‌ی کپی

۱) بکاپ شبانه‌ی ساعت ۳:۳۰ با tar

# crontab -e
30 3 * * * flock -n /var/lock/backup.lock /usr/local/bin/backup.sh >> /var/log/backup.log 2>&1
#!/bin/bash
# /usr/local/bin/backup.sh  --  make it executable: chmod +x
set -euo pipefail
STAMP=$(date +%F)            # inside a script, % needs no escaping
mkdir -p /backup
tar -czf /backup/www-$STAMP.tar.gz -C /var/www .
find /backup -name 'www-*.tar.gz' -mtime +7 -delete

به تفاوت ظریف دقت کنید: در خط crontab درصد به بک‌اسلش نیاز داشت، داخل اسکریپت نه — یکی از دلایلی که توصیه می‌کنیم منطق همیشه در اسکریپت باشد و crontab فقط زمان‌بندش. نسخه‌ی حرفه‌ای‌تر — بکاپ افزایشی، رمزگذاری و انتقال به مقصد بیرونی — در آموزش بکاپ‌گیری از سرور لینوکس آمده است.

۲) اسنپ‌شات ۵ دقیقه‌ای پهنای باند

*/5 * * * * /usr/local/bin/bwsnap.sh eth0 >/dev/null 2>&1

اسکریپت bwsnap.sh همان شمارنده‌خوانِ مقاوم‌به‌ری‌بوت است که در راهنمای پهنای باند سرور مجازی خط‌به‌خط ساختیم. چون شمارنده‌های کرنل تجمعی‌اند، فاصله‌ی ۵ دقیقه‌ای هیچ داده‌ای را از دست نمی‌دهد؛ فاصله‌ی کوتاه‌تر فقط CPU مصرف می‌کند، دقت اضافه نمی‌کند.

۳) زنده نگه‌داشتن ربات تلگرام — کاری که به cron ندهید

الگوی رایج و اشتباه: * * * * * pgrep -f bot.py || python3 /opt/bot/bot.py. یعنی بعد از هر کرش تا ۵۹ ثانیه قطعی، و اگر الگوی pgrep نادقیق باشد، دو نسخه‌ی موازی ربات و پاسخ‌های تکراری. ابزار درست، سرویس systemd با ری‌استارت خودکار است:

# /etc/systemd/system/telegram-bot.service
[Unit]
Description=Telegram bot
After=network-online.target
Wants=network-online.target

[Service]
User=botuser
ExecStart=/usr/bin/python3 /opt/bot/bot.py
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now telegram-bot

حالا ربات چند ثانیه بعد از هر کرش برمی‌گردد و بعد از ری‌بوت هم خودش بالا می‌آید. راه‌اندازی کامل ربات از صفر — توکن، وب‌هوک و نکات امنیتی — در راهنمای راه‌اندازی ربات تلگرام روی سرور مجازی.

سؤالات پرتکرار

cron job چیست و چه فرقی با crontab دارد؟

cron سرویس زمان‌بند لینوکس است که هر دقیقه جدول‌ها را بررسی می‌کند؛ کرون جاب یک خط از آن جدول است — زمان‌بندی پنج‌فیلدی به‌علاوه‌ی یک فرمان — و crontab هم نام جدول است و هم نام دستوری که آن را می‌بینید و ویرایش می‌کنید (crontab -l و crontab -e).

چرا دستورم در ترمینال کار می‌کند ولی کرون جابش اجرا نمی‌شود؟

چون cron پروفایل شل شما را نمی‌خواند: PATH آن معمولاً فقط /usr/bin:/bin است، شل پیش‌فرضش sh است نه bash، ترمینال تعاملی ندارد و علامت درصد را هم خط جدید حساب می‌کند. مسیرها را مطلق بنویسید، درصد را با بک‌اسلش escape کنید و فرمان را یک بار با env -i در محیط حداقلی تست کنید تا خطای واقعی را ببینید.

از کجا مطمئن شوم کرون جاب شبانه واقعاً اجرا شده است؟

سه لایه بسازید: ثبت اجرا در syslog (grep CRON /var/log/syslog)، ری‌دایرکت خروجی به فایل لاگ با >> و 2>&1، و برای کارهای حیاتی الگوی dead man's switch — اسکریپت در پایان موفق یک URL را صدا می‌زند و اگر این تماس نیاید هشدار می‌گیرید. لایه‌ی سوم حتی خاموش‌بودن سرور یا ازکارافتادن خود cron را هم لو می‌دهد.

کرون جاب بهتر است یا تایمر systemd؟

برای کارهای دوره‌ای ساده cron کافی و بسیار ساده‌تر است. تایمر systemd وقتی می‌ارزد که جبران اجرای ازدست‌رفته، لاگ خودکار در journalctl یا وابستگی به سرویس‌های دیگر لازم داشته باشید. زنده نگه‌داشتن پروسه‌ی دائمی — مثل ربات تلگرام — هم اصلاً کار cron نیست؛ آن را به سرویس systemd با Restart=always بسپارید.

کرون جاب در هاست اشتراکی سی‌پنل چطور تنظیم می‌شود؟

در سی‌پنل صفحه‌ی Cron Jobs زیر بخش Advanced همان پنج فیلد زمان‌بندی لینوکس را دارد و منوی Common Settings آن‌ها را برای حالت‌های رایج مثل «هر ۵ دقیقه» یا «روزانه» پر می‌کند؛ در دایرکت‌ادمین همین صفحه زیر Advanced Features است. فرمان را با مسیر مطلق بنویسید — برای اسکریپت PHP از باینری نسخه‌دار سرور مثل /opt/cpanel/ea-php82/root/usr/bin/php استفاده کنید — و فیلد Cron Email را خالی بگذارید یا انتهای فرمان >/dev/null 2>&1 بنویسید تا با هر اجرا ایمیل نگیرید.

قدم بعدی

از همین امروز شروع کنید: برای هر کرون جاب فعلی‌تان فایل لاگ بگذارید، مسیرها را مطلق کنید، منطقه‌ی زمانی سرور را چک کنید و برای بکاپ شبانه یک ping سلامت بسازید. اگر هم سرور آزمایشی ندارید، یک سرور ابری ساعتی بسازید: در حدود ۶۰ ثانیه تحویل می‌گیرید، چند کرون جاب واقعی می‌چینید، خراب می‌کنید و درست می‌کنید — و فقط ساعت‌هایی را می‌پردازید که سرور وجود داشته؛ کارتان که تمام شد حذفش کنید تا هزینه متوقف شود.

آموزش‌های مرتبط

آماده‌ی تمرین عملی هستید؟

سرور ابری ساعتی مهران هاست در ۶۰ ثانیه تحویل می‌شود — تمرین کنید و فقط بابت همان ساعت‌ها پرداخت کنید. هزینه را پیش از ثبت‌نام با محاسبه‌گر برآورد کنید.