جایگزینی Cron با Systemd Timer در لینوکس: زمانبندی دقیق و قابلاعتماد وظایف روی سرور مجازی و اختصاصی

اگر مدیر یک سرور مجازی یا سرور اختصاصی هستید، حتماً با crontab برای زمانبندی بکاپ، پاکسازی لاگ یا اجرای اسکریپتهای نگهداری کار کردهاید — در کنار کارهای ثابت دیگری مثل امنسازی دسترسی SSH به سرور لینوکس که معمولاً همزمان با همین وظایف زمانبندیشده انجام میشود. cron سالها ابزار پیشفرض این کار بوده، اما چند محدودیت دارد که در عمل دردسرساز میشوند: اگر سرور در لحظهٔ اجرای یک job خاموش یا در حال ریبوت باشد، آن job بهسادگی حذف میشود و هیچوقت دوباره اجرا نمیشود. از طرف دیگر، وقتی یک اسکریپت کرون شکست میخورد، معمولاً هیچ لاگ قابلاستفادهای برای فهمیدن دلیل آن وجود ندارد، مگر اینکه خودتان redirect و لاگگیری دستی اضافه کرده باشید.
systemd timer همان کاری را انجام میدهد که cron انجام میداد، اما با ابزارهایی که دقیقاً همین دو مشکل را حل میکنند. در این مقاله با نحوهٔ ساخت فایلهای .service و .timer، استفاده از Persistent=true برای جلوگیری از ازدسترفتن وظایف، و بررسی لاگها با journalctl آشنا میشوید.
محدودیتهای واقعی Cron روی سرور
قبل از رفتن سراغ systemd، بهتر است دقیقتر ببینیم cron کجا کم میآورد:
- بدون اجرای جبرانی: اگر سرور بین ساعت ۲ تا ۴ بامداد خاموش بوده و job بکاپ شما دقیقاً ساعت ۳ زمانبندی شده بود، آن بکاپ هرگز اجرا نمیشود. cron فقط در لحظهٔ دقیق زمانبندیشده چک میکند، نه بعد از آن.
- لاگگیری ضعیف: خروجی استاندارد یک job کرون معمولاً به ایمیل محلی سرور (که اغلب تنظیم نشده) فرستاده میشود یا کلاً گم میشود. فهمیدن اینکه یک job دیشب شکست خورده یا نه، بدون redirect دستی تقریباً غیرممکن است.
- وابستگی ضعیف: cron نمیداند که یک سرویس باید اول بالا بیاید، یا یک mount باید اول آماده باشد. هر job مستقل از بقیهٔ سیستم اجرا میشود.
- مدیریت متفرق: هر خط crontab یک رشتهٔ زمانبندی جدا از منطق اجراست؛ دیباگ کردن یک crontab طولانی با چند ده خط به مرور سخت میشود.
Systemd Timer چیست و چطور کار میکند
systemd timer یک unit از نوع .timer است که به یک unit از نوع .service وصل میشود. فایل .service مشخص میکند چه چیزی اجرا شود، و فایل .timer مشخص میکند چه زمانی. این جداسازی به این معنی است که همان service را میتوانید هم با یک timer و هم بهصورت دستی با systemctl start اجرا کنید، بدون اینکه منطق اجرا را دوباره بنویسید.
هر دو فایل مستقیماً زیر مدیریت systemd قرار میگیرند، یعنی همان ابزارهایی که برای مانیتور کردن سرویسهای دیگر سرور (nginx، postgresql، docker) استفاده میکنید، برای وظایف زمانبندیشده هم کار میکنند: systemctl status، systemctl list-timers، و journalctl -u. اگر یک اپلیکیشن خودتان را هم با systemd service مدیریت میکنید — مثلاً مطابق دیپلوی اپلیکیشن Go روی سرور اختصاصی و VPS با systemd و Nginx — همین دستورها بدون تغییر روی آن سرویس هم کار میکنند.
مقایسهٔ Cron و Systemd Timer
جدول زیر تفاوتهای کلیدی را کنار هم نشان میدهد:
| ویژگی | Cron | Systemd Timer |
|---|---|---|
| اجرای job ازدسترفته بعد از ریبوت | خیر | بله، با Persistent=true |
| لاگ متمرکز و قابلجستوجو | خیر (نیاز به redirect دستی) | بله، از طریق journalctl |
| وابستگی به سرویسها و mount ها | پشتیبانی نمیشود | بله (After=, Requires=) |
| محدود کردن منابع (CPU، حافظه) | پشتیبانی نمیشود | بله، از طریق cgroup در فایل service |
| دقت زمانبندی تصادفی (جلوگیری از thundering herd) | خیر | بله، با RandomizedDelaySec= |
| سینتکس زمانبندی | کوتاهتر (* * * * *) | کمی طولانیتر اما خواناتر (OnCalendar=) |
| در دسترس بودن روی توزیعهای قدیمیتر | همهجا | فقط روی سیستمهایی با systemd |
برای اکثر سرورهای مجازی و اختصاصی امروزی که Ubuntu، Debian، CentOS/Rocky یا AlmaLinux با systemd اجرا میکنند، ردیف آخر جدول عملاً محدودیتی ایجاد نمیکند.
اگر همینجا بخواهید مصرف CPU و RAM یک سرویس را هم محدود کنید، محدود کردن مصرف CPU و RAM سرویسها با systemd و cgroups در سرور لینوکس نحوهٔ تنظیم آن را در همین فایل service نشان میدهد.
ساخت یک وظیفهٔ بکاپ با فایل .service و .timer
فرض کنید میخواهید هر شب یک اسکریپت بکاپ از دیتابیس را اجرا کنید. ابتدا فایل سرویس را میسازیم:
# /etc/systemd/system/db-backup.service
[Unit]
Description=Backup دیتابیس به صورت شبانه
[Service]
Type=oneshot
ExecStart=/usr/local/bin/db-backup.sh
نکته: Type=oneshot به systemd میگوید که این سرویس یکبار اجرا میشود و تمام میشود، برخلاف سرویسهای دائمی مثل nginx.
حالا فایل timer را میسازیم:
# /etc/systemd/system/db-backup.timer
[Unit]
Description=اجرای روزانهٔ db-backup.service
[Timer]
OnCalendar=*-*-* 03:00:00
Persistent=true
RandomizedDelaySec=300
[Install]
WantedBy=timers.target
سه خط بخش [Timer] نکتههای مهمی دارند:
OnCalendar=*-*-* 03:00:00یعنی هر روز ساعت ۳ بامداد.Persistent=trueیعنی اگر سرور در ساعت ۳ خاموش بود، بهمحض بالا آمدن سیستم، این job یکبار اجرا میشود تا جایش خالی نماند.RandomizedDelaySec=300یک تأخیر تصادفی تا ۵ دقیقه اضافه میکند؛ وقتی چند سرور مجازی روی یک هاست فیزیکی مشترک هستند، این گزینه از اجرای همزمان چند job سنگین در یک لحظهٔ دقیق جلوگیری میکند.
برای فعالسازی:
sudo systemctl daemon-reload
sudo systemctl enable --now db-backup.timer
توجه کنید که enable --now روی فایل .timer اجرا میشود، نه روی .service — این timer است که زمانبندی را مدیریت میکند و در زمان مقرر، service مربوط به خودش را صدا میزند.
اجرای جبرانی وظایف ازدسترفته با Persistent=true
این همان قابلیتی است که بیشترین تفاوت را با cron ایجاد میکند. بدون Persistent=true، اگر سرور بین زمانبندیشده و لحظهٔ فعلی خاموش بوده باشد، آن اجرا برای همیشه از دست میرود — دقیقاً مثل cron. با افزودن این یک خط، systemd زمان آخرین اجرای موفق را در دیسک ذخیره میکند و اگر متوجه شود یک اجرا بهدلیل خاموشی سرور رد شده، بهمحض بوت بعدی آن را اجرا میکند.
برای وظایفی مثل بکاپ شبانه یا پاکسازی فایلهای موقت، این یعنی یک ریبوت یا یک دوره maintenance کوتاه دیگر باعث نمیشود یک شب بکاپ نداشته باشید.
لاگگیری با journalctl بهجای شکستهای بیصدا
برای دیدن خروجی کامل یک وظیفه، کافی است نام سرویس را به journalctl بدهید:
journalctl -u db-backup.service --since "1 day ago"
این دستور خروجی استاندارد و استاندارد خطای اسکریپت، بههمراه کد خروج (exit code) و زمان دقیق شروع و پایان را نشان میدهد؛ بدون اینکه لازم باشد خودتان در اسکریپت >> /var/log/db-backup.log 2>&1 اضافه کنید. اگر فقط میخواهید وضعیت کلی و زمان اجرای بعدی را ببینید:
systemctl list-timers db-backup.timer
این دستور آخرین زمان اجرا، زمان اجرای بعدی، و اینکه آیا اجرای قبلی موفق بوده یا نه را در یک خط نشان میدهد — دقیقاً اطلاعاتی که در یک crontab ساده اصلاً وجود ندارد.

