Skip to content
khiopost
Go back

Cloudflare Workers Cron Trigger가 안 돌 때 — 실전 체크리스트 9항목

Cron Trigger가 예상대로 실행되지 않을 때, 순서대로 점검하면 대부분 원인을 찾을 수 있다. 이 글은 내가 실제로 Cloudflare Workers의 Cron Trigger를 운영하면서 겪었던 문제들을 체크리스트 형태로 정리한 것이다. wrangler.toml 설정 실수부터 UTC 타임존 혼동, scheduled() 핸들러 누락까지 — 한 항목씩 따라가다 보면 어디서 막혔는지 금방 파악할 수 있을 거다.

📑 목차

  1. 증상부터 확인하기
  2. wrangler.toml [triggers] 설정 점검
  3. scheduled() 핸들러 올바르게 내보내기
  4. UTC vs KST 타임존 혼동 바로잡기
  5. wrangler deploy로 변경사항 반영했는지 확인
  6. Free Plan 제한사항
  7. 디버깅: wrangler tail, curl 테스트, 대시보드 확인
  8. scheduled() 내부 에러 핸들링 패턴
  9. 자주 쓰는 Cron 표현식 모음

증상부터 확인하기

Cloudflare Workers Cron Trigger wrangler deploy 터미널

Cron Trigger 문제는 증상이 거의 비슷하다. 대시보드에서 Cron을 등록했는데 아무 일도 일어나지 않거나, wrangler tail을 켜놓고 기다려도 로그가 찍히지 않는 상황이 대표적이다. 처음 이 문제를 만났을 때 나는 Worker 코드 자체에 버그가 있는 줄 알고 한참을 헤맸는데, 실제로는 wrangler.toml에 crons 항목을 아예 빼먹은 거였다. 허무하더라.

증상을 크게 나누면 세 가지이다.

아래 체크리스트를 위에서부터 하나씩 확인해 보자. 순서가 중요하다. 설정이 맞아야 코드가 실행되고, 코드가 실행돼야 타임존을 논할 수 있으니까.

wrangler.toml [triggers] 설정 점검

가장 먼저 봐야 할 곳은 wrangler.toml이다. Cron Trigger는 이 파일의 [triggers] 섹션에 정의되며, 여기가 잘못되면 나머지는 의미가 없다.

# wrangler.toml — 올바른 예시
name = "my-cron-worker"
main = "src/index.ts"
compatibility_date = "2024-09-23"

[triggers] crons = [“0 */6 * * *”, “30 2 * * 1”]

자주 발생하는 실수를 정리하면 이렇다.

배포 후 대시보드의 Settings > Triggers 탭에서 등록된 Cron 스케줄이 보이는지 반드시 확인하자. 여기에 표시되지 않으면 Cron은 절대 실행되지 않는다.

scheduled() 핸들러 올바르게 내보내기

Cron Trigger가 발동하면 Cloudflare는 Worker의 scheduled() 핸들러를 호출한다. 이 핸들러가 올바르게 export 되지 않으면 이벤트가 도착해도 아무 일도 일어나지 않는다. 콘솔에 No scheduled handler라는 경고가 찍히는 경우가 이에 해당된다.

// src/index.ts — ES Modules 형식 (권장)
export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    return new Response("OK");
  },

async scheduled( controller: ScheduledController, env: Env, ctx: ExecutionContext ): Promise<void> { // controller.scheduledTime: 예약된 실행 시각 (밀리초 epoch) // controller.cron: 매칭된 cron 표현식 문자열 console.log(Cron fired: ${controller.cron} at ${new Date(controller.scheduledTime).toISOString()});

ctx.waitUntil(doMyTask(env));

}, };

