CronLite runs reliable scheduled webhooks from your own infrastructure.
A self-hosted cron-as-a-service for backend teams that need retries, HMAC signing, failover, metrics, and a built-in Admin UI without another SaaS dependency.
Create jobs through REST, manage them in Admin, or let your MCP agent handle the schedule.
Why developers use CronLite
Built for backend work that has to run.
Replace fragile crontabs
Move recurring backend work out of hidden server crontabs and into an API-driven scheduler with visible executions.
Schedule webhooks with one API call
POST a cron expression, timezone, target URL, and signing secret. CronLite handles the schedule and delivery loop.
Recover from crashes and retries automatically
Retries, orphan recovery, leader election, and circuit breakers are built into the runtime.
Inspect metrics and executions
Prometheus metrics and execution history make scheduled work observable before it wakes someone up.
How It Works
Three steps. Zero complexity.
POST a job
Send a cron expression and webhook URL to the REST API. Get back a job ID.
POST /jobsScheduler fires
CronLite evaluates your cron expression and creates executions at the right time.
0 9 * * * → "Every day at 9am"Webhook delivered
Your endpoint receives an HMAC-signed POST identifying the job and execution.
POST https://your-app.com/hookFeatures
Production-grade from day one
Everything you need to run reliable scheduled webhooks at scale. No SaaS vendor lock-in.
Simple REST API
POST a job with a cron expression and webhook URL. That's the entire integration.
HMAC-Signed Delivery
Every webhook is cryptographically signed. Verify authenticity with a single hash check.
Smart Retries
4 attempts with exponential backoff. Immediate, 30s, 2m, 10m. No config required.
Circuit Breaker
Per-URL circuit breaker protects your downstream services from retry storms.
High Availability
Horizontal scaling via Postgres advisory locks. Leader election and recovery after restart or failover.
AI-Agent Ready
Native MCP support with 10 tools. Let Claude schedule, pause, and manage your cron jobs.
Built-in Admin UI
Your jobs.
A clearer view.
Search your fleet, read schedules in plain language, and follow every delivery. The Job Control Center brings everyday operations into the same service you already run.
WORKSPACE / DEFAULT
Job Control Center
4
Total jobs
3
Active
1
Paused
| Job | State | Schedule | Timezone |
|---|---|---|---|
| invoice-syncteam=billing | Active | Every 5 minutes | UTC |
| daily-digestteam=product | Active | At 09:00 on Monday–Friday | America/New_York |
| session-cleanupteam=platform | Paused | At 04:00 on Sunday | UTC |
| inventory-refreshteam=commerce | Active | Every 15 minutes | UTC |
4 of 4 example jobs match · explore the views or search by name
Find your focus
Live name search, tag and state filters, numbered pages, and saved views for your API key and namespace.
Review, then act
Preview bulk pause and resume before confirming. Changed jobs are skipped, and per-job results stay in the audit history.
Follow the evidence
Inspect persisted execution states and delivery attempts. Acknowledge terminal executions from the Runs and history screens.
Manage your workspace
Create and revoke API keys, preview schedules, and inspect effective settings. Existing namespace data appears when you sign in.
Optional. Embedded in Go. Core workflows work without JavaScript.
Enable the Admin UILet your agent
handle the recurring work
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. Use conversation for agent workflows and the Admin UI when you want a visual view.
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_expression: "0 1 * * *", timezone: "UTC", webhook_url: "https://api.example.com/sync", webhook_secret: "…"
"every sunday at 4am"
"session-cleanup", cron_expression: "0 4 * * 0", timezone: "UTC", webhook_url: "https://api.example.com/purge", webhook_secret: "…"
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.
10 MCP Tools
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 expressionWire it up in 30 seconds
Drop 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": "ec_..."
}
}
}
}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 Auth: Authorization: Bearer ec_... # Namespace-scoped — each key sees # only its own jobs. Same auth as REST.
API
Developer-first by design
No SDK to install. No queue to configure. Just HTTP.
Request
$ curl -X POST http://localhost:8080/jobs \
-H "Authorization: Bearer $CRONLITE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "daily-sync",
"cron_expression": "0 9 * * *",
"timezone": "UTC",
"webhook_url": "https://api.example.com/sync",
"webhook_secret": "replace-with-your-signing-secret",
"tags": {"team": "billing"}
}'Response
{
"id": "a1b2c3d4-e5f6-4890-abcd-ef1234567890",
"namespace": "default",
"name": "daily-sync",
"enabled": true,
"cron_expression": "0 9 * * *",
"timezone": "UTC",
"webhook_url": "https://api.example.com/sync",
"tags": {"team": "billing"},
"created_at": "2026-10-12T14:30:00Z",
"updated_at": "2026-10-12T14:30:00Z"
}Sustained-load tested
50,000
executions in a sustained-load test.
Built to
keep going.
Recurring work needs more than a happy path. CronLite's dispatcher was exercised through wave after wave of executions, repeated failures, and retries—with concurrency kept in check.
See the sustained-load test- Repeated failures & retries
- Bounded concurrency
- Cancellation tested
Under the Hood
Built to recover.
Not a wrapper around crontab. CronLite is a distributed scheduler with leader election, crash recovery, circuit breaking, and parallel dispatch with row locking. All backed by Postgres.
Leader Election
Postgres advisory locks. One leader schedules. If it dies, another takes over once Postgres releases the lock. Timing depends on connection detection and retry settings. No ZooKeeper, no etcd.
pg_try_advisory_lock · automatic failover
Orphan Reconciler
If a worker crashes mid-delivery, the execution doesn't vanish. The reconciler detects stalled jobs and re-queues them automatically.
Circuit Breaker
Per-URL circuit breaker. If your endpoint is down, CronLite stops hammering it. When it recovers, traffic resumes. Protects you from retry storms.
Parallel Dispatch
Workers claim bounded batches from Postgres with SELECT ... FOR UPDATE SKIP LOCKED. Locked rows are skipped. Concurrent delivery slots keep slow receivers separate from polling.
skip locked rows · horizontal scale
At-least-once delivery. A stable ID for deduplication.
Restart catch-up and orphan recovery help scheduled work survive interruptions. Your receiver should deduplicate with X-CronLite-Execution-ID; retries and recovery can still deliver more than once.
On current main: renewable PostgreSQL leases and generation fencing strengthen execution ownership in both dispatch modes. Upgrading to these changes requires draining old writers and applying migration 011.
Read the ownership and upgrade notes ↗Security
Defense in depth.
Not an afterthought.
Security isn't a feature flag you turn on. Every layer of CronLite, from token storage to webhook delivery, is hardened by default.
HMAC-SHA256 Webhook Signing
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: <hmac-sha256-hex>SSRF Blocked at Creation
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.internalTwo-Layer Rate Limiting
Per-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 → HandlerSHA-256 Token Hashing
API keys stored as SHA-256 hashes — plaintext shown once at creation, never persisted. Constant-time comparison via crypto/subtle prevents timing-based extraction.
ec_ + 64 hex chars → sha256 → storedSlow Loris Protection
HTTP client enforces 10s TLS handshake timeout, 30s response header timeout, and per-request context deadlines. Connection pool limits prevent resource exhaustion.
ResponseHeaderTimeout: 30s, MaxIdleConns: 100Error Sanitization
Database 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"Namespace Isolation
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 queryCredential Masking & SSL Warnings
DATABASE_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
The details that make it
production-grade.
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.
Execution ACKs
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}/ackJob Tags
Tag 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:billingTwo-Layer Rate Limiting
Per-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 layerTimezone-Aware Scheduling
Full 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"SSRF Protection Built-In
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.internalGraceful Shutdown Choreography
Coordinated shutdown drains in-flight work within configured deadlines; stale executions can be recovered.
SIGTERM → ordered drain → clean exitDual Dispatch Modes
Channel 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 | dbPer-Job Analytics
Optional 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
Know exactly what's happening.
Always.
CronLite exports Prometheus metrics out of the box. Scheduler activity, delivery attempts, retries, in-flight executions, and leader election. All observable. Plug into Grafana, Datadog, or any Prometheus-compatible stack.
cronlite_scheduler_jobs_triggered_totalcounterScheduled executions createdcronlite_scheduler_jobs_triggered_totalcounterScheduled executions created
cronlite_dispatcher_delivery_attempts_totalcounterDelivery attempts, by attempt number and status classcronlite_dispatcher_delivery_attempts_totalcounterDelivery attempts, by attempt number and status class
cronlite_dispatcher_webhook_duration_secondshistogramWebhook request latency, excluding retry backoffcronlite_dispatcher_webhook_duration_secondshistogramWebhook request latency, excluding retry backoff
cronlite_dispatcher_retry_attempts_totalcounterRetry attempts, excluding the initial attemptcronlite_dispatcher_retry_attempts_totalcounterRetry attempts, excluding the initial attempt
cronlite_dispatcher_events_in_flightgaugeExecutions currently being processedcronlite_dispatcher_events_in_flightgaugeExecutions currently being processed
cronlite_scheduler_tick_drift_secondshistogramDifference between expected and actual scheduler tickscronlite_scheduler_tick_drift_secondshistogramDifference between expected and actual scheduler ticks
cronlite_orphaned_executionsgaugeOrphaned executions observed by the reconcilercronlite_orphaned_executionsgaugeOrphaned executions observed by the reconciler
cronlite_leader_is_leadergaugeWhether this instance holds the leader lockcronlite_leader_is_leadergaugeWhether this instance holds the leader lock
Suggested Alerts
Example Prometheus rule expressions. Tune thresholds for your workload.
rate(cronlite_dispatcher_delivery_outcomes_total{outcome="failed"}[5m]) > 0.1increase(cronlite_scheduler_tick_errors_total[5m]) > 0sum(cronlite_leader_is_leader) == 0cronlite_orphaned_executions > 0Quick Start
Start with Docker Compose
Start the service, create a key, and register your first scheduled webhook.
Your team has better things to build
than a cron system.
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.