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 simple HTTP API without another SaaS dependency.

POST a job. Get webhooks on schedule. That's it.

Self-hosted
Run CronLite inside your own infrastructure instead of adding another scheduler SaaS.
Go + PostgreSQL
One deployable service backed by the database your backend team already knows.
HMAC-signed webhooks
Every delivery can be verified by your endpoint before it does real work.
MCP tools for agents
Let Claude and other MCP clients create, pause, trigger, and inspect jobs.

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, target URL, headers, and body. 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.

01

POST a job

Send a cron expression and webhook URL to the REST API. Get back a job ID.

POST /v1/jobs
02

Scheduler fires

CronLite evaluates your cron expression and creates executions at the right time.

0 9 * * * → "Every day at 9am"
03

Webhook delivered

Your endpoint receives an HMAC-signed POST with the payload you configured.

POST https://your-app.com/hook

Features

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 with sub-3s failover.

AI-Agent Ready

Native MCP support with 10 tools. Let Claude schedule, pause, and manage your cron jobs.

MCP-native · Model Context Protocol

Let 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. No curl. No dashboard. Just tell it what you need.

Claude · CronLite MCP10 tools

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

resolve-schedule· tool call

"every day at 1am"

0 1 * * * → "Every day at 1:00 AM"
create-job· tool call

"stripe-invoice-sync", cron: "0 1 * * *", url: "https://api.internal/sync"

Job created · id: 7f3a.. · next: tonight 1:00 AM
resolve-schedule· tool call

"every sunday at 4am"

0 4 * * 0 → "Every Sunday at 4:00 AM"
create-job· tool call

"session-cleanup", cron: "0 4 * * 0", url: "https://api.internal/purge"

Job created · id: a2c9.. · next: Sunday 4:00 AM

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

pause-job· tool call

id: "7f3a.."

Job paused · won't fire until resumed

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 job
list-jobsList all jobs with optional filters
get-jobGet job details + recent executions
update-jobPartially update job config
delete-jobPermanently delete a job
pause-jobPause scheduling without deleting
resume-jobResume a paused job
trigger-jobFire a job immediately, off-schedule
next-runPreview the next 5 scheduled runs
resolve-scheduleNatural language → cron expression

Wire 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

claude_desktop_config.json
{
  "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-config
# 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

Developer-first by design

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

Built to not fail.

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.

Leader Election

Postgres advisory locks. One leader schedules. If it dies, another takes over in <3 seconds. No ZooKeeper, no etcd.

LEADERInstance 1
LEADERInstance 2
Instance 3

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.

+
Execution created
Worker claims it
Worker crashes
?
Execution orphaned
Reconciler detects
Re-queued & delivered

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.

CLOSED
Requests flow
TRIP
Failures detected
OPEN
Blocked
RECOVER
Test & resume
200 OK 5xx / timeout blocked

Parallel Dispatch

Workers poll Postgres with SELECT ... FOR UPDATE SKIP LOCKED. No row contention. Multiple instances claim different jobs simultaneously.

Worker A
invoice-sync
email-digest
Worker B
db-backup
cache-warm
Worker C
analytics-roll
cert-renew

zero contention · horizontal scale

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: sha256=...

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.internal

Two-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 → Handler

SHA-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.

cl_ + 64 hex chars → sha256 → stored

Slow 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: 100

Error 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 query

Credential 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=disable

Not 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}/ack

Job 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:billing

Two-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 layer

Timezone-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.internal

Graceful Shutdown Choreography

Ordered sequence: scheduler stops → reconciler stops → dispatcher drains in-flight (30s) → HTTP server drains (10s). Zero dropped executions during deploys.

SIGTERM → ordered drain → clean exit

Dual 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 | db

Per-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: 86400

Observability

Know exactly what's happening.
Always.

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_totalcounter

Total executions created, by job and status

cronlite_deliveries_totalcounter

Webhook delivery attempts, by status code

cronlite_delivery_duration_secondshistogram

Webhook response time distribution

cronlite_retries_totalcounter

Retry attempts triggered, by job

cronlite_circuit_breaker_stategauge

Circuit breaker state per URL (0=closed, 1=open)

cronlite_scheduler_lag_secondsgauge

Delay between scheduled time and actual dispatch

cronlite_reconciler_recovered_totalcounter

Orphaned executions recovered by reconciler

cronlite_leader_is_leadergauge

Whether this instance holds the leader lock

Suggested Alerts

Drop these into your Prometheus alertmanager.

DeliveryFailureRatewarning
rate(cronlite_deliveries_total{status!="200"}[5m]) > 0.1
SchedulerLagHighcritical
cronlite_scheduler_lag_seconds > 30
LeaderLostcritical
sum(cronlite_leader_is_leader) == 0
CircuitBreakerOpenwarning
cronlite_circuit_breaker_state == 1

Quick Start

Running in under a minute

Docker, PostgreSQL, and three commands. That's it.

$ git clone https://github.com/djlord-it/cronlite
$ cd cronlite && docker compose up -d

# Bootstrap your first API key
$ curl -X POST localhost:8080/v1/bootstrap

# Create your first job
$ curl -X POST localhost:8080/v1/jobs \
-H "Authorization: Bearer <your-key>" \
-d '{"name":"ping","cron":"*/5 * * * *","url":"https://httpbin.org/post"}'

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.