SSD Nodes Learn Hosting plans →
راهنماها Matt Connorتوسط Matt Connor · به‌روزرسانی شده 2026-09-04

چرا Cron Job در لینوکس اجرا نمی‌شود؟ 5 دلیل اصلی

اگر Cron Job شما اجرا نمی‌شود، احتمالا به دلیل تنظیم نبودن PATH، خطای عدم خروج، یا نبود لاگ است. این راهنما 5 دلیل فنی و راه‌حل رفع مشکل در توزیع‌های لینوکس را بررسی می‌کند.

چرا cron job شما اجرا نمی‌شود

یک cron job که «هرگز اجرا نمی‌شود» تقریباً همیشه اجرا شده است. این کار در محیطی غیر از shell شما اجرا شده، در همان ثانیه اول شکست خورده و پیام خطا به جایی ارسال شده که شما آن را نمی‌خوانید. پنج دلیل تقریباً تمام گزارش‌های خرابی را توضیح می‌دهند: مسیر جستجو (search path)، علامت درصد، فایل crontab اشتباه، خروجی که به ایمیل ارسال شده و اسکریپتی که انتظار یک نشست ورود (login session) را دارد.

سرویس cron یک daemon (سرویس پس‌زمینه) است که فایل‌های crontab را می‌خواند و دستورات را طبق زمان‌بندی اجرا می‌کند. این سرویس .bashrc شما را نمی‌خواند، ترمینال باز نمی‌کند، login shell را شروع نمی‌کند و هنگام شکست خوردن یک دستور، به شما اطلاع نمی‌دهد. هر دلیلی که در ادامه می‌آید، ناشی از همین چهار واقعیت است.

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

آیا cron اصلاً اجرا شده است؟

نام unit این دیمون در خانواده‌های مختلف توزیع‌های لینوکسی متفاوت است. هر دو را بررسی کنید و سپس لاگ را بخوانید.

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 است. در هر سیستم‌عامل فقط یکی از این نام‌ها وجود دارد، بنابراین اگر یکی از دستورات زیر خطای unknown unit داد، طبیعی است و مشکلی وجود ندارد.

ورودی‌هایی را که سیستم خودتان ثبت کرده است، بخوانید. به دنبال خطی که از یک راهنما کپی شده است نگردید، زیرا عبارت‌بندی لاگ‌ها بین پیاده‌سازی‌های مختلف cron و تنظیمات لاگ‌گیری متفاوت است. شما فقط دو مورد را بررسی می‌کنید: آیا ورودی در دقیقه‌ای که زمان‌بندی کرده‌اید وجود دارد، و آیا آن ورودی نام دستور شما را ذکر کرده است یا خیر. وجود ورودی با نام دستور شما یعنی cron وظیفه خود را انجام داده و خطا در داخل خودِ دستور است. عدم وجود هرگونه ورودی یعنی cron اصلاً زمان‌بندی شما را دریافت نکرده است، که این مورد مربوط به دلیل 3 در ادامه است.

برخی ایمیج‌ها پیام‌های cron را به‌جای journal، از طریق rsyslog به یک فایل می‌فرستند. مسیر /var/log را برای یافتن فایلی با نام cron یا syslog بررسی کنید و انتهای آن را بخوانید.

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

اگر نه unit و نه لاگ وجود دارد، ممکن است cron اصلاً نصب نشده باشد. ایمیج‌های ابری مینیمال و کانتینرها اغلب آن را حذف می‌کنند.

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

دلیل 1: cron به PATH شما دسترسی ندارد

شل تعاملی شما متغیر PATH را از فایل‌های /etc/profile، ~/.profile، ~/.bashrc و هر فایلی که آن‌ها فراخوانی می‌کنند، می‌سازد. هیچ‌کدام از این‌ها برای یک cron job اجرا نمی‌شوند. cron دستور را با محیط محدود خود شروع می‌کند، بنابراین برنامه‌ای که خارج از دایرکتوری‌های استاندارد سیستم قرار دارد، پیدا نمی‌شود. هر چیزی در مسیرهای /usr/local/bin، /opt، مدیریت‌کننده‌های نسخه زبان، محیط‌های مجازی Python یا فضای کاری Go در معرض این مشکل هستند. این job در همان خط اول شکست می‌خورد و شل یک خطای "not found" تولید می‌کند که متن دقیق آن به شلی که آن را اجرا کرده بستگی دارد.