اگر چند سرور دارید و میخواهید لاگهای journalctl همهٔ آنها را در یک جا جمع کنید، راهاندازی لاگ متمرکز با Grafana Loki و Alloy گزینهٔ بعدی طبیعی است.
چه زمانی هنوز سراغ Cron بروید
systemd timer برای اکثر وظایف نگهداری سرور گزینهٔ بهتری است، اما دو مورد وجود دارد که cron هنوز سادهتر است:
- اسکریپتهای خیلی کوچک و یکباره که نیازی به لاگگیری دقیق یا اجرای جبرانی ندارند؛ نوشتن یک خط crontab سریعتر از ساخت دو فایل unit است.
- محیطهای بدون systemd (مثلاً برخی کانتینرهای حداقلی یا توزیعهای قدیمیتر) که اصلاً systemd ندارند.
برای وظایف production روی یک سرور مجازی یا اختصاصی — بکاپگیری، rotation لاگ، پاکسازی فایلهای موقت، health-check دورهای — ارزش افزودهٔ Persistent=true و لاگ متمرکز معمولاً بیشتر از هزینهٔ نوشتن دو فایل اضافه است.
جمعبندی
cron ابزار ساده و قدیمیای است که هنوز هم کار میکند، اما روی سرورهای امروزی دو ضعف اصلی آن — نبود اجرای جبرانی و نبود لاگ متمرکز — میتوانند باعث شوند یک بکاپ شبانه بیصدا از دست برود. systemd timer با دو فایل ساده (.service و .timer)، گزینهٔ Persistent=true، و لاگگیری یکپارچه با journalctl، همان زمانبندی را با قابلیت رهگیری و اطمینان بیشتر فراهم میکند.
اگر این تغییر را روی چند سرور بهصورت همزمان انجام میدهید، اتوماسیون راهاندازی و پیکربندی چند سرور مجازی و اختصاصی با Ansible میتواند ساخت و فعالسازی فایلهای .service و .timer را روی همهٔ سرورها یکجا انجام دهد.
اگر زیرساخت شما از همین الان روی یک سرور مجازی آریانت اجرا میشود، همین امروز میتوانید وظایف بکاپ و نگهداری را از crontab به systemd timer منتقل کنید و با journalctl مطمئن شوید که هیچ job دیگر بیصدا شکست نمیخورد.




