SSD Nodes Learn 🎉 VPS от $5.50/мес
Руководства Matt ConnorАвтор: Matt Connor

Почему триггер расписания n8n срабатывает не вовремя

Триггер расписания в n8n часто работает некорректно из-за конфликта трех часовых поясов. Узнайте, как настроить переменные TZ, GENERIC_TIMEZONE и параметры workflow для синхронизации.

Почему триггер расписания n8n срабатывает не в то время

Триггер расписания в n8n срабатывает не в то время, так как n8n считывает настройки часового пояса из трех разных источников, и исправление только одного из них решает проблему лишь частично. Эти три источника: переменная TZ самого контейнера, значение по умолчанию для экземпляра GENERIC_TIMEZONE и часовой пояс, заданный внутри конкретного рабочего процесса. Настройте все три параметра один раз, и все последующие расписания будут срабатывать в ожидаемое время.

Сначала уточним один момент. В свежеустановленном self-hosted n8n расписание не привязано к UTC (всемирному координированному времени). Системные часы контейнера работают по UTC, так как официальный образ не задает переменную TZ. Планировщик — это отдельный уровень, и согласно документации n8n, значение по умолчанию для GENERIC_TIMEZONEAmerica/New_York (по состоянию на август 2026 года), поэтому в нетронутом экземпляре триггеры расписания срабатывают по времени Нью-Йорка. Именно поэтому смещение, о котором сообщают пользователи, редко совпадает с их реальной разницей с UTC. Владелец экземпляра в Берлине, который устанавливает время 06:00, получает срабатывание в 12:00 по местному времени, а в течение нескольких недель марта, когда США перешли на летнее время, а Европа еще нет — в 11:00.

Три уровня часовых поясов и приоритеты

TZ — это часовой пояс операционной системы внутри контейнера. В документации n8n он описывается как переменная, задающая системный часовой пояс для управления выводом скриптов и команд, таких как date. Он определяет, что выводит date внутри контейнера, какая метка времени попадает в строку лога контейнера, что возвращает new Date() в узле Code и что «видит» любой запущенный там shell-скрипт. Он не влияет на время срабатывания триггера Schedule Trigger.

GENERIC_TIMEZONE — это часовой пояс экземпляра n8n. Документация называет его часовым поясом экземпляра n8n и отмечает его важность для узлов планировщика, таких как Cron. В данном контексте Cron означает стандартный синтаксис планирования по времени, который n8n предоставляет в виде опции Custom (Cron) в триггере Schedule Trigger.

Часовой пояс рабочего процесса (workflow) задается индивидуально для каждого процесса. Откройте рабочий процесс на холсте, нажмите на три точки в правом верхнем углу, выберите Settings, а затем измените значение Timezone. Это переопределяет GENERIC_TIMEZONE для конкретного рабочего процесса.

Для триггера Schedule Trigger порядок приоритетов фиксирован. n8n использует часовой пояс рабочего процесса, если он задан; в противном случае — часовой пояс экземпляра из GENERIC_TIMEZONE; если и он не задан — встроенное значение по умолчанию America/New_York. TZ не учитывается ни на одном из этапов этого выбора.

Для дат внутри узлов ответ зависит от того, к каким часам обращается код. Luxon, библиотека для работы с датами, используемая в выражениях n8n, опирается на часовой пояс n8n, поэтому $now и $today следуют тому же порядку приоритетов (сначала рабочий процесс, затем экземпляр), что и триггер. Обычный JavaScript new Date() в узле Code обращается к операционной системе, поэтому он следует TZ. Это разделение является основным источником путаницы: триггер может работать корректно, в то время как все метки времени, записываемые рабочим процессом, будут отличаться на несколько часов.

Настройка всех трех параметров в файле 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 перезапускает тот же контейнер с переменными окружения, которые были заданы при его создании, поэтому изменения в файле не повлияют на запущенный процесс. up -d обнаруживает изменения в окружении и пересоздает контейнер. Если вы храните эти значения в файле env, а не в самом файле конфигурации, правило пересоздания остается прежним; в руководстве по работе с файлами env и секретами в 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, вызывающий new Date().toString(), вернет строку в формате UTC, строки логов контейнера будут иметь метки времени UTC, а любое имя файла, сформированное на основе системных часов, будет менять дату не в полночь по местному времени.

