5P by GN⁺ | ★ favorite | 댓글 1개
  • systemd 타이머cron의 실무적 대체물로, 일정에 따라 .service 같은 유닛을 실행하고 이력·출력·환경 관리를 더 명확하게 해줌
  • 전통적 cron은 모호한 $PATH, 유실되기 쉬운 stdout/stderr, 추적하기 어려운 실행 이력, 읽기 힘든 스케줄 문법이 약점임
  • 타이머는 같은 스템의 .timer.service를 연결하고, OnCalendar, OnBootSec, OnUnitActiveSec 로 시각·이벤트 기준 실행을 표현함
  • systemd-analyze calendarsystemctl list-timers로 시간 표현과 다음 실행 시각을 확인할 수 있고, WakeSystem=은 절전 상태에서도 실행을 깨울 수 있음
  • RandomizedOffsetSecFixedRandomDelay=는 동시 실행 피크를 줄이고, Persistent=는 비활성 중 놓친 실행을 온라인 직후 보완함

systemd 타이머를 cron 대체로 쓰는 이유

  • cron job은 실제 cron 데몬이 아니어도 “매일 이것을 실행”, “매달 저것을 실행”처럼 일정에 따라 작업을 실행하는 컴퓨팅 기본 요소를 가리키는 말로 널리 쓰임
  • systemd timer는 특정 일정에 따라 다른 유닛, 보통 .service를 실행하는 systemd 유닛이며, 전통적인 cron 데몬의 기능적 대체물이 될 수 있음
  • 전통적인 cron에는 몇 가지 실무상 약점이 있음
    • 모호한 $PATH 설정 때문에 스크립트 실행 결과를 예측하기 어려움
    • stdoutstderr 출력이 블랙홀로 들어가거나 호스트의 메일 시스템으로 보내지는 일이 많음
    • 실행 이력을 추적하고 질의하기 어려움
    • 01,31 04,05 1-15 1,6 * 같은 스케줄 문법은 사람이 읽기 쉽거나 직관적이지 않음
  • systemd 타이머는 이런 문제를 줄이면서 cron 스타일 표현과 비슷한 캘린더 설정도 제공함

기본 구조: 서비스와 타이머

  • systemd 타이머는 실행 대상이 필요하며, .service 유닛은 논리적으로 스크립트처럼 볼 수 있음
  • 예시로 /etc/systemd/system/roulette.service에 다음 유닛을 두면 10분의 1 확률로 컴퓨터를 종료하는 서비스를 설치하게 됨
[Unit]
Description=1 in 10 chance to break your chains

[Service]
ExecStart=/usr/bin/env bash -c '[[ $(($RANDOM % 10)) == 0 ]] && systemctl poweroff || echo LIVE ANOTHER DAY'
  • ExecCondition=은 조건부 실행을 systemd 서비스 옵션으로 표현하는 더 통합된 방식이며, “계속 실행해야 하는가?”를 유닛 수준에서 더 명확하게 드러냄
[Unit]
Description=1 in 10 chance to break your chains

[Service]
ExecCondition=/run/current-system/sw/bin/bash -c '[[ $(($RANDOM % 10)) == 0 ]]'
ExecStart=/run/current-system/sw/bin/systemctl poweroff
  • 조건이 충족되지 않으면 저널에 더 명확한 문구가 남음
May 05 11:05:32 diesel systemd[3117]: Condition check resulted in 1 in 10 chance to break your chains being skipped.
  • 일반적으로 systemd가 제공하는 옵션을 활용하는 편이 직접 스크립팅하는 것보다 더 나은 경험을 줌
    • OnFailure=는 서비스 스크립트 실패에 반응할 때 쓸 수 있음
    • Restart=는 일시적 실패에서 복구를 시도할 때 쓸 수 있음

타이머 유닛 연결과 실행

  • 같은 파일 스템을 가진 /etc/systemd/system/roulette.timer를 두면 roulette.service와 타이머를 연결할 수 있음
