예약 발행이 엉뚱한 시각에 나갈 때 — 타임존 점검 순서 5단계

블로그 글을 “내일 오전 9시”로 예약했는데 실제로는 오후 6시에 올라가 있거나, 반대로 새벽에 먼저 공개돼 버리는 경우가 있습니다. 차이가 정확히 9시간이라면 거의 100% 시간대(타임존) 문제입니다. 한국 표준시는 UTC보다 9시간 빠르기 때문에, 어딘가 한 곳이 UTC로 계산하고 다른 곳이 한국 시각으로 계산하면 그 차이가 그대로 발행 시각에 찍힙니다. 이번 글에서는 예약 발행 시간이 안 맞을 때 어디부터 확인해야 하는지, 실제로 원인이 되는 다섯 군데를 순서대로 점검하는 방법을 정리합니다.

먼저 차이 시간부터 보세요

원인을 찾기 전에 “몇 시간 어긋났는지”를 정확히 재면 범위가 크게 좁혀집니다.

어긋난 정도유력한 원인확인할 곳
정확히 9시간 늦게/빠르게한쪽이 UTC, 한쪽이 한국 시각사이트 시간대 설정, 스크립트의 날짜 계산
하루(24시간) 어긋남UTC 기준으로 “오늘/내일”을 계산함클라우드 서버·CI에서 도는 자동화
몇 분~몇 시간씩 불규칙하게 늦음예약 실행 장치(WP-Cron)가 제때 안 돎방문자 수, 서버 cron 설정
아예 안 나가고 “예약 누락”WP-Cron 미실행 또는 플러그인 충돌wp-cron.php 호출 여부

특히 두 번째 경우를 놓치기 쉽습니다. 한국 시각 오전 8시는 UTC로는 전날 오후 11시입니다. 이 시각에 UTC 서버에서 “내일 9시”를 계산하면 한국 기준으로는 오늘 9시, 즉 한 시간 뒤가 됩니다. 하루치 검토 여유를 두려고 예약을 걸었는데 그 여유가 통째로 사라지는 셈입니다.

1단계: 워드프레스 사이트 시간대 확인

가장 먼저 볼 곳입니다. 워드프레스를 새로 설치하면 시간대 기본값이 UTC+0입니다. 관리자 화면의 설정 → 일반 → 시간대에서 “서울”을 고르면 됩니다. “UTC+9″처럼 숫자 오프셋을 직접 고르는 것보다 도시 이름을 고르는 편이 낫습니다. 한국은 서머타임이 없어 결과는 같지만, 도시로 지정해야 REST API의 timezone_string에 값이 채워져서 외부 도구가 시간대를 정확히 인식합니다.

관리자 화면에 들어가지 않고 스크립트로 확인할 수도 있습니다. REST API 루트 주소는 로그인 없이도 사이트 시간대 설정을 돌려줍니다.

import requests
from datetime import datetime

SITE = "https://example.com"   # 내 워드프레스 주소

# 1) REST API 루트에 사이트 시간대 설정이 그대로 들어 있습니다
info = requests.get(SITE + "/wp-json/", timeout=10).json()
print("timezone_string :", repr(info.get("timezone_string")))
print("gmt_offset      :", info.get("gmt_offset"))

# 2) 최근 글 하나의 date(사이트 시각)와 date_gmt(UTC)를 비교합니다
post = requests.get(SITE + "/wp-json/wp/v2/posts?per_page=1", timeout=10).json()[0]
local = datetime.fromisoformat(post["date"])
gmt = datetime.fromisoformat(post["date_gmt"])
diff = (local - gmt).total_seconds() / 3600
print("최근 글          :", post["date"], "(UTC", post["date_gmt"] + ")")
print("차이             :", diff, "시간")

if diff != 9:
    print("경고: 사이트 시간대가 한국(UTC+9)이 아닙니다. 설정 > 일반 > 시간대를 확인하세요.")

시간대가 기본값 그대로인 사이트에서 돌리면 이렇게 나옵니다.

timezone_string : ''
gmt_offset      : 0
최근 글          : 2026-10-07T09:00:00 (UTC 2026-10-07T09:00:00)
차이             : 0.0 시간
경고: 사이트 시간대가 한국(UTC+9)이 아닙니다. 설정 > 일반 > 시간대를 확인하세요.

date와 date_gmt가 똑같다면 사이트가 UTC로 동작하고 있다는 뜻입니다. 이 상태에서 “09:00″으로 예약하면 한국 시각으로는 오후 6시에 공개됩니다.