مسیر کامل (absolute path) هر دستوری که job شما استفاده می‌کند را پیدا کنید.

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

سپس یا آن مسیرهای کامل را در job بنویسید، یا 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 را ذخیره می‌کند، بنابراین job در نهایت با یک مسیر جستجو مواجه می‌شود که هیچ دایرکتوری قابل استفاده‌ای در آن نیست. کل لیست را به‌صورت کامل بنویسید.

یک مدیریت‌کننده نسخه به چیزی بیش از یک مسیر نیاز دارد. nvm، pyenv، rbenv و asdf یک تابع شل یا یک دایرکتوری shims از فایل .bashrc شما نصب می‌کنند و یک cron job هرگز آن فایل را نمی‌خواند. فایل اجرایی نسخه‌دار را با مسیر کامل فراخوانی کنید، یا اسکریپت init مربوط به مدیریت‌کننده را به عنوان خط اول اسکریپت خود 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 یک جایگزینی دستور (command substitution) ناقص دریافت می‌کند و باقی خط شما به عنوان ورودی استاندارد وارد می‌شود. هر علامت درصد را با یک backslash به صورت escape درآورید.

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

دو لایه این یک خط را به ترتیب می‌خوانند. \% یک قانون cron است که توسط cron پیش از شروع هر کاری اعمال می‌شود. $(date +\%F) یک جایگزینی دستور است که بعداً توسط 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ای است که می‌توانید عیب‌یابی کنید.

Cause 3: which crontab did you edit?

There is no single crontab. There are several files, with different owners and different field counts, and a job written into the wrong one is invisible.

  • crontab -e edits the crontab of the user who runs the command. sudo crontab -e edits root's. Two people debugging the same box often end up reading two different files.
  • sudo crontab -l -u deploy lists another user's crontab, which is how you confirm what is actually installed for the account that should run the job.
  • /etc/crontab and every file in /etc/cron.d carry one extra field between the schedule and the command: the user to run as. Paste a five-field user crontab line into /etc/cron.d and the first word of your command is read as a username.
  • Files in /etc/cron.d must be named with letters, digits, underscores and hyphens. A file called backup.sh or site.conf is skipped because of its name alone. Rename it to backup and check your log again.
  • Files in /etc/cron.d should be owned by root, and must not be writable by group or others. ls -l /etc/cron.d shows you both facts at once.
  • Scripts dropped into /etc/cron.daily and its siblings follow the same naming rule and must also carry the execute bit. A missing execute bit is a silent skip.
  • /etc/cron.allow and /etc/cron.deny decide who may install a crontab at all. If either exists on your box, read it before assuming your user is allowed one.

Install a user crontab with the crontab command instead of editing the spool file by hand, because crontab parses the file before installing it. When you save, read what the command prints back to you. If it refuses the file, the previous version stays live and your change never took effect, which looks exactly like cron ignoring you.

The owner also decides permissions. A job in root's crontab creates root-owned files that the application reading them may not be able to write. A job in a normal user's crontab cannot read a root-only directory. Match the owner to the work: application maintenance belongs to the application's own account, which is the reasoning behind replacing WordPress wp-cron with a system cron job. The mode of the files your job creates comes from the umask it inherits, and that is another value which is not your shell's, so how umask sets file permissions is worth reading if a job's output lands unreadable.

دلیل 4: خروجی به ایمیلی ارسال شده که کسی آن را نمی‌خواند

cron هر چیزی را که یک job در خروجی استاندارد (standard output) و خطای استاندارد (standard error) می‌نویسد، جمع‌آوری می‌کند. اگر job حتی یک کاراکتر هم نوشته باشد، cron آن متن را به سیستم ایمیل محلی تحویل می‌دهد که آدرس آن متعلق به مالک crontab یا هر مقداری است که در MAILTO تعیین شده است. در یک VPS مینیمال، معمولاً هیچ MTA (عامل انتقال ایمیل) نصب نیست، بنابراین هیچ چیزی تحویل داده نمی‌شود. خطای شما برای لحظه‌ای وجود داشته و سپس ناپدید شده است. دقیقاً به همین دلیل است که یک job خراب، بی‌صدا به نظر می‌رسد.

خروجی را به فایلی که کنترل آن را در دست دارید هدایت کنید.

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