async function doMyTask(env: Env) { // 실제 작업 로직 const res = await fetch(“https://api.example.com/data”); if (!res.ok) { throw new Error(API 호출 실패: ${res.status} ${res.statusText}); } // … }

주의할 점이 몇 가지 있다.

UTC vs KST 타임존 혼동 바로잡기

이건 정말 많이 겪는 문제이다. Cloudflare Workers의 Cron 표현식은 무조건 UTC 기준이다. 한국 시간(KST)은 UTC+9이므로, 한국 시간 오전 9시에 실행하고 싶으면 UTC 0시, 즉 0 0 * * *로 설정해야 한다.

나는 처음에 0 9 * * *로 설정해놓고 왜 한국 시간 오후 6시에 실행되는지 한참 고민했다. 당연히 UTC 9시는 KST 18시인데, 그때는 그걸 깨닫기까지 시간이 좀 걸렸다.

자주 쓰는 시간대 변환표를 정리해 두겠다.

원하는 KST 시각 UTC 시각 Cron 표현식
매일 KST 06:00 UTC 21:00 (전날) 0 21 * * *
매일 KST 09:00 UTC 00:00 0 0 * * *
매일 KST 12:00 UTC 03:00 0 3 * * *
매일 KST 18:00 UTC 09:00 0 9 * * *
매일 KST 00:00 (자정) UTC 15:00 0 15 * * *
평일 KST 09:00 UTC 00:00 (월~금) 0 0 * * 1-5

주의할 점이 하나 더 있다. KST 기준 새벽 시간대(00:00~08:59)를 노리는 Cron은 UTC 기준으로 전날 15:00~23:59에 해당한다. 요일 조건이 포함된 Cron에서 이 부분을 놓치면 엉뚱한 요일에 실행될 수 있다. 예를 들어 “매주 월요일 KST 06:00″을 원한다면 0 21 * * 0(UTC 일요일 21시)으로 설정해야 한다. 0 21 * * 1로 쓰면 화요일 새벽에 실행된다.

wrangler deploy로 변경사항 반영했는지 확인

wrangler.toml에서 crons를 수정하거나 scheduled() 핸들러를 고친 뒤에는 반드시 wrangler deploy를 다시 실행해야 한다. 로컬에서 아무리 코드를 바꿔도 배포하지 않으면 Cloudflare 엣지에는 이전 버전이 돌아가고 있다.

배포 후에 확인해야 할 출력 메시지가 있다.

$ npx wrangler deploy

⛅️ wrangler 3.80.4

Total Upload: 12.34 KiB / gzip: 3.21 KiB Worker Startup Time: 2 ms Uploaded my-cron-worker (1.42 sec) Deployed my-cron-worker triggers (0.23 sec) https://my-cron-worker.username.workers.dev schedule: 0 */6 * * * schedule: 30 2 * * 1 Current Version ID: a1b2c3d4-e5f6-7890-abcd-ef1234567890

schedule: 줄이 출력에 포함되어 있는지 확인하자. 이 줄이 보이지 않으면 [triggers] 섹션 설정에 문제가 있는 거다. 또한 Current Version ID가 이전과 달라졌는지도 체크 포인트이다. 같은 ID라면 코드 변경이 실제로 반영되지 않은 것이다.

Free Plan 제한사항

Cloudflare Workers Free Plan에서 Cron Trigger를 사용할 때 알아둬야 할 제약이 있다.

디버깅: wrangler tail, curl 테스트, 대시보드 확인

설정을 다 점검했는데도 동작하지 않을 때, 아래 세 가지 방법으로 직접 확인할 수 있다.

wrangler tail로 실시간 로그 확인

wrangler tail은 배포된 Worker의 로그를 실시간으로 스트리밍한다. Cron이 발동되는 시점에 맞춰 켜두면 scheduled() 핸들러 내부의 console.log 출력을 바로 볼 수 있다.

# 실시간 로그 모니터링
$ npx wrangler tail --format pretty

출력 예시 (Cron 실행 시)

GET https://my-cron-worker.username.workers.dev/ - Ok @ 2026-04-02T00:00:01Z (log) Cron fired: 0 0 * * * at 2026-04-02T00:00:00.000Z (log) Task completed successfully

에러 발생 시

(error) API 호출 실패: 503 Service Unavailable

로그가 아예 안 찍히면 Cron 자체가 발동되지 않은 것이고, (error)가 찍히면 핸들러는 호출되었으나 내부 로직에 문제가 있는 것이다. 이 구분이 트러블슈팅의 핵심이다.

curl로 __scheduled 엔드포인트 테스트

Cron이 실행될 때까지 기다리기 힘들다면, 로컬에서 직접 scheduled() 핸들러를 트리거할 수 있다. Wrangler의 로컬 개발 서버는 /__scheduled 엔드포인트를 제공한다.

# 먼저 로컬 개발 서버 실행
$ npx wrangler dev

다른 터미널에서 scheduled 이벤트 트리거

$ curl “http://localhost:8787/__scheduled?cron=0+0+*+*+*“

응답 예시

성공: 빈 응답 (204 No Content) 또는 “Ran scheduled event”

실패: 에러 메시지 출력

이 방법은 실제 Cron 발동 없이도 핸들러 로직을 빠르게 검증할 수 있어서 개발 단계에서 굉장히 유용하다. 나는 새로운 Cron 작업을 추가할 때마다 이 방법으로 먼저 테스트하고, 확인이 끝난 뒤에 배포하는 습관을 들였다. 그 전에는 배포하고 Cron 시각이 올 때까지 멍하니 기다렸는데, 이제 와 생각하면 시간 낭비가 심했다.

대시보드 Cron 히스토리 확인

Cloudflare 대시보드에서 Workers & Pages > 해당 Worker > Triggers 탭으로 이동하면 Cron 실행 히스토리를 볼 수 있다. 각 실행의 상태(Success/Failure), 실행 시각, 실행 시간(duration)이 표시된다. wrangler tail을 놓쳤을 때 여기서 과거 실행 기록을 확인할 수 있다.

히스토리가 완전히 비어 있다면 Cron 등록 자체가 안 된 상태이니 wrangler.toml 설정부터 다시 점검해야 한다.

scheduled() 내부 에러 핸들링 패턴

Cron Trigger에서 실행되는 scheduled() 핸들러는 HTTP 요청과 달리 응답을 반환할 대상이 없다. 에러가 발생하면 조용히 실패하고, 다음 Cron 시각에 다시 시도하는 게 전부이다. 그래서 에러 핸들링을 직접 구현해야 한다.

export default {
  async scheduled(
    controller: ScheduledController,
    env: Env,
    ctx: ExecutionContext
  ): Promise<void> {
    ctx.waitUntil(
      (async () => {
        try {
          const result = await doMyTask(env);
          console.log(`[${controller.cron}] 작업 완료:`, result);
        } catch (err) {
          const errorMessage = err instanceof Error ? err.message : String(err);
          console.error(`[${controller.cron}] 작업 실패: ${errorMessage}`);
      // 실패 알림 (Slack, Discord 등)
      await fetch(env.SLACK_WEBHOOK_URL, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          text: `Cron 작업 실패 (${controller.cron}): ${errorMessage}`,
        }),
      }).catch((e) =&gt;
        console.error("알림 발송도 실패:", e)
      );
    }
  })()
);

}, };

