SSD Nodes Learn 🎉 VPS از $5.50/ماه
راهنماها Matt Connorتوسط Matt Connor

حل مشکل اجرای اشتباه زمان‌بندی در n8n

تریگر زمان‌بندی n8n به دلیل تداخل تنظیمات TZ و GENERIC_TIMEZONE در ساعت اشتباه اجرا می‌شود. برای رفع این خطا، هر سه لایه تنظیمات کانتینر و ورک‌فلو را مطابق راهنمای 2026 هماهنگ کنید.

چرا تریگر زمان‌بندی n8n در ساعت اشتباه اجرا می‌شود

تریگر زمان‌بندی (Schedule Trigger) در n8n به این دلیل در ساعت اشتباه اجرا می‌شود که n8n منطقه زمانی (timezone) خود را از سه منبع مجزا می‌خواند و اصلاح تنها یکی از آن‌ها، فقط بخشی از مشکل را حل می‌کند. این سه منبع عبارتند از متغیر TZ خودِ کانتینر، مقدار پیش‌فرض نمونه (instance) در GENERIC_TIMEZONE، و منطقه زمانی تنظیم‌شده در داخل یک ورک‌فلو (workflow) خاص. هر سه مورد را یک‌بار تنظیم کنید تا تمام زمان‌بندی‌هایی که پس از آن ایجاد می‌کنید، دقیقاً در زمان مورد انتظار شما اجرا شوند.

ابتدا یک تصور غلط را اصلاح کنید. یک نمونه n8n که به‌صورت self-hosted راه‌اندازی شده، زمان‌بندی را بر اساس UTC (زمان هماهنگ جهانی) انجام نمی‌دهد. ساعت کانتینر روی UTC تنظیم شده است، زیرا ایمیج رسمی هیچ مقدار TZ را تعیین نمی‌کند. زمان‌بندی یک لایه مجزا است و مقدار پیش‌فرض مستندشده برای GENERIC_TIMEZONE در n8n برابر با America/New_York است (تا اوت 2026)، بنابراین یک نمونه دست‌نخورده، تریگرهای زمان‌بندی خود را بر اساس زمان نیویورک اجرا می‌کند. به همین دلیل است که اختلاف ساعتی که کاربران گزارش می‌دهند، به‌ندرت با فاصله واقعی آن‌ها از UTC مطابقت دارد. کاربری در برلین که درخواست اجرای عملیات در ساعت 06:00 را دارد، آن را در ساعت 12:00 به وقت محلی دریافت می‌کند؛ و در طول هفته‌هایی از ماه مارس که ایالات متحده به ساعت تابستانی تغییر وضعیت داده اما اروپا هنوز این کار را نکرده است، این اختلاف به 11:00 می‌رسد.

سه لایه منطقه زمانی و اولویت آن‌ها

TZ منطقه زمانی سیستم‌عامل داخل کانتینر است. مستندات n8n آن را متغیری توصیف می‌کنند که منطقه زمانی سیستم را تنظیم می‌کند تا خروجی اسکریپت‌ها و دستوراتی مانند date را کنترل کند. این متغیر تعیین می‌کند که date داخل کانتینر چه چیزی را چاپ کند، چه برچسب زمانی در خطوط لاگ کانتینر ثبت شود، new Date() در یک Code node چه مقداری برگرداند و هر اسکریپت shell که در آنجا اجرا می‌کنید چه زمانی را ببیند. این متغیر هیچ تأثیری بر زمان اجرای Schedule Trigger ندارد.

GENERIC_TIMEZONE منطقه زمانی نمونه n8n است. مستندات آن را منطقه زمانی نمونه n8n می‌نامند و ذکر می‌کنند که برای گره‌های زمان‌بندی مانند Cron اهمیت دارد. منظور از Cron در اینجا، نحو استاندارد زمان‌بندی مبتنی بر زمان است و n8n آن را به عنوان گزینه Custom (Cron) در Schedule Trigger ارائه می‌دهد.

