Skip to main content

z4j-scheduler

v1.4.0 Apache-2.0

z4j-scheduler

Engine-agnostic Python task scheduler. One service drives Celery, RQ, Dramatiq, Huey, arq, and taskiq.

z4j-scheduler is a Python task scheduler that drives any of the six engines z4j supports from one process. Schedules live in z4j's database; the dashboard creates, edits, pauses, resumes, and renames them without a daemon restart. The scheduler ticks once per second, dispatches due fires over gRPC to brain, and brain delivers the schedule.fire command to the agent, which enqueues the task on whichever broker the engine uses. Live editing, importer + exporter for every native scheduler, HA leader election, and a tamper-evident HMAC-chained audit log of edits, in one binary.

Latest
v1.4.0
Released
2026-04-30
License
Apache-2.0

Install

bash
pip install z4j-scheduler

Capabilities

  • Engine-agnostic dispatch (Celery, RQ, Dramatiq, Huey, arq, taskiq)
  • Live edit from dashboard, declarative config, or REST
  • Per-schedule catch-up policy (skip / fire-one-missed / fire-all-missed)
  • Importer + exporter for every native scheduler (round-trip pinned by tests)
  • Postgres advisory-lock leader for HA
  • Tamper-evident HMAC-chained audit log of schedule edits
  • Cron, interval, one-shot, and solar (sunrise/sunset) triggers
  • Embedded mode (brain-supervised subprocess) or standalone process

Limitations

  • Adds one process to your stack (or one subprocess in embedded mode)
  • Brain becomes a hard dependency for schedule edits
  • Current PyPI release is 1.4.0 (2026-05-03); not yet battle-tested at scale, operate with a fast-response posture for the first weeks
how it differs

vs the existing Python schedulers

A factual capability matrix across the five most-used alternatives. All of them do their job well; the case for z4j-scheduler is the specific combination of engine-agnostic dispatch, live editing, and a tamper-evident audit chain in one service. If your stack runs one engine and one short schedule list, the existing native scheduler is probably the right call.

1

Engine & framework reach

Capability celery-beat django-celery-beat rq-scheduler APScheduler 4 system cron z4j-scheduler
Engines supported ~ Celery only ~ Celery only ~ RQ only ~ in-process only ~ shell exec All 6 (Celery, RQ, Dramatiq, Huey, arq, taskiq)
Framework agnostic Django only host OS Django / Flask / FastAPI / bare
Multiple engines, one process Celery + RQ + Huey side-by-side, one dashboard
2

Operations & observability

Capability celery-beat django-celery-beat rq-scheduler APScheduler 4 system cron z4j-scheduler
Edit live (no daemon restart) ~ Django admin only ~ persistent jobstore only Dashboard / declarative / REST
Built-in dashboard ~ Django admin (basic) Fire history, run-now, edit, audit log
Fire history per schedule ~ syslog only Buffered + acked + searchable
Audit log of schedule edits ~ 3rd-party django-auditlog HMAC-chained, tamper-evident
Manual fire-now button ~ API only Dashboard + REST
RBAC / project scoping ~ Django auth only ~ UNIX permissions Project + role-scoped (admin / operator / viewer)
3

Reliability & catch-up

Capability celery-beat django-celery-beat rq-scheduler APScheduler 4 system cron z4j-scheduler
HA / leader election single instance only single instance only single instance only ~ pluggable, manual single host Postgres advisory-lock leader, rolling-restart safe
Catch-up on outage ~ all-or-nothing ~ all-or-nothing ~ default fire-all ~ coalescing only missed = lost Per-schedule: skip / fire-one-missed / fire-all-missed
DST / IANA tz correctness ~ UTC default Validated at API; DST fall-back fold fixed
Solar (sunrise / sunset / dusk) Astronomical events with location
Replay past fires from dashboard One-click replay
4

Migration & lock-in

Capability celery-beat django-celery-beat rq-scheduler APScheduler 4 system cron z4j-scheduler
Importer FROM other schedulers - - - - - All 6 native schedulers + crontab
Exporter TO other schedulers - - - - - No lock-in (round-trip pinned by tests)
Coexist with native scheduler - - - - - z4j-celerybeat coexistence adapter
Declarative-in-source schedules beat_schedule dict DB-only ~ manual Python crontab file z4j_scheduler.declarative reconciler
5

