A self-hosted cron-as-a-service for backend teams that need retries, HMAC signing, failover, metrics, and a simple HTTP API without another SaaS dependency.
POST a job. Get webhooks on schedule. That's it.
Why developers use CronLite
Move recurring backend work out of hidden server crontabs and into an API-driven scheduler with visible executions.
POST a cron expression, target URL, headers, and body. CronLite handles the schedule and delivery loop.
Retries, orphan recovery, leader election, and circuit breakers are built into the runtime.
Prometheus metrics and execution history make scheduled work observable before it wakes someone up.
How It Works
Send a cron expression and webhook URL to the REST API. Get back a job ID.
POST /v1/jobsCronLite evaluates your cron expression and creates executions at the right time.
0 9 * * * → "Every day at 9am"Your endpoint receives an HMAC-signed POST with the payload you configured.
POST https://your-app.com/hookFeatures
Everything you need to run reliable scheduled webhooks at scale. No SaaS vendor lock-in.
POST a job with a cron expression and webhook URL. That's the entire integration.
Every webhook is cryptographically signed. Verify authenticity with a single hash check.
4 attempts with exponential backoff. Immediate, 30s, 2m, 10m. No config required.
Per-URL circuit breaker protects your downstream services from retry storms.
Horizontal scaling via Postgres advisory locks. Leader election with sub-3s failover.
Native MCP support with 10 tools. Let Claude schedule, pause, and manage your cron jobs.
If you're building with Claude, custom agents, or any MCP client, CronLite ships 10 native tools. Your agent can create, pause, trigger, and manage cron jobs through conversation. No curl. No dashboard. Just tell it what you need.
I need a nightly job that syncs our Stripe invoices to the data warehouse, and a weekly cleanup that purges stale sessions every Sunday at 4am
"every day at 1am"
"stripe-invoice-sync", cron: "0 1 * * *", url: "https://api.internal/sync"
"every sunday at 4am"
"session-cleanup", cron: "0 4 * * 0", url: "https://api.internal/purge"
Both jobs are live. stripe-invoice-sync runs nightly at 0 1 * * *, session-cleanup fires every Sunday at 0 4 * * 0. Both have HMAC signing enabled. You can say "pause stripe sync" or "trigger cleanup now" anytime.
Pause the stripe sync — we're migrating the warehouse this week
id: "7f3a.."
Done. Stripe sync is paused. The cleanup job is unaffected and will still run Sunday. Say "resume stripe sync" when the migration is done.
Full CRUD + scheduling ops. No wrapper SDK needed.
create-jobCreate a new scheduled cron joblist-jobsList all jobs with optional filtersget-jobGet job details + recent executionsupdate-jobPartially update job configdelete-jobPermanently delete a jobpause-jobPause scheduling without deletingresume-jobResume a paused jobtrigger-jobFire a job immediately, off-schedulenext-runPreview the next 5 scheduled runsresolve-scheduleNatural language → cron expressionDrop this into your MCP config. Your agent gets full control over scheduling, pausing, triggering, and monitoring jobs.
Claude Desktop / Claude Code
Stdio transport via MCP proxy
{
"mcpServers": {
"cronlite": {
"command": "./cronlite-mcp",
"env": {
"CRONLITE_URL": "http://localhost:8080",
"CRONLITE_API_KEY": "cl_live_..."
}
}
}
}Any MCP Client
Streamable HTTP \u2014 embedded on every instance
# MCP endpoint is built into CronLite # No extra binary needed Endpoint: http://localhost:8080/mcp Transport: Streamable HTTP (SSE) Auth: Authorization: Bearer cl_live_... # Namespace-scoped — each key sees # only its own jobs. Same auth as REST.
API
No SDK to install. No queue to configure. Just HTTP.
Request
$ curl -X POST http://localhost:8080/v1/jobs \
-H "Authorization: Bearer cl_live_abc123" \
-H "Content-Type: application/json" \
-d '{
"name": "daily-sync",
"cron": "0 9 * * *",
"url": "https://api.example.com/sync",
"headers": {"X-Source": "cronlite"},
"body": {"action": "full_sync"}
}'Response
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"name": "daily-sync",
"cron": "0 9 * * *",
"status": "active",
"next_run": "2026-04-07T09:00:00Z",
"created_at": "2026-04-06T14:30:00Z"
}Under the Hood
Not a wrapper around crontab. CronLite is a distributed scheduler with leader election, crash recovery, circuit breaking, and lock-free parallel dispatch. All backed by Postgres.
Postgres advisory locks. One leader schedules. If it dies, another takes over in <3 seconds. No ZooKeeper, no etcd.
pg_try_advisory_lock · automatic failover
If a worker crashes mid-delivery, the execution doesn't vanish. The reconciler detects stalled jobs and re-queues them automatically.
Per-URL circuit breaker. If your endpoint is down, CronLite stops hammering it. When it recovers, traffic resumes. Protects you from retry storms.
Workers poll Postgres with SELECT ... FOR UPDATE SKIP LOCKED. No row contention. Multiple instances claim different jobs simultaneously.
zero contention · horizontal scale
Security
Security isn't a feature flag you turn on. Every layer of CronLite, from token storage to webhook delivery, is hardened by default.
Every webhook delivery is signed with your secret. Constant-time comparison via hmac.Equal() prevents timing attacks. Receivers verify with a single hash check.
X-CronLite-Signature: sha256=...Webhook URLs targeting private networks (RFC 1918), loopback, link-local, and cloud metadata endpoints are rejected when you create the job — not at delivery time.
10.0.0.0/8, 172.16.0.0/12, 169.254.x, metadata.google.internalPer-IP token bucket before auth (10 req/s default). Per-namespace token bucket after auth (100 req/s default). Abuse stopped at both layers independently.
IP → Auth → Namespace → HandlerAPI keys stored as SHA-256 hashes — plaintext shown once at creation, never persisted. Constant-time comparison via crypto/subtle prevents timing-based extraction.
cl_ + 64 hex chars → sha256 → storedHTTP client enforces 10s TLS handshake timeout, 30s response header timeout, and per-request context deadlines. Connection pool limits prevent resource exhaustion.
ResponseHeaderTimeout: 30s, MaxIdleConns: 100Database errors, constraint names, and query fragments are never exposed in API responses. Every unknown error returns a generic 500. No information leakage.
internal_error: "internal server error"Every API key is scoped to a namespace. Jobs, executions, and tags are completely isolated. One tenant cannot see or affect another's data, ever.
namespace injected at auth → enforced at queryDATABASE_URL passwords and REDIS_ADDR credentials are masked in config output. Startup warns loudly if sslmode=disable. Safe for logging, safe for debugging.
postgres://*** · WARNING: sslmode=disableNot Your Typical Cron
Most cron tools stop at "fire a webhook." CronLite sweats the hard problems. The ones that wake you up at 3am when you skip them.
Webhooks aren't fire-and-forget. Downstream services acknowledge delivery. Query unacknowledged executions. Know exactly which results haven't been processed yet.
POST /executions/{id}/ackTag jobs with key-value pairs. Filter by tag with AND semantics. Organize by team, environment, service — query exactly what you need.
?tag=env:prod&tag=team:billingPer-IP limits applied before auth (10 req/s). Per-namespace limits applied after auth (100 req/s). Stops abuse at both layers. Most tools do one or the other.
IP layer → Auth → Namespace layerFull IANA timezone support. Cron expressions evaluate in the job’s timezone, not UTC. “Every weekday at 9am New York” actually means 9am New York.
timezone: "America/New_York"Webhook URLs targeting private networks, loopback, link-local, or cloud metadata services are rejected at creation time. Security-first, not bolted on after a breach.
Blocks RFC1918, 169.254.x, metadata.google.internalOrdered sequence: scheduler stops → reconciler stops → dispatcher drains in-flight (30s) → HTTP server drains (10s). Zero dropped executions during deploys.
SIGTERM → ordered drain → clean exitChannel mode: in-memory event bus, lowest latency, single instance. DB mode: Postgres polling with SKIP LOCKED, multi-instance HA. Pick the right trade-off for your stage.
DISPATCH_MODE=channel | dbOptional Redis integration for execution count timeseries. Per-job retention (0–7 days). Minute-bucket granularity. Best-effort — never blocks the scheduler.
analytics.retention_seconds: 86400Observability
CronLite exports Prometheus metrics out of the box. Every execution, delivery, retry, circuit breaker state change, and leader election. All observable. Plug into Grafana, Datadog, or any Prometheus-compatible stack.
cronlite_executions_totalcounterTotal executions created, by job and statuscronlite_executions_totalcounterTotal executions created, by job and status
cronlite_deliveries_totalcounterWebhook delivery attempts, by status codecronlite_deliveries_totalcounterWebhook delivery attempts, by status code
cronlite_delivery_duration_secondshistogramWebhook response time distributioncronlite_delivery_duration_secondshistogramWebhook response time distribution
cronlite_retries_totalcounterRetry attempts triggered, by jobcronlite_retries_totalcounterRetry attempts triggered, by job
cronlite_circuit_breaker_stategaugeCircuit breaker state per URL (0=closed, 1=open)cronlite_circuit_breaker_stategaugeCircuit breaker state per URL (0=closed, 1=open)
cronlite_scheduler_lag_secondsgaugeDelay between scheduled time and actual dispatchcronlite_scheduler_lag_secondsgaugeDelay between scheduled time and actual dispatch
cronlite_reconciler_recovered_totalcounterOrphaned executions recovered by reconcilercronlite_reconciler_recovered_totalcounterOrphaned executions recovered by reconciler
cronlite_leader_is_leadergaugeWhether this instance holds the leader lockcronlite_leader_is_leadergaugeWhether this instance holds the leader lock
Drop these into your Prometheus alertmanager.
rate(cronlite_deliveries_total{status!="200"}[5m]) > 0.1cronlite_scheduler_lag_seconds > 30sum(cronlite_leader_is_leader) == 0cronlite_circuit_breaker_state == 1Quick Start
Docker, PostgreSQL, and three commands. That's it.
CronLite is free, open-source, and runs anywhere Docker does. One binary, one Postgres, and you have a production-grade scheduler that handles retries, failover, and crash recovery.