Если настроить только TZ, произойдет обратное. docker compose exec n8n date выведет ваше локальное время, что выглядит как успех, но триггер расписания останется в America/New_York и сработает с отклонением в шесть часов от заданного вами времени. Этот вариант отнимает больше всего времени на отладку, так как проверка, которую большинство пользователей выполняет в первую очередь, теперь проходит успешно.

Если вы зададите часовой пояс для рабочего процесса (workflow), а затем измените GENERIC_TIMEZONE, рабочий процесс проигнорирует это изменение. Значение, заданное в рабочем процессе, имеет приоритет и сохраняется до тех пор, пока кто-либо не откроет настройки этого процесса. Если один рабочий процесс запускается в странное время, в то время как остальные работают корректно, почти всегда причина именно в этом.

Проверьте время вместо догадок

Сравните время хоста и контейнера напрямую.

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

Первые две команды должны выводить одинаковое время, как только будет задана переменная TZ. Команда printenv выводит по одной строке для каждой существующей переменной, поэтому две строки означают, что обе переменные заданы, а одна строка указывает на частично настроенное состояние.

Теперь запросите время у самого n8n изнутри рабочего процесса (workflow), так как оболочка контейнера не покажет вам часовой пояс на уровне рабочего процесса. Добавьте узел Code в рабочий процесс, который работает некорректно, и выполните его один раз с помощью Execute Workflow.

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

n8n_zone — это часовой пояс, который будет использовать триггер Schedule Trigger данного рабочего процесса; он уже определён с учётом приоритета «рабочий процесс важнее экземпляра», поэтому он даёт прямой ответ на вопрос. system_time содержит часовой пояс самого контейнера из TZ. Запустите это в том рабочем процессе, который работает некорректно, а не в новом, так как настройки уровня рабочего процесса сохраняются вместе с ним. Если эти два значения не совпадают, вы нашли проблему, не открывая ни одного конфигурационного файла.

Cron-выражения в узле Schedule Trigger

Узел Schedule Trigger позволяет задавать фиксированные интервалы от секунд до месяцев, а также использовать опцию Custom (Cron) для всех остальных случаев. Cron-выражение интерпретируется в часовом поясе, установленном для рабочего процесса, поэтому 0 6 * * * означает 06:00 именно в этом часовом поясе, а не 06:00 UTC. Пятизначное выражение из crontab guru можно вставлять без изменений. n8n также поддерживает опциональное поле секунд, которое в таблице полей документации стоит первым: секунда, минута, час, день месяца, месяц, день недели.

Никогда не вычисляйте смещение вручную. Запись 0 4 * * * на сервере с UTC для достижения 06:00 в Берлине будет верной зимой, но будет ошибаться на час всё лето, так как в Берлине действует время UTC+1 зимой и UTC+2 летом. Установите нужный часовой пояс и укажите локальное время, которое вам действительно требуется.

Как переход на летнее время влияет на задачу, запланированную на 02:30

Местное время по часам не является гарантированным моментом времени. Дважды в год один час исчезает, а другой повторяется, и любая задача, запланированная на эти часы, подвергается воздействию. Вы можете пронаблюдать это с помощью date на любом сервере Linux, даже без использования 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, а локальное время показывайте пользователям

Стандартный подход разделяет две функции часового пояса. Машинам нужен стабильный интервал. Людям нужно время, которое легко прочитать.

  • Для задач, за которыми никто не следит, установите часовой пояс рабочего процесса на UTC. Сюда относятся резервное копирование, прогрев кэша, отправка логов и генерация отчетов. В UTC интервал между двумя запусками всегда равен заданному значению, в любой день года, так как в UTC нет перехода на летнее время.
  • Для задач, которые просматривает человек, сохраняйте расписание в UTC и выполняйте конвертацию непосредственно при выводе. Это делается одним выражением: {{ $now.setZone('Europe/Berlin').toFormat('yyyy-MM-dd HH:mm') }} добавляет локальное время в тело сообщения, в то время как триггер остается стабильным.