>> خروجی استاندارد را به انتهای فایل اضافه می‌کند. 2>&1 خطای استاندارد را به همان جایی هدایت می‌کند که خروجی استاندارد در حال حاضر به آن اشاره دارد، بنابراین این دستور باید بعد از تغییر مسیر (redirect) اصلی بیاید. اگر به صورت معکوس یعنی 2>&1 >> file نوشته شود، خطای استاندارد مقصد اصلی خود را حفظ می‌کند و خطایی که به دنبال آن هستید، دقیقاً همان بخشی است که هرگز به فایل نمی‌رسد.

ژورنال (journal) مقصد مناسب دیگری است. logger خروجی را با برچسبی که انتخاب می‌کنید در syslog می‌نویسد.

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

آن را با journalctl -t backup-site بخوانید. این کار باعث می‌شود خروجی خودِ job در کنار ورودی‌های cron باقی بماند، بنابراین دنبال کردن خط زمانی آسان است. اگر به سوابقی نیاز دارید که نشان دهد چه کسی چه دستوری را روی سرور اجرا کرده است، این یک سیستم جداگانه است و حسابرسی دستورات کاربر روی سرور آن را پوشش می‌دهد.

MAILTO="" در ابتدای یک crontab، ارسال ایمیل را برای jobهای زیر آن غیرفعال می‌کند. تنظیم MAILTO روی یک آدرس واقعی تنها زمانی کمک می‌کند که یک MTA فعال وجود داشته باشد، بنابراین پیش از آنکه به آن تکیه کنید، مطمئن شوید که ایمیل از سرور خارج می‌شود.

یک قانون هنگام عیب‌یابی: هرگز > /dev/null 2>&1 را اضافه نکنید. این محبوب‌ترین خط در هر crontab است و تنها مدرکی که دارید را از بین می‌برد. پس از اینکه job به درستی کار کرد، می‌توانید آن را دوباره اضافه کنید.

دلیل 5: اسکریپت به محیطی متکی است که cron آن را فراهم نمی‌کند

هنگامی که دستور پیدا شد و خروجی آن دریافت گردید، آنچه باقی می‌ماند تمام مواردی است که نشست (session) شما به‌طور خودکار در اختیار دارد.

  • ممکن است shell مورد استفاده bash نباشد. با استفاده از ls -l /bin/sh بررسی کنید. در Debian و Ubuntu این دستور به dash اشاره دارد، بنابراین تست‌های double bracket، آرایه‌ها و source با خطای نحوی (syntax error) مواجه می‌شوند. یک خط #!/bin/bash به ابتدای اسکریپت اضافه کنید و آن را فراخوانی کنید، یا SHELL را در ابتدای crontab تنظیم نمایید.
  • دایرکتوری کاری (working directory) همان دایرکتوری نیست که شما در آن حضور داشتید. در همه جا از مسیرهای مطلق (absolute paths) استفاده کنید، یا در خط اول اسکریپت از cd برای رفتن به دایرکتوری مورد نظر استفاده کنید. مسیر نسبی رایج‌ترین دلیل برای این است که یک job «وقتی دستی اجرا می‌شود کار می‌کند».
  • تنظیمات locale با نشست شما متفاوت است. هر چیزی که تاریخ یا عدد را فرمت می‌کند یا متن را مرتب‌سازی می‌کند، ممکن است تحت یک LANG متفاوت، خروجی متفاوتی تولید کند. اگر مرحله بعدی آن خروجی را پردازش می‌کند، به جای حدس و گمان، locale را در خود اسکریپت تنظیم کنید.
  • هیچ TTY (ترمینال) وجود ندارد. دستوری که درخواست تأیید می‌کند، ویرایشگری را باز می‌کند یا نوار پیشرفت نمایش می‌دهد، ممکن است متوقف (hang) شود یا از کار بیفتد. هر پرچم (flag) غیرتعاملی که ابزار ارائه می‌دهد را اضافه کنید.
  • هیچ SSH agent وجود ندارد. SSH_AUTH_SOCK در محیط cron نیست، بنابراین دستور ssh یا rsync که به دلیل بارگذاری agent شما کار می‌کرد، اکنون در احراز هویت شکست می‌خورد. به job کلید اختصاصی خود را بدهید که مالک آن همان کاربرِ job باشد.
  • هیچ user session bus وجود ندارد، بنابراین systemctl --user از یک cron job شکست می‌خورد مگر اینکه XDG_RUNTIME_DIR تنظیم شده باشد. استفاده از یک system unit راهکار بهتری است.