몇 가지 팁을 덧붙이다.

자주 쓰는 Cron 표현식 모음

매번 cron 문법을 찾아보기 귀찮아서 정리해 둔다. 모두 UTC 기준이니 KST로 변환할 때는 위의 변환표를 참고하자.

표현식 의미
* * * * * 매분 실행
*/5 * * * * 5분마다 실행
0 * * * * 매시 정각 실행
0 */6 * * * 6시간마다 실행 (0시, 6시, 12시, 18시 UTC)
0 0 * * * 매일 UTC 00:00 (KST 09:00)
0 0 * * 1-5 평일(월~금) UTC 00:00
0 0 1 * * 매월 1일 UTC 00:00
30 14 * * 0 매주 일요일 UTC 14:30 (KST 23:30)

Cron 표현식이 의도대로 파싱되는지 확인하고 싶다면 crontab.guru 같은 온라인 도구를 활용하자. 다만 이 도구들은 로컬 타임존 기준이 아니라 UTC 기준으로 해석된다는 점을 잊으면 안 된다.

마무리

정리하면, Cron Trigger가 작동하지 않을 때 점검 순서는 이렇다.

  1. wrangler.toml[triggers] 섹션과 crons 배열 문법 확인
  2. scheduled() 핸들러가 올바른 형식으로 export 되었는지 확인
  3. Cron 표현식이 UTC 기준인지 확인 (KST와 9시간 차이)
  4. wrangler deploy 실행 후 출력에서 schedule: 줄 확인
  5. Free Plan 제한(Cron 3개, CPU 10ms) 해당 여부 확인
  6. wrangler tail 또는 curl /__scheduled로 실제 동작 검증

대부분의 문제는 1~3번에서 잡힌다. 특히 UTC 타임존 혼동은 한 번 겪으면 절대 잊지 않는데, 처음 겪기 전에 이 글을 봤다면 한 단계 건너뛴 셈이니 다행이다. Cron이 제대로 돌기 시작하면 그때부터 진짜 재미있는 자동화를 구축할 수 있으니 — 환경변수 Secrets 설정까지 마치면 완벽하다 —, 설정에서 막히지 않길 바란다.


Share this post on:

Previous Post
WordPress Application Passwords 생성 및 REST API 연동 가이드 (2026)
Next Post
Cloudflare Workers 환경변수가 undefined일 때 — 원인 3가지와 해결