[Unit]
Description=impending destruction

[Timer]
OnCalendar=10:00

[Install]
WantedBy=timers.target
  • 기본적으로 타이머의 Unit= 설정은 같은 스템에 .service가 붙은 서비스 유닛을 선택함
    • 이 예시에서는 roulette.service가 선택됨
    • 다른 이름의 서비스 유닛을 실행하려면 Unit=을 바꿀 수 있음
  • ExecStart= 대상은 기본적으로 셸 명령으로 실행되지 않음
    • 절대 경로 대상은 스크립트나 문자열 인자로 스크립트를 기대하는 인터프리터처럼 다뤄야 함
    • ExecStart=/usr/bin/echo Hello | /usr/bin/awk는 이 문맥에서 파이프가 의미 없기 때문에 동작하지 않음
  • ExecStart= 인자는 기본적으로 일부 시스템 매니저 기본값 외의 환경 변수를 상속하지 않음
    • 기본 $PATH는 거의 비어 있는 상태에 가까움
    • /usr/bin/env를 실행하면 systemctl 같은 항목을 사용할 수 있게 하는 간단한 보호 장치가 됨
    • ExecStart=/usr/bin/bash만 썼다면 $PATH에 기본 항목이 들어가지만, env 사용은 추가 안전장치임
  • 타이머 없이 서비스를 직접 실행할 수 있음
systemctl start roulette
  • [Install] 섹션이 없는 서비스는 enable할 수 없으며, 이 구조에서는 타이머가 서비스를 일관되게 실행하는 표준 방식임
  • systemctl은 명시적 접미사가 없어도 기본적으로 roulette.service에 대해 동작함
  • .timer 유닛에 systemctl start를 적용하면 타이머를 작동 상태로 만들지만, 실제 Unit= 대상 서비스를 즉시 실행하지는 않음
systemctl start roulette.timer
  • status는 타이머가 다음에 언제 실행될지 보여줌
systemctl status roulette.timer
Trigger: Sat 2026-04-18 10:00:00 MDT; 35min left
  • 가장 단순한 흐름은 실행 대상 서비스를 만들고, 일정이 있는 타이머를 같은 위치에 두고, 대상이 아니라 타이머를 시작하는 방식임
  • 타이머 유닛의 [Install]WantedBy=가 있으면 부팅 시에도 타이머가 올라오게 할 수 있음
systemctl enable roulette.timer

시간 표현: 캘린더 이벤트와 기간

  • 타이머에서는 일정 표현 방식이 중요하며, 반복되는 시간 구간과 캘린더 이벤트 또는 타임스탬프를 구분해야 함
  • systemd.time(7) 매뉴얼은 예제가 충분하고 타이머 작성 시 첫 번째 참고 자료로 쓸 만함
  • systemd-analyze는 시간 표현식을 검증하고 설명할 수 있음
systemd-analyze calendar '*-*-* *:*:*'
Normalized form: *-*-* *:*:*
    Next elapse: Sat 2026-04-18 16:44:26 MDT
       (in UTC): Sat 2026-04-18 22:44:26 UTC
       From now: 431ms left
  • systemd 타이머는 반복되는 벽시계 기준 시각뿐 아니라, 전통적인 cron과 달리 어떤 이전 이벤트를 기준으로 반복되는 기간도 정의할 수 있음
  • daily의 완전한 형태는 매년, 매월, 매일 00:00:00에 실행된다는 뜻임
*-*-* 00:00:00
│ │ │ │  │  ╰── at second 00
│ │ │ │  ╰───── at minute 00
│ │ │ ╰──────── at hour 00
│ │ ╰────────── every day
│ ╰──────────── every month
╰────────────── every year
  • daily 같은 축약어, 완전한 형식, systemd.time(7)의 다른 지원 값을 사용할 수 있고, systemd-analyze로 가정을 검증할 수 있음