2단계: 발행 스크립트가 보내는 날짜 값 확인

사이트 설정이 맞는데도 어긋난다면, 다음은 날짜를 만들어 보내는 쪽입니다. 워드프레스 REST API의 글 객체에는 날짜 필드가 두 개 있습니다.

  • date — 사이트 시간대 기준 시각. 사이트가 서울로 설정돼 있으면 “2026-10-08T09:00:00″은 한국 시각 9시입니다.
  • date_gmt — UTC 기준 시각. 사이트 설정과 상관없이 항상 같은 순간을 가리킵니다.

문제는 스크립트에서 “실행하는 컴퓨터의 현재 시각”을 그대로 쓸 때 생깁니다. 내 PC에서는 한국 시각이라 잘 맞다가, 같은 스크립트를 클라우드 서버나 GitHub Actions로 옮기는 순간 서버 시계(대부분 UTC) 기준으로 계산이 바뀝니다. 해결책은 시간대를 코드에 명시하는 것입니다.

from datetime import datetime, timedelta
from zoneinfo import ZoneInfo   # 파이썬 3.9 이상, 윈도우는 pip install tzdata 필요

KST = ZoneInfo("Asia/Seoul")

# 나쁜 예: 실행하는 컴퓨터의 시계를 그대로 믿습니다
naive = datetime.now() + timedelta(days=1)

# 좋은 예: "한국 시각 내일 09:00"을 명시적으로 만듭니다
tomorrow = datetime.now(KST).date() + timedelta(days=1)
publish_at = datetime(tomorrow.year, tomorrow.month, tomorrow.day, 9, 0, tzinfo=KST)

print("한국 시각 :", publish_at.isoformat())
print("UTC 시각  :", publish_at.astimezone(ZoneInfo("UTC")).isoformat())

# 워드프레스 REST API에는 UTC 값을 date_gmt로 넘기면 사이트 설정과 무관하게 정확합니다
payload = {
    "title": "예약 테스트",
    "status": "future",
    "date_gmt": publish_at.astimezone(ZoneInfo("UTC")).strftime("%Y-%m-%dT%H:%M:%S"),
}
print(payload)

datetime.now()처럼 시간대 정보가 없는 값을 “naive” 객체라고 부릅니다. 이런 값은 실행 환경에 따라 의미가 달라지므로, 예약 시각처럼 중요한 값에는 쓰지 않는 게 원칙입니다. date_gmt로 UTC 값을 넘기면 사이트 시간대 설정이 나중에 바뀌어도 발행 순간이 흔들리지 않습니다.

윈도우에는 운영체제 차원의 IANA 시간대 데이터가 없어서 ZoneInfo("Asia/Seoul")가 ZoneInfoNotFoundError를 냅니다. pip install tzdata로 데이터 패키지를 설치하면 해결됩니다.

3단계: 자바스크립트의 날짜 해석 규칙 확인

Node.js로 발행 도구를 만들었다면 Date의 해석 규칙을 꼭 알아둬야 합니다. 날짜만 있는 문자열과 시각까지 있는 문자열의 기준이 서로 다릅니다.

// 같은 문자열처럼 보여도 해석 기준이 다릅니다 (실행 PC가 UTC인 클라우드 서버라고 가정)
new Date('2026-10-08').toISOString();          // '2026-10-08T00:00:00.000Z'  날짜만 → UTC로 해석
new Date('2026-10-08T09:00:00').toISOString(); // '2026-10-08T09:00:00.000Z'  시각 포함 → 실행 PC 로컬로 해석
new Date('2026-10-08T09:00:00+09:00').toISOString(); // '2026-10-08T00:00:00.000Z'  오프셋 명시 → 어디서 돌려도 동일

// 한국 날짜를 안전하게 구하는 법: 실행 PC 시간대와 무관
const kstToday = new Intl.DateTimeFormat('sv-SE', { timeZone: 'Asia/Seoul' }).format(new Date());
console.log(kstToday); // 예: '2026-10-07'

세 줄 모두 “10월 8일”을 다루는 것 같지만, 오프셋이 없는 두 줄은 실행 환경에 따라 결과가 달라집니다. 날짜를 문자열로 주고받을 때는 +09:00이나 Z를 항상 붙이고, “오늘이 며칠인지”는 Intl.DateTimeFormat에 timeZone을 지정해서 구하는 습관을 들이면 이런 사고가 사라집니다.