منطقه زمانی گردش‌کار (workflow) برای هر گردش‌کار به‌صورت جداگانه تنظیم می‌شود. گردش‌کار را در بوم (canvas) باز کنید، سه نقطه در گوشه سمت راست بالا را انتخاب کنید، به Settings بروید و سپس مقدار Timezone را تغییر دهید. این تنظیم، GENERIC_TIMEZONE را برای همان یک گردش‌کار خاص نادیده می‌گیرد (override می‌کند).

برای یک Schedule Trigger، ترتیب اولویت ثابت است. n8n اگر گردش‌کار دارای منطقه زمانی باشد از آن استفاده می‌کند، در غیر این صورت از منطقه زمانی نمونه در GENERIC_TIMEZONE و در نهایت از پیش‌فرض داخلی خود یعنی America/New_York استفاده می‌کند. TZ در هیچ‌یک از مراحل این تصمیم‌گیری بررسی نمی‌شود.

برای تاریخ‌ها در داخل گره‌های شما، پاسخ به این بستگی دارد که کد از کدام ساعت پرس‌وجو می‌کند. Luxon، کتابخانه تاریخ که در پس‌زمینه عبارت‌های n8n قرار دارد، از منطقه زمانی n8n استفاده می‌کند، بنابراین $now و $today از همان ترتیب اولویت گردش‌کار و سپس نمونه پیروی می‌کنند که در مورد Trigger نیز صادق است. دستور ساده JavaScript یعنی new Date() در یک Code node از سیستم‌عامل پرس‌وجو می‌کند، بنابراین از TZ پیروی می‌کند. این تفاوت، منشأ اصلی سردرگمی در این بخش است: ممکن است Trigger درست عمل کند، در حالی که تمام برچسب‌های زمانی که گردش‌کار می‌نویسد، ساعت‌ها اختلاف داشته باشند.

تنظیم هر سه مورد در فایل Compose

مقادیر TZ و GENERIC_TIMEZONE را در فایل کنار یکدیگر قرار دهید تا کسی یکی را تنظیم نکند و دیگری را فراموش کند. قطعه‌کد زیر بخش مربوط به منطقه زمانی در یک سرویس فعال است. مابقی فایل، شامل reverse proxy و گواهی، از یک نمونه n8n خودمیزبان روی VPS پشت HTTPS گرفته شده است.

services:
  n8n:
    image: docker.n8n.io/n8nio/n8n
    restart: unless-stopped
    ports:
      - "127.0.0.1:5678:5678"
    environment:
      - GENERIC_TIMEZONE=Europe/Berlin
      - TZ=Europe/Berlin
      - N8N_RUNNERS_ENABLED=true
      - N8N_ENFORCE_SETTINGS_FILE_PERMISSIONS=true
    volumes:
      - n8n_data:/home/node/.n8n

volumes:
  n8n_data:

آن را با docker compose up -d اعمال کنید، نه با docker compose restart. دستور restart همان container قبلی را با محیطی که با آن ساخته شده است دوباره اجرا می‌کند، بنابراین تغییرات فایل روی فرآیند در حال اجرا اعمال نمی‌شود. دستور up -d تغییرات محیط را تشخیص داده و container را دوباره می‌سازد. اگر این مقادیر را به‌جای درج مستقیم، در یک فایل env نگهداری می‌کنید، همان قانون بازسازی اعمال می‌شود و راهنمای مدیریت فایل env و secrets در Compose توضیح می‌دهد که آن فایل از کجا خوانده می‌شود.

از نام منطقه IANA (Internet Assigned Numbers Authority) به فرم Region/City استفاده کنید، مانند Europe/Berlin یا America/Sao_Paulo. این نام‌ها قوانین تغییر ساعت تابستانی را برای آن مکان در بر دارند، بنابراین با تغییر ساعت محلی، اختلاف زمانی نیز تغییر می‌کند. نام‌هایی با اختلاف ثابت مانند Etc/GMT+5 هرگز با تغییر فصل تغییر نمی‌کنند و علامت آن‌ها برعکس چیزی است که تصور می‌کنید. دستور LC_ALL=C TZ=Etc/GMT+5 date +%z را اجرا کنید تا -0500 را چاپ کند. از این نام‌ها اجتناب کنید.