이벤트 기준 실행이 더 적합한 경우

  • 실제 작업에는 “매일 같은 시각에 실행”보다 “다른 이벤트 이후에 실행”이 더 잘 맞을 때가 많음
  • 임시 디렉터리를 비우는 작업은 부팅 직후 cron 표현식이 지나갔다면 /tmp에 정리할 것이 거의 없을 수 있음
  • “컴퓨터가 시작된 지 한 시간 뒤 실행하고 그 뒤 매시간 실행”이라고 표현하면 서비스의 실제 동작과 일정 논리가 더 잘 맞음
[Timer]
OnBootSec=1h
OnUnitActiveSec=1h
  • OnBootSec=1h는 머신 시작 1시간 뒤 한 번 실행한다는 뜻임
  • OnUnitActiveSec=1hUnit=이 실행된 지 1시간 뒤 다시 실행한다는 뜻이며, 타이머가 암묵적으로 계속 반복되게 만듦
  • 이런 주기적 기간 표현은 “매시간 이 분에 실행” 같은 표현보다 “가끔 한 번씩 실행” 용도에 더 자주 맞음
  • Advent of Code API를 폴링하는 Slack 봇 예시에서는 */15 cron 표현식이 API의 “15분마다” 정책을 지키지만, 모두가 같은 식으로 폴링하면 트래픽이 몰릴 수 있음
  • 코드 수정 뒤 타이머를 시작하고 15분이 지날 때마다 실행되게 하면 필요한 동작은 충족하면서 thundering herd 문제를 줄일 가능성이 있음

타이머 상태를 한눈에 보기

  • systemctl list-timers는 한 머신의 타이머 상황을 요약해서 보여주는 고수준 명령임
systemctl list-timers
NEXT                                 LEFT LAST                                  PASSED UNIT                         ACTIVATES
Mon 2026-04-20 15:15:00 MDT      1min 40s Mon 2026-04-20 15:00:05 MDT        13min ago zfs-snapshot-frequent.timer  zfs-snapshot-frequent.service
Mon 2026-04-20 15:32:16 MDT         18min Mon 2026-04-20 14:22:15 MDT        51min ago fwupd-refresh.timer          fwupd-refresh.service
Mon 2026-04-20 16:00:00 MDT         46min Mon 2026-04-20 15:00:05 MDT        13min ago logrotate.timer              logrotate.service
Mon 2026-04-20 16:00:00 MDT         46min Mon 2026-04-20 15:00:05 MDT        13min ago zfs-snapshot-hourly.timer    zfs-snapshot-hourly.service
Tue 2026-04-21 00:00:00 MDT            8h Mon 2026-04-20 09:43:22 MDT     5h 29min ago zfs-snapshot-daily.timer     zfs-snapshot-daily.service
Tue 2026-04-21 07:31:28 MDT           16h Sun 2026-04-19 20:15:47 MDT           7h ago systemd-tmpfiles-clean.timer systemd-tmpfiles-clean.service
Mon 2026-04-27 00:00:00 MDT        6 days Mon 2026-04-20 09:43:22 MDT     5h 29min ago zfs-snapshot-weekly.timer    zfs-snapshot-weekly.service
Mon 2026-04-27 01:09:27 MDT        6 days Mon 2026-04-20 09:43:22 MDT     5h 29min ago fstrim.timer                 fstrim.service
Mon 2026-04-27 04:28:38 MDT        6 days Mon 2026-04-20 09:43:22 MDT     5h 29min ago zpool-trim.timer             zpool-trim.service
Fri 2026-05-01 00:00:00 MDT 1 week 3 days Wed 2026-04-01 10:07:51 MDT 1 week 1 day ago zfs-snapshot-monthly.timer   zfs-snapshot-monthly.service
Fri 2026-05-01 03:17:17 MDT 1 week 3 days Wed 2026-04-01 10:07:51 MDT 1 week 1 day ago zfs-scrub.timer              zfs-scrub.service