Security

Capability celery-beat django-celery-beat rq-scheduler APScheduler 4 system cron z4j-scheduler
Wire-protocol HMAC + replay protection ~ broker-dependent ~ broker-dependent - HMAC-SHA256 + per-session seq+nonce binding
Tamper-evident audit chain Per-row HMAC, prev-hmac chain, DB-level UNIQUE
Zero-downtime secret rotation - - - - - Z4J_PREVIOUS_SECRETS multi-key window
6

License & project shape

Capability celery-beat django-celery-beat rq-scheduler APScheduler 4 system cron z4j-scheduler
License BSD-3 BSD-3 MIT MIT BSD Apache-2.0
Process model long-running daemon daemon + DB long-running daemon in-process or daemon init / systemd Standalone OR brain-embedded subprocess
Idle CPU footprint low low low low negligible Negligible (1s tick, semaphore-bounded)
supported ~ partial not supported - not applicable

Last verified 2026-04-28. We update this matrix when an upstream scheduler ships a meaningful capability change. The data is in sites/z4j-com/src/pages/schedulers/z4j-scheduler.astro (top of frontmatter, COMPARISON array). PRs welcome.

when teams pick z4j-scheduler

Use cases the existing schedulers do not cover

Multi-engine shop, one source of truth

You run Celery for legacy services, RQ for a Django app, and arq for a FastAPI service. Each has its own schedule daemon today, with three different config formats and no shared audit. z4j-scheduler is one service that drives all three from one dashboard, with one audit log of every schedule edit.

Compliance-sensitive scheduling

An auditor wants to know who paused the nightly billing job last Tuesday and when. celery-beat does not keep a record. django-celery-beat keeps a partial one if you wired up django-auditlog. z4j-scheduler records every edit (create, update, pause, delete) in an HMAC-chained log that is tamper-evident at the database row level.

Live editing without restarts

Teams running celery-beat in Kubernetes know the dance: edit the static beat_schedule dict, restart the pod, hope nothing else breaks. z4j-scheduler picks up edits within one tick (one second by default), no restart. The schedule lives in Postgres, the dashboard writes to it, the scheduler reads from it.

Migration in or out, no lock-in

Importer reads existing schedules from celery-beat (static dict or django-celery-beat), rq-scheduler, APScheduler jobstores, Huey periodic tasks, arq cron, taskiq schedule sources, or system crontab. Exporter writes them back. Round-trip integrity is pinned by tests; you can leave whenever you want.

None of these are theoretical. Each one started as an operator running into the same wall with celery-beat or a similar single-engine scheduler and asking us for a way out. If your shop hits the same shape, this is what z4j-scheduler is for.

how it works

Three things, decoupled

Most Python schedulers fuse three concerns into one process: where the schedule lives, when it fires, and what runs the task. z4j-scheduler keeps them separate.

  • Where the schedule lives: z4j's database. Edited from the dashboard, declarative config, or REST.
  • When it fires: the z4j-scheduler service ticks once per second, dispatches due fires over gRPC to brain.
  • What runs the task: your existing engine worker (Celery, RQ, arq, etc.) on its existing broker. z4j-scheduler does not run tasks itself.

The decoupling is what lets one z4j-scheduler instance manage schedules across a Celery project, an RQ project, and a Huey project from the same dashboard, with a single audit log of edits.

when to use it

Fit check

Greenfield, multiple engines, or multi-project: use z4j-scheduler. Define schedules from the dashboard or declarative config; one source of truth across the org.

Migrating from celery-beat / rq-scheduler / APScheduler: run the one-shot importer to copy existing schedules into z4j, verify in the dashboard, then disable the native scheduler. Or run both during a migration window using the coexistence adapter (z4j-celerybeat).

One engine, short schedule list, no team scaling concern: the existing native scheduler is probably fine. z4j-scheduler adds a process to your stack; that's only worth it if you'd benefit from the dashboard, audit chain, or cross-engine surface.

install

Two deploy modes