چرا تنظیم تنها یکی از آن‌ها، مشکل را به‌صورت ناقص حل می‌کند

تنظیم GENERIC_TIMEZONE به‌تنهایی باعث می‌شود Schedule Trigger در ساعتی که می‌خواهید اجرا شود، اما هر چیزی که زمان را از سیستم‌عامل می‌خواند، همچنان روی UTC باقی بماند. یک Code node که new Date().toString() را فراخوانی می‌کند، یک رشته UTC برمی‌گرداند، خطوط لاگ کانتینر با زمان UTC ثبت می‌شوند و هر نام فایلی که بر اساس ساعت سیستم ساخته شود، در نیمه‌شب اشتباه تغییر می‌کند.

تنظیم TZ به‌تنهایی نتیجه معکوس دارد. docker compose exec n8n date زمان محلی شما را چاپ می‌کند که به نظر موفقیت‌آمیز می‌رسد، در حالی که Schedule Trigger همچنان روی America/New_York است و با اختلاف شش ساعت نسبت به ساعتی که درخواست کرده‌اید، اجرا می‌شود. این نسخه‌ای است که بیشترین زمان را هدر می‌دهد، زیرا بررسی اولیه‌ای که اکثر افراد انجام می‌دهند، همان موردی است که اکنون درست به نظر می‌رسد.

اگر یک workflow timezone را تنظیم کنید و سپس GENERIC_TIMEZONE را تغییر دهید، آن workflow تغییر را نادیده می‌گیرد. مقدار تنظیم‌شده در workflow اولویت دارد و تا زمانی که شخصی تنظیمات آن workflow را باز نکند، همان مقدار باقی می‌ماند. اگر یک workflow در ساعت عجیبی اجرا می‌شود در حالی که سایر workflowهای مجاور آن مشکلی ندارند، تقریباً همیشه دلیل آن همین است.

به‌جای حدس زدن، ساعت‌ها را بررسی کنید

ساعت میزبان (host) و کانتینر را مستقیماً با هم مقایسه کنید.

date
docker compose exec n8n date
docker compose exec n8n printenv TZ GENERIC_TIMEZONE

پس از تنظیم TZ، دو دستور اول باید زمان یکسانی را نمایش دهند. printenv برای هر متغیر موجود، یک خط خروجی چاپ می‌کند؛ بنابراین مشاهده دو خط به معنای تنظیم بودن هر دو متغیر است و مشاهده یک خط نشان می‌دهد که وضعیت نیمه‌تنظیم‌شده است.

اکنون از داخل یک workflow، این موضوع را از خود n8n بپرسید، زیرا shell کانتینر نمی‌تواند به شما بگوید که منطقه زمانی (timezone) در سطح workflow چیست. یک Code node به workflow مشکل‌دار اضافه کنید و آن را یک بار با Execute Workflow اجرا کنید.

return [
  {
    json: {
      n8n_time: $now.toISO(),
      n8n_zone: $now.zoneName,
      system_time: new Date().toString(),
    },
  },
];

n8n_zone منطقه‌ای است که Schedule Trigger این workflow از آن استفاده خواهد کرد؛ این مقدار قبلاً از طریق اولویت workflow نسبت به instance تعیین شده است، بنابراین مستقیماً به پرسش شما پاسخ می‌دهد. system_time منطقه زمانی خود کانتینر را از TZ دریافت می‌کند. این دستور را در همان workflow مشکل‌دار اجرا کنید، نه در یک workflow جدید، زیرا تنظیمات سطح workflow همراه با خودِ workflow جابه‌جا می‌شوند. اگر این دو مقدار با هم تفاوت داشتند، شما بدون باز کردن حتی یک فایل پیکربندی، مشکل را پیدا کرده‌اید.