11 timers listed.
Pass --all to see loaded but inactive timers, too.
  • 한 명령만으로 타이머 일정에 따라 실행되는 항목의 전체 그림을 얻을 수 있음
  • list-timers는 자주 쓰이는 systemd 하위 명령 계열의 일부임
    • list-units도 유용함
    • list-pathssystemctl에 더 최근 추가된 하위 명령임

절전 상태에서 깨워 실행하기

  • WakeSystem=은 시간이 경과한 타이머가 시스템을 절전 상태에서 깨우도록 할 수 있음
WakeSystem=
    Takes a boolean argument. If true, an elapsing timer will
    cause the system to resume from suspend, should it be
    suspended and if the system supports this.
...
  • 이 기능은 사람이 노트북 덮개를 여는 물리적 동작을 하지 않아도 중요한 스크립트를 실행해야 할 때 유용함
  • Arch나 NixOS처럼 사용 전 패키지 업데이트 다운로드를 지원하는 배포판에서는 밤늦게 업데이트 패키지를 미리 가져오고, 아침에 키보드 앞에서 업데이트할 수 있음
  • .service가 끝난 뒤 다시 절전 상태로 들어가게 하려면 수동으로 다시 절전 처리해야 한다고 매뉴얼에 나와 있음

실행 시각 분산과 thundering herd 완화

  • thundering herd 문제는 여러 프로세스가 동시에 깨어날 때 생기는 시스템 문제임
  • 전 세계 Debian 시스템이 모두 00:00:00apt update하도록 하드코딩되어 있다면, 자정은 모두에게 나쁜 트래픽 피크 시간이 됨
  • FixedRandomDelay=RandomizedOffsetSec=는 실행 시각을 분산하는 데 도움이 됨
FixedRandomDelay=
    Takes a boolean argument. When enabled, the randomized delay
    specified by RandomizedDelaySec= is chosen deterministically,
    and remains stable between all firings of the same timer,
    even if the manager is restarted. ...

RandomizedOffsetSec=
    Offsets the timer by a stable, randomly-selected, and evenly
    distributed amount of time between 0 and the specified time
    value. ...
  • 소프트웨어 업데이트를 확인하는 실제 시스템에서 이런 설정을 사용할 수 있음
  • 실행을 균등 분포로 퍼뜨리면 thundering herd 문제를 줄이고, 동작을 일관되게 만들며, 분산 서비스를 조율 중인 데몬 재시작 같은 방해 활동을 피하는 데 도움이 됨
  • 타이밍 옵션은 전반적으로 매우 설정 가능하고 세밀한 제어를 제공함

놓친 실행을 즉시 보완하기

  • Persistent=는 절전 중인 노트북 때문에 건너뛰면 안 되지만 WakeSystem=까지는 필요하지 않은 일정 스크립트에 특히 적합함
Persistent=
    Takes a boolean argument. If true, the time when the service
    unit was last triggered is stored on disk. When the timer is
    activated, the service unit is triggered immediately if it
    would have been triggered at least once during the time when
    the timer was inactive. ...
  • 구성 관리 체크인을 예약한 시스템이 다운타임을 겪었다면, .timerPersistent=를 두는 것만으로 온라인 직후 올바른 상태로 수렴할 수 있음
  • Persistent=가 없으면 타이머의 정상 실행 시각까지 기다려야 할 수 있고, 그 시간이 길어질 수 있음
  • 놓친 활성화를 감지했을 때 기다리면 안 되는 다른 작업으로 시스템 업데이트, 배치 작업 확인 등이 있음

타이머 작성 시 주의할 점

  • systemctl --user로 다루는 사용자 매니저 문맥의 타이머도 유효하지만, [Install]에 쓰는 대상에 주의해야 함
  • 배포판에 따라 사용자 타이머의 적절한 대상은 default.target일 수 있음
  • cron과 마찬가지로 정확한 시스템 시계를 유지해야 한다는 일반적인 주의사항은 그대로 적용됨
  • systemd 사용자는 timedatectl timesync-status로 동기화 상태를 확인할 수 있음
  • 많은 편집기는 systemd 유닛 파일 형식을 기본 지원하며, 유닛 파일이 커질 때 도움이 됨
  • Emacs에서는 emacs systemd 패키지를 사용할 수 있음