در Fedora، Rocky و Alma یک مورد مشکوک دیگر نیز وجود دارد. SELinux دسترسی cron jobها را محدود می‌کند، بنابراین jobای که به مسیری با برچسب (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 کاربری که قرار است job اصلی را اجرا کند اضافه کنید؛ از مسیرهای مطلق (absolute paths) در هر دو سمت استفاده کنید.

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

یک دقیقه صبر کنید، سپس /home/deploy/cron-probe.log را بخوانید و آن را با خروجی همان دستورات در shell خود مقایسه کنید. خط PATH، دایرکتوری کاری و تنظیمات locale معمولاً به‌تنهایی دلیل شکست اجرای دستور را مشخص می‌کنند. به دو نکته در این تنظیمات توجه کنید: علامت‌های درصد داخل اسکریپت قرار دارند، جایی که قوانین cron اعمال نمی‌شود، و مسیر لاگ باید جایی باشد که کاربرِ job دسترسی نوشتن در آن را داشته باشد.

به‌محض رسیدن به پاسخ، آن خط را از crontab حذف کنید. jobای که هر دقیقه اجرا می‌شود و به یک فایل داده اضافه می‌کند، دیسک‌های کوچک را به‌سرعت و در سکوت پر می‌کند.

آیا زمان‌بندی همان چیزی است که در نظر داشتید؟

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

هنگامی که هم «روز ماه» و هم «روز هفته» محدود شده باشند، یعنی هیچ‌کدام * نباشند، cron کار را زمانی اجرا می‌کند که یکی از این دو فیلد مطابقت داشته باشد. 0 0 13 * 5 به معنای «جمعه سیزدهم» نیست. این دستور در نیمه‌شب روز سیزدهم هر ماه و همچنین در نیمه‌شب هر جمعه اجرا می‌شود. برای رسیدن به یک روز خاص، یکی از این دو فیلد را به صورت * رها کنید و شرط دیگر را داخل اسکریپت تست کنید.

سرویس cron از منطقه زمانی سیستم استفاده می‌کند. بسیاری از ایمیج‌های VPS به‌صورت پیش‌فرض روی UTC (زمان هماهنگ جهانی) تنظیم شده‌اند؛ بنابراین کاری که برای ساعت 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 از قبل گرفته شده باشد، بلافاصله متوقف می‌شود؛ بنابراین اجرای هم‌پوشان (overlapping) به جای انباشته شدن روی اجرای اول، متوقف می‌گردد.

چه زمانی systemd timer ابزار بهتری است

ابزار cron در یک مورد خوب عمل می‌کند: اجرای یک دستور در یک زمان مشخص. در سایر موارد، ضعیف است. یک timer بدون نیاز به هیچ redirect، لاگ‌ها را در journal ثبت می‌کند، وضعیت خروجی (exit status) را برای پرس‌وجو در اختیار شما می‌گذارد، امکان ترتیب‌بندی نسبت به network-online.target را فراهم می‌کند و با ایجاد تأخیر تصادفی، مانع از این می‌شود که صدها سرور همگی در یک ثانیه شروع به کار کنند. زمانی که کار شما به هر یک از این قابلیت‌ها نیاز دارد، استفاده از یک systemd service و timer روی VPS نسبت به مدیریت یک خط در crontab، زحمت کمتری دارد. نوشتن بخش service، پرسشی را مطرح می‌کند که cron هرگز نمی‌پرسد: واحد (unit) چگونه متوجه می‌شود که کار واقعاً شروع شده است؟ بنابراین ابتدا معنای Type= برای واحدهای simple، forking و notify را مطالعه کنید، زیرا اسکریپتی که خود را در حالت پیش‌فرض daemonize می‌کند، باعث می‌شود واحد فعال به نظر برسد در حالی که هیچ پردازشی در پس‌زمینه وجود ندارد. رفتار بازنشانی (retry) نیز باید در همین بخش تعریف شود، زیرا سیاست‌های restart در systemd تعیین می‌کنند که پس از یک شکست چه اتفاقی بیفتد، در حالی که cron هیچ پاسخی برای این مسئله ندارد.

cron را برای کارهای کوچک حفظ کنید. هر چیزی که دارای وابستگی یا سیاست بازنشانی است را به یک timer منتقل کنید. هر دو می‌توانند روی یک سرور اجرا شوند، بنابراین نیازی نیست این مهاجرت را در یک مرحله به پایان برسانید.

FAQ

چرا cron job من به‌صورت دستی اجرا می‌شود اما از طریق cron شکست می‌خورد؟

زیرا محیط shell شما با محیط cron متفاوت است. shell ورود شما فایل‌های /etc/profile و ~/.bashrc را می‌خواند که متغیرهای PATH، تنظیمات محلی (locale) و متغیرهای agent شما را تعیین می‌کنند. cron دستور را بدون هیچ‌کدام از این‌ها، از یک دایرکتوری کاری متفاوت و گاهی با یک shell متفاوت اجرا می‌کند. برای هر دستور از مسیرهای مطلق (absolute paths) استفاده کنید، تنظیمات مورد نیاز خود را در ابتدای crontab یا داخل اسکریپت قرار دهید و یک job آزمایشی یک‌دقیقه‌ای زمان‌بندی کنید که خروجی env | sort، pwd و id را در یک فایل لاگ بنویسد تا بتوانید محیط واقعی cron را به جای حدس زدن، مشاهده کنید.

چگونه بررسی کنم که آیا cron واقعاً job مرا اجرا کرده است؟

لاگ daemon را بخوانید. در Debian و Ubuntu از journalctl -u cron و در Fedora، Rocky و Alma از journalctl -u crond استفاده کنید؛ برخی ایمیج‌ها پیام‌ها را از طریق rsyslog به فایلی در مسیر /var/log هدایت می‌کنند. به دنبال ورودی در دقیقه‌ای که زمان‌بندی کرده‌اید بگردید و بررسی کنید که آیا دستور شما را نام برده است یا خیر. نبود ورودی به این معنی است که cron هرگز زمان‌بندی را دریافت نکرده است، پس مطمئن شوید که crontab درستی را ویرایش کرده‌اید. ورودی بدون نتیجه به این معنی است که دستور شروع شده و متوقف شده است، بنابراین خروجی آن را با یک redirect ضبط کنید.

چرا date +%Y در داخل crontab از کار می‌افتد؟

cron کاراکتر % را در فیلد دستور به عنوان یک کاراکتر خاص در نظر می‌گیرد. اولین % که escape نشده باشد، دستور را پایان می‌دهد، هر چیزی بعد از آن به عنوان ورودی استاندارد (standard input) به آن دستور ارسال می‌شود و هر % بعدی به یک خط جدید تبدیل می‌شود. بنابراین نام فایلی که با فرمت تاریخ ساخته شده، هرگز به برنامه‌ای که برای آن نوشته‌اید نمی‌رسد. هر علامت درصد را با \% escape کنید، یا دستور را به یک اسکریپت منتقل کرده و اسکریپت را از cron فراخوانی کنید، زیرا در داخل اسکریپت، علامت درصد معنای خاصی ندارد.

خروجی cron job من کجا می‌رود؟

به سیستم ایمیل محلی، خطاب به مالک crontab یا هر مقصدی که MAILTO تعیین کرده باشد. اکثر ایمیج‌های VPS هیچ عامل انتقال ایمیلی (MTA) نصب‌شده ندارند، بنابراین پیام حذف می‌شود و به نظر می‌رسد job بی‌صدا اجرا شده است. خروجی را با >> /path/to/log 2>&1 به یک فایل هدایت کنید، این ترتیب را حفظ کنید تا خطای استاندارد (standard error) به دنبال خروجی استاندارد (standard output) بیاید، یا آن را از طریق logger -t myjob pipe کرده و با journalctl -t myjob بخوانید. تا زمانی که در حال دیباگ هستید از > /dev/null 2>&1 استفاده نکنید.

آیا باید از cron استفاده کنم یا systemd timer؟

برای یک دستور ساده در زمانی مشخص از cron استفاده کنید، به‌ویژه زمانی که ممکن است نیاز داشته باشید آن را به ماشینی منتقل کنید که systemd ندارد. زمانی از timer استفاده کنید که می‌خواهید خروجی بدون نیاز به redirect در journal ثبت شود، وضعیت خروج (exit status) قابل پرس‌وجو باشد، ترتیب اجرا پس از بالا آمدن شبکه باشد، تأخیر شروع تصادفی داشته باشید یا سیاست تلاش مجدد (retry policy) پس از شکست نیاز باشد. هر دو می‌توانند روی یک سرور اجرا شوند، بنابراین می‌توانید jobها را یکی‌یکی و در صورت نیاز منتقل کنید.