عبارات Cron در گره Schedule Trigger

گره Schedule Trigger فواصل زمانی ثابتی از ثانیه تا ماه را ارائه می‌دهد، به‌علاوه گزینه Custom (Cron) برای هر زمان‌بندی که در آن فواصل پوشش داده نمی‌شود. عبارت cron در منطقه زمانی (timezone) تعیین‌شده برای workflow خوانده می‌شود، بنابراین 0 6 * * * به معنای 06:00 در همان منطقه است، نه 06:00 به وقت UTC. یک عبارت پنج‌بخشی از crontab guru همان‌طور که هست جای‌گذاری می‌شود. n8n همچنین یک فیلد اختیاری ثانیه را می‌پذیرد که جدول فیلدهای مستندات آن را در ابتدا قرار داده است: ثانیه، دقیقه، ساعت، روز ماه، ماه، روز هفته.

هرگز offset را به‌صورت دستی کدگذاری نکنید. نوشتن 0 4 * * * روی یک instance با تنظیمات UTC برای رسیدن به ساعت 06:00 در برلین، در زمستان درست است اما در تمام طول تابستان یک ساعت خطا دارد، زیرا برلین در زمستان UTC+1 و در تابستان UTC+2 است. منطقه زمانی را تنظیم کنید و ساعتی که واقعاً مد نظر دارید را به وقت محلی بنویسید.

تأثیر تغییر ساعت تابستانی بر وظایف زمان‌بندی‌شده در ساعت 02:30

زمان محلی روی ساعت دیواری، یک لحظهٔ تضمین‌شده نیست. دو بار در سال، یک ساعت ناپدید و یک ساعت تکرار می‌شود و هر وظیفه‌ای که در آن ساعات زمان‌بندی شده باشد، تحت تأثیر قرار می‌گیرد. می‌توانید این اتفاق را با date روی هر سیستم لینوکسی، بدون دخالت n8n، مشاهده کنید.

LC_ALL=C TZ=Europe/Berlin date -d '2027-03-28 02:30'
date: invalid date '2027-03-28 02:30'

این یک غلط تایپی در دستور نیست. در تاریخ 2027-03-28 ساعت برلین مستقیماً از 02:00 به 03:00 می‌پرد، بنابراین ساعت 02:30 محلی در آن روز وجود ندارد و date از تبدیل آن به یک لحظهٔ زمانی خودداری می‌کند. وظیفه‌ای که روی 02:30 محلی تنظیم شده باشد، هیچ لحظه‌ای برای اجرا ندارد. زمان‌های مجاور مشکلی ندارند: date -d '2027-03-28 01:30' به عنوان CET و date -d '2027-03-28 03:30' به عنوان CEST تفسیر می‌شوند.

تغییر ساعت پاییزی تصویر آینه‌ای این وضعیت است. در تاریخ 2027-10-31 ساعت برلین از 03:00 به 02:00 عقب کشیده می‌شود، بنابراین ساعت 02:30 دو بار تکرار می‌شود.

LC_ALL=C TZ=Europe/Berlin date -d '2027-10-31 02:30 CEST' '+%s'
LC_ALL=C TZ=Europe/Berlin date -d '2027-10-31 02:30 CET' '+%s'
1824942600
1824946200

دو لحظهٔ متفاوت که هر دو 02:30 محلی نامیده می‌شوند و 3600 ثانیه با هم فاصله دارند. وظیفه‌ای که در آن زمان قفل شده باشد، یا دو بار اجرا می‌شود یا یک بار در ساعتی که کسی انتخاب نکرده است، و هیچ‌کدام از این نتایج برای اجرای صورت‌حساب یا چرخش بک‌آپ مطلوب نیست. زمان‌بندی را از آن بازه خارج کنید. در اکثر مناطق اروپا و آمریکای شمالی، بازهٔ پرخطر بین 00:00 تا 03:00 محلی است.