Embedded mode

For single-container homelab / small-deploy setups, z4j spawns z4j-scheduler as a supervised subprocess in its own lifespan and auto-mints loopback mTLS at boot. No extra ops surface.

Z4J_EMBEDDED_SCHEDULER=true
Z4J_SCHEDULER_GRPC_ENABLED=true

Standalone mode

For production, run z4j-scheduler as a separate process or container. Multiple instances elect a leader via Postgres advisory lock; only the leader ticks. Followers stay warm.

pip install z4j-scheduler

z4j-scheduler serve \
  --brain-grpc-url brain.internal:7701 \
  --brain-rest-url https://brain.internal \
  --tls-cert /etc/z4j/scheduler.crt \
  --tls-key /etc/z4j/scheduler.key \
  --tls-ca /etc/z4j/ca.crt
frequently asked

z4j-scheduler: FAQ

How is z4j-scheduler different from celery-beat?

celery-beat fires Celery tasks only and reads its schedule list from a Python dict, a Django database, or a redbeat backend that is fixed at start time. z4j-scheduler fires tasks on any of six engines (Celery, RQ, Dramatiq, Huey, arq, taskiq), keeps the schedule list in z4j's database, and lets you edit live from a dashboard or REST API without a daemon restart. The two are not direct substitutes; we ship z4j-celerybeat as a coexistence adapter so you can run them side by side during a migration window.

Is z4j-scheduler a Celery Beat alternative?

It can replace celery-beat for teams that want live editing from a dashboard, a tamper-evident audit log of schedule changes, multi-engine support, or HA leader election. The one-shot importer copies your existing celery-beat schedule (static dict or django-celery-beat rows) into z4j; you verify in the dashboard, then disable celery-beat. If your stack is pure Celery and a single daemon meets your needs, celery-beat is still a fine choice.

Does z4j-scheduler replace APScheduler?

Different niches. APScheduler runs in-process and is excellent for scheduling functions inside one Python application. z4j-scheduler is a separate companion service that dispatches to engine workers running elsewhere, with a dashboard, audit log, and cross-engine surface. We also ship z4j-apscheduler if you want to keep APScheduler and just surface its schedules in the z4j dashboard.

Is z4j-scheduler production-ready?

z4j-scheduler 1.4.0 (May 2026) is the current release. The protocol, audit chain, and HA leader election have unit and integration tests covering every code path. It is not yet battle-tested at large scale; the honest posture for the first weeks is fast-response, frequent-release, and a friendly issue tracker. Operate with that in mind, especially for high-frequency schedule edits.

How does HA work?

Multiple z4j-scheduler instances connect to the same Postgres database and race for an advisory lock. Only the lock holder ticks; followers stay warm. If the leader's connection drops, a follower acquires the lock within seconds and starts ticking. Rolling restarts are safe; missed ticks during the handover are caught by the per-schedule catch-up policy (skip / fire-one-missed / fire-all-missed).

Can I run z4j-scheduler embedded inside z4j?

Yes. Embedded mode runs z4j-scheduler as a supervised subprocess inside z4j container, with auto-minted loopback mTLS at boot. This is the right pick for homelab and small-team deploys where adding another service is not worth the operational tax. Standalone mode is recommended once you scale past a single brain instance.

Will I get locked into z4j if I switch?

z4j-scheduler ships an exporter for every native scheduler, with round-trip integrity pinned by tests. If you decide to leave, you export your schedules back to celery-beat / rq-scheduler / APScheduler / native-cron format and disable z4j-scheduler. The schedule definitions are yours, not ours.

What licence is z4j-scheduler under?

Apache 2.0. Importing z4j-scheduler into a commercial project does not affect your application's licensing. z4j (server + dashboard + API) is AGPL v3, isolated in its own process; the scheduler is separate.

Does z4j-scheduler handle DST and timezone correctness?

Yes. Cron and interval triggers are validated against IANA time zones at the API boundary, the DST fall-back fold is fixed (no double-fires during the ambiguous hour), and per-schedule catch-up policies handle the spring-forward gap explicitly. The test suite includes property-based tests for every transition in every IANA zone z4j supports.

More schedulers

See also