Такое разделение применимо и за пределами n8n. Когда часть вашей автоматизации работает как systemd service and timer on the VPS, строка OnCalendar считывается в системном часовом поясе, который является четвертыми часами со своей собственной настройкой. Если все планировщики работают в UTC, вам нужно помнить только одно правило вместо четырех. Это также важно для любых задач, суммирующих данные за период, поскольку an n8n AI agent workflow, запрашивающий показатели за вчерашний день, будет тихо использовать разные 24-часовые интервалы в зависимости от того, какой часовой пояс был применен.

Типичные сбои и диагностика

Все задачи запускаются с опозданием на шесть часов. Параметр GENERIC_TIMEZONE не был задан, поэтому применяется встроенное значение по умолчанию America/New_York. docker compose exec n8n printenv GENERIC_TIMEZONE не выводит никакой информации. Установите этот параметр, а затем пересоздайте контейнер.

Вы изменили файл Compose, но ничего не изменилось. Вы выполнили docker compose restart, поэтому контейнер сохранил исходное окружение. Выполните docker compose up -d, а затем подтвердите изменения с помощью docker compose exec n8n printenv TZ.

Триггер настроен верно, но временные метки не совпадают. Установлен только GENERIC_TIMEZONE. Узел new Date() (Code node) по-прежнему считывает время UTC из операционной системы. Установите TZ в то же значение и пересоздайте контейнер.

Один из рабочих процессов игнорирует настройки экземпляра. В настройках этого рабочего процесса задан собственный часовой пояс, который имеет приоритет над GENERIC_TIMEZONE. Откройте рабочую область, нажмите на три точки, выберите Settings, затем Timezone.

Ежедневная задача выполнилась дважды или пропустила день в этом году. Запланированный час запуска совпал с переходом на летнее или зимнее время. Измените время запуска или переведите этот рабочий процесс на использование UTC.

FAQ

Почему мой триггер расписания в n8n срабатывает не в тот час?

Рабочий процесс определяет часовой пояс иначе, чем вы предполагаете. n8n выбирает часовой пояс рабочего процесса, если он задан, в противном случае — часовой пояс экземпляра из GENERIC_TIMEZONE, а если и он не задан — встроенное значение по умолчанию America/New_York. На самостоятельно развернутом экземпляре, где никто не настроил GENERIC_TIMEZONE, расписания работают по времени Нью-Йорка, а не по UTC, поэтому смещение редко совпадает с вашим расстоянием от UTC. Выполните docker compose exec n8n printenv GENERIC_TIMEZONE. Отсутствие вывода означает, что переменная не была установлена.

В чем разница между TZ и GENERIC_TIMEZONE в n8n?

TZ — это часовой пояс операционной системы внутри контейнера. Он определяет, что возвращает date внутри контейнера, какие метки времени отображаются в строках логов контейнера, что возвращает new Date() в узле Code и что видит любой запущенный вами скрипт. GENERIC_TIMEZONE — это часовой пояс экземпляра n8n, который используют узлы расписания и выражения Luxon, такие как $now. Установка одного без другого приведет либо к корректному триггеру с неверными метками времени, либо к верным меткам времени при триггере, который срабатывает с опозданием на несколько часов. Установите для обоих параметров одинаковое значение.

Что лучше настроить: часовой пояс рабочего процесса или GENERIC_TIMEZONE?

Установите GENERIC_TIMEZONE в качестве значения по умолчанию для всего экземпляра, а настройку для конкретного рабочего процесса используйте только в тех случаях, когда рабочий процесс действительно относится к другому часовому поясу. Значение рабочего процесса имеет приоритет над значением экземпляра и не учитывает последующие изменения GENERIC_TIMEZONE, поэтому забытое переопределение для конкретного рабочего процесса будет сложно обнаружить спустя месяцы.

Что происходит с задачей, запланированной на 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 и преобразуйте время в местное только там, где его будет читать человек.