4단계: 서버와 스케줄러의 시간대 확인

자동화가 리눅스 서버의 cron으로 돈다면, cron은 서버 시스템 시간대를 기준으로 실행 시각을 계산합니다. 클라우드 서버는 대부분 UTC로 만들어지므로 0 9 * * *라고 적으면 한국 시각 오후 6시에 실행됩니다.

# 서버 시간대 확인
timedatectl
#   Time zone: Etc/UTC (UTC, +0000)   ← 클라우드 서버 대부분의 기본값

# 한국 시간대로 변경 (cron도 이 값을 따라갑니다)
sudo timedatectl set-timezone Asia/Seoul

# 시스템을 바꾸기 싫다면 명령 하나만 한국 시각으로 실행
TZ=Asia/Seoul date "+%F %T"

서버 전체 시간대를 바꾸기 부담스럽다면 crontab 시각을 UTC로 환산해서 적으면 됩니다. 한국 오전 9시는 UTC 0시이므로 0 0 * * *입니다. GitHub Actions의 schedule 트리거도 기본적으로 UTC 기준이라 같은 방식으로 환산해야 합니다.

윈도우 작업 스케줄러는 PC 로컬 시각을 따르므로 보통 문제가 없지만, 트리거 설정의 “표준 시간대 간 동기화”를 체크해두면 PC 시간대가 바뀌어도 원래 지정한 절대 시각에 실행됩니다. 노트북을 들고 해외 출장을 가는 경우에 의미가 있습니다.

5단계: 예약이 “늦게” 나간다면 WP-Cron 확인

시간대가 전부 맞는데도 몇 분에서 몇 시간씩 늦게 공개되거나, 글 목록에 “예약 누락(Missed schedule)”이 뜬다면 원인은 시간대가 아니라 WP-Cron입니다. 워드프레스의 예약 작업은 진짜 cron이 아니라, 누군가 사이트에 접속할 때 “지금 실행할 예약이 있나?”를 확인하는 방식으로 돕니다. 그래서 방문자가 적은 새벽 시간이나 개설 초기 블로그에서는 예약 시각이 지나도 아무도 접속하지 않아 발행이 밀립니다. 캐시 플러그인이나 CDN이 페이지를 캐시에서 바로 응답해 PHP가 실행되지 않을 때도 같은 일이 생깁니다.

# 1) wp-config.php 에 추가 (방문자 접속에 의존하는 WP-Cron 끄기)
define('DISABLE_WP_CRON', true);

# 2) 서버 crontab -e 에 추가 (5분마다 진짜 cron이 예약 작업을 실행)
*/5 * * * * curl -s https://example.com/wp-cron.php?doing_wp_cron > /dev/null 2>&1

이렇게 바꾸면 방문자 유무와 상관없이 5분 간격으로 예약 작업이 처리됩니다. 공유 호스팅이라 crontab을 쓸 수 없다면 호스팅 관리 화면의 “크론 작업” 메뉴에서 같은 주소를 등록하면 됩니다.

점검 순서 요약

  1. 어긋난 시간을 잰다 — 9시간이면 시간대, 불규칙하면 WP-Cron.
  2. /wp-json/의 timezone_string과 gmt_offset을 확인한다.
  3. 발행 스크립트가 시간대 없는 현재 시각을 쓰는지 본다. 가능하면 date_gmt로 UTC를 넘긴다.
  4. 자동화가 도는 서버·CI의 시계가 UTC인지 확인하고, cron 시각을 환산한다.
  5. 늦게 나가는 문제라면 DISABLE_WP_CRON과 서버 cron으로 바꾼다.

마무리

지금 바로 할 수 있는 가장 작은 일은 1단계의 확인 스크립트를 한 번 돌려보는 것입니다. 1분이면 끝나고, 차이가 9시간이 아니라면 그 자리에서 원인이 드러납니다. 그다음으로 발행 스크립트에서 datetime.now()나 오프셋 없는 날짜 문자열을 찾아 시간대를 명시하면, 같은 스크립트를 어느 서버로 옮겨도 예약 시각이 흔들리지 않습니다.

다음 글에서는 예약한 글이 실제로 공개됐는지를 RSS 피드로 자동 확인하는 스크립트를 만들어 보겠습니다. 오늘 다룬 시간 문제가 생겼을 때 가장 먼저 알려주는 안전장치가 됩니다.

댓글 남기기