댓글과 토론

Lobste.rs 의견들
  • systemd가 완벽하진 않지만, 많은 설계 결정은 더 전통적인 과거 방식에서 얻은 배움을 바탕으로 한다는 느낌이 듦
    최근 Lennart Poettering이 그 배경을 설명한 CRE의 2015년 에피소드를 다시 들었는데, 지금도 추천할 만함

  • 뼛속까지 systemd를 안 좋아하는 쪽이지만, systemd.timers는 이 제품에서 “그나마 나은” 개념 중 하나라고 봄
    그래서 글쓴이가 타당한 불만을 가진 사람들을 깎아내리는 식으로 방어한 건 좀 의외였음
    그래도 at 명령과 함께 쓰는 건 좋음. 특정 시각에 한 번 실행할 명령은 at, 그 외에는 systemd 타이머와 단순한 유닛 파일을 쓰는 식임
    가장 보고 싶은 개선점은 어느 사용자가 타이머를 실행 중인지 알 수 있게 하는 것임. 2026년에 셸박스를 운영하는 몇 안 되는 사람 중 하나긴 하지만, 매초 디스크를 두드리는 타이머를 어떤 사용자가 만들었는지 알 수 있으면 유용함

    • 이 목적이라면 모두가 시스템 타이머를 설치하게 하는 대신 사용자 유닛을 쓸 수 있지 않을까 싶음
      loginctl enable-linger를 쓰면 활성 사용자 세션 없이도 실행될 수 있는 것으로 알고 있음. 물론 그걸로 충분하지 않은 용례도 있을 테고, 구체적인 상황은 모름
  • systemd 타이머는 특히 사용자 관리 쪽에서 초기 부담이 더 낮았으면 좋겠음
    필요한 설정량을 보면 crontab -e를 이기기가 정말 어렵다

    • 타이머 하나 설정하는 데 여러 설정 파일과 서비스를 요구하는 systemd 방식은… API 선택으로는 말이 안 될 정도임
  • cron 스크립트 로그를 체계적으로 모으는 방법을 오래 고민하다가, 그냥 systemd 타이머를 쓰면 된다는 걸 깨달음
    로깅 문제가 해결됨. 이제 cron을 다시 쓸 이유가 없고, 더 일찍 알았으면 좋았겠음

    • logger로 파이프하거나 로그 파일에 >>로 붙이거나, 기본값 그대로 두고 이메일을 받으면 되지 않나?
  • 구식이라고 해도 좋지만, 서버에서 나에게 닿는 이메일은 아직도 설정해 둠
    자동화해 두면 새 호스트마다 공짜로 따라오고, 평소에도 꽤 편리함
    예를 들어 멀티플렉서 하나 열고 long_running_process | mail root@localhost -s "done $?"를 실행한 뒤 잊어버리는 식임

  • 좋은 글이고, 나도 비슷한 글의 초안을 갖고 있다가 최근에 다시 참고해야 했음
    나처럼 systemd 토끼굴로 들어간다면, 관련 프로젝트 폴더에 있는 유닛 파일과 타이머를 /etc/systemd/system/심볼릭 링크해 두는 걸 추천함
    systemd에 대한 한 가지 불만은 배포판이 설치한 유닛과 직접 작성한 유닛을 구분해 주지 않는다는 점인데, 심볼릭 링크로 그 분리를 직접 유지할 수 있음

    • 사실 그 구분은 경로가 담당함
      시스템/패키지/배포판 유닛은 /usr/lib/systemd/system에 들어가고, 로컬 오버라이드나 로컬 유닛은 /etc/systemd/system에 들어감