- Documentation
- Cron Workers
- Creating a Cron Worker
Creating a Cron Worker
You can create a cron worker from any project’s dashboard. Open the project, click Create new, and choose Cron Worker. The wizard takes you through three short steps.
The wizard
-
Schedule and timezone. Give the worker a name (1–100 characters), enter a cron expression, and pick the timezone the schedule should run in.
-
Destination. Pick one of the six destination types and fill in its specific configuration.
-
Payload, timeout, and retries. Provide an optional JSON payload, set the per-attempt timeout in seconds, and choose how many retries failed attempts should get.
After you click Create, the worker is active immediately and the first scheduled fire happens on the next slot.
Step 1 — Schedule and timezone
The schedule field accepts standard cron syntax — five fields (minute hour day-of-month month day-of-week). The wizard validates the expression as you type and shows the next scheduled fire below the field.
| Example | Meaning |
|---|---|
0 * * * * | Every hour, on the hour |
*/15 * * * * | Every 15 minutes |
0 9 * * * | Every day at 9:00 |
0 9 * * 1-5 | Weekdays at 9:00 |
30 3 1 * * | First day of every month at 3:30 |
0 0 * * 0 | Sundays at midnight |
The timezone is an IANA database identifier (e.g., America/Sao_Paulo, UTC, Europe/Lisbon). The wizard validates the value and suggests a correction for common typos like Sao_Paulo.
Step 2 — Destination
Pick one of the six tiles. Each destination has its own configuration — see Destination types for the full reference.
Step 3 — Payload, timeout, and retries
Payload
The payload is an optional JSON object sent verbatim on every fire. Empty ({}) is allowed.
{
"job": "rotate-uploads",
"max_age_hours": 24,
"dry_run": false
}
Limits:
- Must be a JSON object (not an array, not a primitive).
- Maximum size: 64 KiB serialized.
- The wizard previews the byte count as you type and warns when you approach the cap.
Timeout
The maximum time a single attempt can run before being considered failed. Range: 5–300 seconds, default 30 seconds. The timeout applies to each individual attempt — retries each get the full window.
Pick a value that comfortably exceeds your destination’s typical processing time. If your HTTP endpoint usually replies in 2 seconds, a 30-second timeout leaves headroom for the occasional slow run without letting truly stuck requests hold the slot.
Max retries
How many times to retry a failed attempt before giving up. Range: 1–5, default 3. Retries use exponential backoff with jitter to avoid synchronizing with other clients.
After creation
You land on the worker detail page with:
- A status badge (
active,suspended, ordeleted). - A live countdown to the next scheduled fire.
- A summary of the last run.
- The full run history table (newest first, paginated).
- Action buttons: Manual trigger, Pause / Resume, Edit destination, Delete.
See Monitoring and runs for what each of these does and how to investigate failures.
From the CLI
The guara crons create -f <file> command accepts a YAML or JSON spec file with the same shape as the API request body. Pass -f spec.yaml (or .json) and the CLI validates locally then submits — any 400 response with field-level Zod issues is pretty-printed with the offending key bolded.
guara crons create -f cron.yaml
guara crons create -f cron.yaml --project my-project --json
guara crons update hourly -f patch.yaml
The destination_ref is a discriminated union — its required fields depend on destination_type. The serviceEndpointId (HTTP) and catalogServiceId (everything else) are UUIDs you can find via guara services list --json and guara services info --json for the target service.
HTTP
name: Hourly cleanup job
schedule: '0 * * * *'
timezone: UTC
destination_type: http
destination_ref:
type: http
serviceEndpointId: 1d6e9a0a-2c7b-4a1c-b8a9-2f6d4f7b6e10
path: /jobs/cleanup-stale-uploads
headers:
X-Job-Source: cron
X-Tenant: acme
payload_template:
job: rotate-uploads
max_age_hours: 24
dry_run: false
timeout_seconds: 30
max_retries: 3
The cron worker always sends POST with the JSON payload as the body and Content-Type: application/json. The platform sets Traceparent and Tracestate automatically; reserved headers (Authorization, Cookie, Host, etc.) cannot be overridden — see Destination types → HTTP.
NATS
name: Daily digest publisher
schedule: '0 7 * * *'
timezone: America/Sao_Paulo
destination_type: nats
destination_ref:
type: nats
catalogServiceId: 9b2f0a5e-3d4c-4f8b-a0c1-7e6d5b4a3c20
subject: jobs.daily-digest
payload_template:
template: monthly-summary
audience: all-users
timeout_seconds: 30
max_retries: 3
Redis
name: Cache warm-up
schedule: '*/15 * * * *'
timezone: UTC
destination_type: redis
destination_ref:
type: redis
catalogServiceId: 5a8d7c6b-1e2f-4a3d-9c8b-7f6e5d4c3b21
channel: cache:warmup
payload_template:
keys:
- homepage
- pricing
- docs-index
timeout_seconds: 15
max_retries: 2
Valkey
name: Session sweep
schedule: '*/10 * * * *'
timezone: UTC
destination_type: valkey
destination_ref:
type: valkey
catalogServiceId: 7c9b1a2d-4e5f-6a7b-8c9d-0e1f2a3b4c52
channel: sessions:sweep
payload_template:
before_age_minutes: 60
timeout_seconds: 15
max_retries: 2
RabbitMQ
name: Reports kick-off
schedule: '0 4 * * *'
timezone: UTC
destination_type: rabbitmq
destination_ref:
type: rabbitmq
catalogServiceId: 4d8e1c2b-3a5f-4b6c-9d8e-1f2a3b4c5d63
exchange: reports
routingKey: reports.daily
requireRoutable: true
payload_template:
report: daily-revenue
format: parquet
timeout_seconds: 60
max_retries: 3
Set requireRoutable: true to fail the run loudly when no queue is bound to the exchange/routing key. Omit it (or set false) for fire-and-forget fan-out.
Postgres NOTIFY
name: Job queue ping
schedule: '*/5 * * * *'
timezone: UTC
destination_type: postgres
destination_ref:
type: postgres
catalogServiceId: 2e6f8a1b-9c0d-4e1f-8a2b-3c4d5e6f7a84
channel: jobs_pending
payload_template:
hint: scan-now
timeout_seconds: 10
max_retries: 1
The platform wraps the JSON payload inside a small _meta envelope with the run ID and a W3C traceparent before issuing NOTIFY — listeners receive that envelope as the payload string. See Destination types → Postgres NOTIFY for the exact wrapper shape and the 8 KB Postgres limit.