زمان‌بندی زیرساخت بر اساس UTC و نمایش زمان محلی به کاربران

پاسخ استاندارد، دو وظیفه‌ای که یک منطقه زمانی (timezone) انجام می‌دهد را از هم جدا می‌کند. ماشین‌ها به یک بازه زمانی پایدار نیاز دارند. انسان‌ها به ساعتی خوانا نیاز دارند.

  • برای کارهایی که ناظری ندارند، منطقه زمانی گردش‌کار (workflow) را روی UTC تنظیم کنید. پشتیبان‌گیری، گرم‌کردن کش (cache warming)، ارسال لاگ و تولید گزارش در این دسته قرار می‌گیرند. در UTC، فاصله بین دو اجرا دقیقاً همان بازه‌ای است که تعیین کرده‌اید، چرا که UTC فاقد تغییرات ساعت تابستانی (daylight saving) است.
  • برای کارهایی که توسط انسان خوانده می‌شوند، زمان‌بندی را در UTC نگه دارید و در لحظه نمایش، آن را تبدیل کنید. یک عبارت ساده این کار را انجام می‌دهد: {{ $now.setZone('Europe/Berlin').toFormat('yyyy-MM-dd HH:mm') }} زمان محلی را در بدنه پیام قرار می‌دهد، در حالی که محرک (trigger) همچنان پایدار باقی می‌ماند.

همین تفکیک در خارج از n8n نیز صدق می‌کند. هنگامی که بخشی از اتوماسیون شما به عنوان یک سرویس و تایمر systemd روی VPS اجرا می‌شود، خط OnCalendar آن در منطقه زمانی سیستم خوانده می‌شود که خود یک ساعت چهارم با تنظیمات مستقل است. نگه داشتن تمام زمان‌بندها روی UTC باعث می‌شود به جای چهار قانون، فقط یک قانون را به خاطر بسپارید. این موضوع برای هر چیزی که یک بازه زمانی را خلاصه می‌کند نیز اهمیت دارد، زیرا یک گردش‌کار عامل هوش مصنوعی n8n که آمار دیروز را درخواست می‌کند، بسته به اینکه کدام منطقه زمانی تعیین شده باشد، بی‌سروصدا از یک بازه 24 ساعته متفاوت استفاده خواهد کرد.

حالت‌های شکست و خروجی‌هایی که مشاهده خواهید کرد

همه چیز با شش ساعت اختلاف اجرا می‌شود. مقدار GENERIC_TIMEZONE هرگز تنظیم نشده است، بنابراین مقدار پیش‌فرض داخلی یعنی America/New_York اعمال می‌شود. docker compose exec n8n printenv GENERIC_TIMEZONE هیچ خروجی‌ای چاپ نمی‌کند. آن را تنظیم کنید و سپس container را دوباره بسازید.

فایل Compose را ویرایش کردید اما هیچ تغییری اعمال نشد. شما دستور docker compose restart را اجرا کردید، بنابراین container محیط اصلی خود را حفظ کرد. دستور docker compose up -d را اجرا کنید و سپس با docker compose exec n8n printenv TZ تغییرات را تأیید کنید.

تریگر درست است اما زمان‌ها اشتباه هستند. فقط GENERIC_TIMEZONE تنظیم شده است. یک new Date() در گره Code همچنان زمان UTC را از سیستم‌عامل می‌خواند. مقدار TZ را روی همان مقدار تنظیم کرده و container را دوباره بسازید.

یک ورک‌فلو تنظیمات instance را نادیده می‌گیرد. آن ورک‌فلو در تنظیمات خود دارای منطقه زمانی اختصاصی است که بر GENERIC_TIMEZONE اولویت دارد. بوم (canvas) را باز کنید، روی سه نقطه کلیک کرده، به بخش Settings و سپس Timezone بروید.

یک job روزانه امسال دو بار اجرا شد یا یک روز را رد کرد. ساعت زمان‌بندی‌شدهٔ آن در بازهٔ تغییر ساعت تابستانی (daylight saving) قرار دارد. ساعت اجرا را تغییر دهید یا آن ورک‌فلو را به UTC منتقل کنید.

FAQ

چرا تریگر زمان‌بندی n8n من در ساعت اشتباهی اجرا می‌شود؟

گردش‌کار (workflow) شما در حال استفاده از منطقه زمانی متفاوتی نسبت به تصور شماست. n8n اگر گردش‌کار دارای منطقه زمانی باشد از آن استفاده می‌کند، در غیر این صورت از منطقه زمانی نمونه (instance) تعریف‌شده در GENERIC_TIMEZONE و در نهایت از مقدار پیش‌فرض داخلی خود یعنی America/New_York استفاده می‌کند. در یک نمونه self-hosted که کسی GENERIC_TIMEZONE را تنظیم نکرده باشد، زمان‌بندی‌ها بر اساس زمان نیویورک انجام می‌شوند، نه UTC؛ به همین دلیل است که اختلاف زمانی به‌ندرت با فاصله شما از UTC مطابقت دارد. دستور docker compose exec n8n printenv GENERIC_TIMEZONE را اجرا کنید. اگر خروجی خالی باشد، یعنی این مقدار هرگز تنظیم نشده است.

تفاوت بین TZ و GENERIC_TIMEZONE در n8n چیست؟

TZ منطقه زمانی سیستم‌عامل داخل کانتینر است. این متغیر تعیین می‌کند که date داخل کانتینر چه مقداری برگرداند، چه برچسب‌های زمانی در خطوط لاگ کانتینر ظاهر شوند، new Date() در یک Code node چه خروجی داشته باشد و هر اسکریپتی که در آنجا اجرا می‌کنید چه زمانی را مشاهده کند. GENERIC_TIMEZONE منطقه زمانی نمونه n8n است و همان چیزی است که گره‌های زمان‌بندی (schedule nodes) و عبارت‌های Luxon مانند $now از آن استفاده می‌کنند. تنظیم یکی بدون دیگری باعث می‌شود یا تریگر صحیح داشته باشید اما برچسب‌های زمانی اشتباه باشند، یا برچسب‌های زمانی صحیح باشند اما تریگر با اختلاف ساعت اجرا شود. هر دو را روی یک مقدار یکسان تنظیم کنید.

آیا باید منطقه زمانی گردش‌کار را تنظیم کنم یا GENERIC_TIMEZONE را؟

مقدار GENERIC_TIMEZONE را به‌عنوان پیش‌فرض برای کل نمونه تنظیم کنید و از تنظیمات مخصوص هر گردش‌کار فقط در مواردی استفاده کنید که یک گردش‌کار واقعاً متعلق به منطقه زمانی دیگری باشد. مقدار گردش‌کار بر مقدار نمونه اولویت دارد و از تغییرات بعدی در GENERIC_TIMEZONE پیروی نمی‌کند؛ بنابراین ردیابی یک override فراموش‌شده در سطح گردش‌کار، ماه‌ها بعد بسیار دشوار خواهد بود.

چه اتفاقی برای کاری که در ساعت 02:30 زمان‌بندی شده است هنگام تغییر ساعت می‌افتد؟

آن زمان محلی یا ناپدید می‌شود یا دو بار رخ می‌دهد. LC_ALL=C TZ=Europe/Berlin date -d '2027-03-28 02:30' مقدار date: invalid date '2027-03-28 02:30' را برمی‌گرداند، زیرا ساعت برلین در آن روز از 02:00 به 03:00 می‌پرد. در تاریخ 2027-10-31، همان زمان روی ساعت دیواری به دو لحظه با فاصله یک ساعت از هم نگاشت می‌شود. کارهای زمان‌بندی‌شده را خارج از بازه محلی 00:00 تا 03:00 قرار دهید، یا گردش‌کار را روی UTC تنظیم کنید و تبدیل به زمان محلی را فقط در جایی انجام دهید که قرار است توسط انسان خوانده شود.