01 · Quickstart
Your first job in five minutes
You need a publicly reachable HTTP or HTTPS URL. CronEngine runs independently of your browser, so your computer can be off after the job is saved.
-
1
Create the job
Open Dashboard → New cron job. Give it a useful name and paste the endpoint URL.
-
2
Choose the request
Select GET for a simple trigger, or POST/PUT/PATCH when you need a raw request body. Add authentication or headers if required.
-
3
Set the schedule
Use the visual builder, a five-field expression such as 0 8 * * 1-5, or a macro such as @daily. The schedule uses your account timezone.
-
4
Test and activate
Use Test request to verify connectivity and success rules. Save the job as active, then inspect Run history after it fires.
Good first test: schedule a GET endpoint with @hourly, leave the default success rule and overlap protection enabled, and store responses only on failure.
02 · Feature guide
What you get after creating an account
These product tools are available on every plan, including Free. Plans change capacity, history retention, timeout length, channel count, and how frequently a schedule can run.
Build and control
Create jobs without guesswork
-
Visual schedule builder and raw cron
Use guided fields, macros, or a five-field expression with live validation and an immediate reserved-run estimate. -
Test request and manual runs
Check the current form before saving, or trigger an existing job from the dashboard without waiting for its schedule. -
Pause, resume, edit, and delete
Temporarily stop a job without losing its request, schedule, organization, or alert settings. -
Import and export
Fill the form from a cURL command, a CronEngine JSON export, or a crontab. Copy or download a JSON snapshot for backups and moving job settings.
Monitor and debug
See what happened and what comes next
-
Operational dashboard
Scan active and paused jobs, next runs, last outcomes, HTTP status, and scheduled-run usage. -
Timezone-aware Agenda
Browse upcoming executions by local day and time, with an option to include paused jobs. -
Detailed run history
Inspect success, status, duration, error explanations, and saved response content in pretty or raw form. -
Execution charts and success rate
Compare duration across recent runs and spot failures or performance changes visually.
Organize and operate
Keep a growing job list manageable
-
Nested maps
Group jobs by environment, project, client, or workflow, with root maps and one level of sub-maps. -
Colored tags
Label jobs by priority, owner, environment, or task type for faster scanning. -
Bulk actions
Select jobs to pause or delete together, and bulk-manage maps and tags. -
Fast dashboard actions
Copy an endpoint URL, open history, edit, test, pause, resume, or delete from the job row.
03 · Schedules
Cron syntax and local time
CronEngine accepts a five-field expression. Names are not accepted in jobs created in the UI; use the numeric ranges below. Imported crontabs may normalize month and weekday names, and Sunday is stored as 0.
| Field | Allowed | Examples |
|---|---|---|
| Minute | 0-59 | */5 every 5 minutes |
| Hour | 0-23 | 8,17 at 08:00 and 17:00 |
| Day | 1-31 or L | L last day of the month |
| Month | 1-12 | 1,7 January and July |
| Weekday | 0-6 | 1-5 Monday through Friday |
Each field supports *, a value, comma-separated values, ranges, and steps: N, N,M, N-M, */S, N/S, or N-M/S. The special L is valid only by itself in the day field.
*/15 * * * *Every 15 minutes0 8 * * 1-5Weekdays at 08:0030 6 1 * *First of each month at 06:300 23 L * *Last day of each month at 23:000 9 15 * 1Only when the 15th is a MondaySupported macros
@hourly0 * * * *
@daily0 0 * * *
@midnight0 0 * * *
@weekly0 0 * * 0
@monthly0 0 1 * *
@yearly0 0 1 1 *
@annually0 0 1 1 *
Macros are case-insensitive and stored in lowercase. @reboot is not supported because dispatcher restarts are not a reliable per-job schedule.
Important: day-of-month and weekday use AND
When both fields are restricted, CronEngine requires both to match. Some Unix cron implementations use OR instead. In CronEngine, 0 9 15 * 1 runs at 09:00 only when the 15th of the month is a Monday, not on every Monday plus every 15th.
Timezone behavior
Schedules use the timezone in Account settings, not the server timezone. @daily means local midnight. Changing your timezone recalculates upcoming executions for all active jobs.
Example: 0 9 * * * in Europe/Brussels stays at 09:00 Brussels time as the UTC offset changes.
DST behavior
During the spring clock jump, a nonexistent local time is skipped. During the autumn rollback, a repeated matching local time runs once in each real occurrence.
Month lengths, leap years, and calendar boundaries are resolved from the real local calendar.
When a clock is the wrong answer
Two questions a cron expression cannot answer. Both live behind the same switch on the job form, next to Repeats, and neither changes anything about a job you already have.
Once, at a moment you pick
Choose Once and a date and time in your account timezone. It fires at that moment, then completes: it will not run again, it does not reserve any of your monthly runs, and it stops occupying an active-job slot the moment it has run. One-off requests draw on their own monthly allowance instead. If its moment passes while the job is paused, it stays paused rather than firing late.
After another job has finished
Choose After a job, pick the job to follow, and say which ending should start it: only after it succeeds, only after it fails, or either way. The follower has no clock of its own. It is armed when the job it follows finishes an execution, which means after every retry that execution made, on the verdict the execution ended with, not on whichever attempt happened to be last.
It costs what the job it follows costs
A follower runs whenever the job above it runs, so it reserves the same number of monthly runs and uses an active-job slot. Following an hourly job costs another hourly job. Change the schedule at the top of a chain and everything below it is repriced with it.
Chains are lines, not webs
A job follows one other job, and up to five jobs can follow the same one. A chain can be at most five jobs long, and a link that would loop back on itself is refused when you save. Only a repeating job, or a job that already follows one, can be followed: a one-off has already spent its moment.
Failures do not cascade quietly
A follower set to run only after success simply does not run when the job above it fails, and nothing is recorded for it. Each job in a chain keeps its own timeout, retries, overlap protection, notifications and history. Delete a job that others follow and they are paused, so nothing is left waiting on something that no longer exists.
04 · HTTP requests
What CronEngine sends
Your endpoint must be publicly reachable. HTTP works, but HTTPS is strongly recommended whenever a URL, header, or body contains sensitive data.
Methods
GET, POST, PUT, PATCH, DELETE, and HEAD are supported.
Custom headers
Enter one Name: value header per line, up to 8,000 bytes total.
Request bodies
POST, PUT, and PATCH send a raw body of up to 10,000 bytes.
Content types
Choose JSON, form-urlencoded, or plain text for body methods.
Authentication
No auth, Bearer token, or HTTP Basic username and password.
Network safety
Private, local, metadata, reserved, blocked hosts, and blocked IPs are rejected.
POST /hooks/nightly HTTP/1.1
Host: api.example.com
User-Agent: CronEngine-Dispatcher/2
Authorization: Bearer your-dedicated-token
Content-Type: application/json
Accept: application/json
X-Environment: production
{"source":"cronengine","task":"nightly-sync"}
Managed headers
CronEngine manages Authorization through the auth fields and manages Content-Type through its selector. Connection, content length, host, proxy, transfer-encoding, trailer, upgrade, and user-agent headers cannot be overridden.
Credential storage
Job tokens, Basic credentials, custom header values, and request bodies are encrypted with AES-256-GCM before they are stored, under a key held outside the database and kept out of database backups. Workers decrypt each value only when they send your request. Authentication secrets are write-only: once saved they are never shown again, and you replace rather than read them. Still use unique, least-privilege credentials that can be rotated or revoked.
Variables
The URL, the request headers and the request body can all contain {{...}} placeholders. CronEngine fills them in at the moment the request goes out, which is why {{now}} is the time of the actual call and {{execution.attempt}} counts up across retries.
| Variable | Value | Example |
|---|---|---|
| {{job.id}} | This job's numeric id | 57 |
| {{job.name}} | The job name you gave it | Nightly report |
| {{execution.id}} | Id of this run, identical across its retries | exe_290aa7905d54b2220d5430fe650f2e79 |
| {{execution.attempt}} | Attempt number, 1 on the first try | 1 |
| {{execution.scheduled_at}} | When this run was due (UTC) | 2026-09-10T22:00:00Z |
| {{now}} | Current time in UTC | 2026-09-10T22:00:15Z |
| {{now.unix}} | Current time as a Unix timestamp | 1789077615 |
| {{now.date}} | Current date in your timezone | 2026-09-11 |
| {{now.time}} | Current time of day in your timezone | 00:00:15 |
| {{now.local}} | Current time in your timezone | 2026-09-11T00:00:15+02:00 |
https://api.example.com/reports/{{now.date}}?run={{execution.id}}
X-Run-Id: {{execution.id}}
X-Attempt: {{execution.attempt}}
{"job":"{{job.name}}","due":"{{execution.scheduled_at}}","sent":"{{now}}"}
Values are encoded for where they land
A value in a URL is percent-encoded, a value in a JSON body is escaped as a JSON string, one in a form body is form-encoded, and one in a header loses anything that could start a new line. A job named Bob"s job therefore cannot break your JSON, and a variable can never introduce a slash or a colon that would move the request to a different path or host. For the same reason, variables are not allowed in the hostname.
Anything else is left alone
Only the names in the table are replaced. If your body already contains {{customer_id}} for a template engine further down your own pipeline, it is sent exactly as written. The same is true of a typo: {{job.nmae}} arrives verbatim rather than turning into an empty string, so the mistake is visible in your logs. Test request renders variables the same way the scheduler does, so you can see the finished request before saving.
05 · Responses
Success, redirects, and saved content
Default success rule
Any HTTP 2xx or 3xx response is successful.
A network error, DNS failure, refused connection, TLS failure, or timeout is always a failure because no response was received. You can customize accepted ranges, add exact codes, and optionally require that the captured body contains or does not contain case-sensitive text. Status and body conditions can use AND or OR.
Scheduled executions follow at most four redirect hops (five requests in the chain, including the original). Every destination is checked against the outbound safety rules. Test request and Run now do not follow redirects.
CronEngine reads the first 10 KiB (10,240 bytes) of a response. Larger responses receive a [truncated] marker. Body success checks use this captured portion.
The request stops at your plan’s timeout, from 20 seconds on Free through 600 seconds on Enterprise. A timeout is recorded as a failure.
| Save mode | What is stored in run history |
|---|---|
| Always | The captured response for successful and failed runs. |
| Only on failure | The captured response only when the configured success criteria fail. |
| Never | No response body. Status, duration, success, and error details are still recorded. |
The save mode controls history storage, not whether CronEngine captures the first 10 KB while evaluating body-based success rules.
06 · Execution
Skips are not failures
Expected control flow
Overlap protection is enabled by default. If the previous execution is still within its lease when the next occurrence becomes due, that occurrence is consumed and skipped.
- No failed history row
- No health-state change
- No failure notification
An attempted execution failed
The request was attempted and encountered a network error or did not satisfy the job’s success criteria.
- Recorded in run history
- Updates consecutive-failure health
- May create a notification event
Automatic retries
Retries are off by default on every job, and every job you already have keeps behaving exactly as it does today. Turn them on per job under Execution behavior, from zero up to your plan's ceiling. Leave them at zero when calling your endpoint twice would do the work twice.
Only failures that a second try could fix
Timeouts, refused or reset connections, and statuses that mean "come back later": 408, 425, 429, 500, 502, 503, 504 and Cloudflare's 52x. A 404, a 401, a hostname that does not resolve, or a response that simply failed your success criteria is a settled answer, so it is left alone.
Backoff, and one run either way
Waits grow between attempts, 15s, 30s, 60s, then 2 minutes, each jittered. Every attempt belongs to one execution: it costs one scheduled run, keeps one slot of history, and only notifies you once the run is really over. A run that fails and then succeeds on its retry is a success, and never emails you.
Your endpoint can tell attempts apart
Every attempt carries the same X-CronEngine-Execution-ID and an increasing X-CronEngine-Attempt, so a receiver that needs exactly-once behaviour can deduplicate on the execution id.
Notification delivery retries separately
Once a run creates an alert event, delivery has its own durable outbox. Transient email/provider/network failures and webhook HTTP 408, 425, 429, or 5xx responses are retried with exponential backoff, up to five attempts by default. That retries the alert, not the job.
Before you turn retries on
- Your endpoint may do the same work twice, seconds apart. A retry is the identical request sent again: a charge taken, an email sent, a row inserted can all happen a second time, and CronEngine cannot undo it. If the work is not safe to repeat, either leave retries at zero or deduplicate on the
X-CronEngine-Execution-IDheader, which is identical across every attempt of one run. - Retries do not extend your request timeout. Each attempt is still cut off at your plan's timeout; a job that needs longer than that needs a bigger plan, not retries. Retrying a request that timed out simply tries again from zero.
- A failing run stays busy for longer. Attempts plus the waits between them add up: on Business, 3 retries of a 300-second job can keep one occurrence going for roughly 22 minutes before it is finally recorded as failed. With Prevent overlapping runs on, occurrences due inside that window are skipped, so a frequently scheduled job runs noticeably less often while its endpoint is down. The job form shows the worst case for the setting you pick.
Free-plan safety: a Free job is automatically paused after 20 consecutive failed executions and a one-time account email is queued. A success resets the count; manually resuming the job starts a fresh count.
07 · Notifications
Choose when and where alerts arrive
| Trigger | Behavior |
|---|---|
| None | No per-run alert events. |
| Every failure | Creates an alert for each failed execution. |
| State change | Alerts on the first failure, then again on recovery. |
| Failure threshold | Alerts when consecutive failures reach your threshold, then on recovery. |
| Every run | Alerts for successful and failed runs; available on paid plans. |
Channels include email, generic signed webhooks, Slack, Discord, and Telegram. Channel counts and daily delivery caps depend on the plan and are enforced independently per channel type. Weekly and monthly account digests are separate from per-job alerts.
Generic webhook payload
What CronEngine sends to your endpoint
Generic webhooks receive an HTTPS POST with JSON. The envelope is versioned, contains a stable event ID, and includes the job and run snapshot that caused the notification.
{
"version": "1",
"id": "evt_1842",
"type": "job.failed",
"created_at": "2026-07-25T08:09:10Z",
"job": {
"id": 7,
"name": "Nightly invoice sync",
"url": "https://example.com/tasks/invoices",
"history_url": "https://cronengine.com/jobs/history?id=7"
},
"run": {
"id": 9124,
"success": false,
"http_status": 503,
"duration_ms": 812,
"error": "The endpoint returned HTTP 503.",
"fired_at": "2026-07-25T08:09:10Z"
}
}
Event types
- job.failed
- A failure alert or configured threshold was reached.
- job.recovered
- The first success after an open failure incident.
- job.run.succeeded
- A successful every-run notification.
- job.run.failed
- A failed every-run notification.
Delivery contract
- Delivery is at least once, so the same event can arrive more than once.
- Return any 2xx status to acknowledge the event.
-
Store
idand ignore duplicates with the same ID. - Network errors, 408, 425, 429, and 5xx responses retry. Other non-2xx responses fail permanently.
-
http_statusis null when no HTTP response was received. - Notification redirects are not followed.
Headers on every generic webhook
X-CronEngine-Webhook-Version: 1
X-CronEngine-Event-Id: evt_1842
X-CronEngine-Event-Type: job.failed
X-CronEngine-Timestamp: 1784966950
Optional signature
When a signing secret is configured, CronEngine adds X-CronEngine-Signature: v1=<hex HMAC-SHA256>. Verify the HMAC over <timestamp>.<raw body> before decoding JSON, use a constant-time comparison, and reject stale timestamps.
Slack and Discord use their native incoming-webhook payloads. Telegram uses the official Bot API. Each includes the outcome, HTTP status, duration, error, history link, and stable event ID.
Full webhook reference08 · Heartbeats
Watch the work we cannot run
A cron job is CronEngine calling your URL. A heartbeat is the mirror image: your script calls us when it finishes, and we raise the alarm when that call does not arrive. Use it for work that has to run on your own machine, where we cannot reach in to start it.
| Who calls | What it notices | |
|---|---|---|
| Cron job | We call your URL | A call of ours that failed |
| Heartbeat | Your script calls us | Silence |
That second row is the point. If the machine is switched off, nothing fails and nothing is reported, so an outbound check sees a healthy nothing. A heartbeat notices that the ping stopped.
One line in your crontab
Create a monitor and you get a ping URL. Call it when your work finishes.
0 3 * * * /usr/local/bin/backup.sh && curl -fsS https://cronengine.com/ping/hb_…
The && matters: the ping only happens when the script
succeeded. Anyone holding that URL can report that your work ran, so treat it like a
password. Deleting the monitor is the only way to revoke one.
| URL | Meaning |
|---|---|
| /ping/<token> | Finished, successfully. This is the one that moves the deadline. |
| /ping/<token>/start | Started. Gives you a duration, and catches a run that begins but never ends. |
| /ping/<token>/fail | Finished, and it knows it failed. Marks it down without waiting for the deadline. |
Two ways to say when it is due
Every so often for "at least once a day".
On a schedule for work that does not run around
the clock: a cron expression in your account timezone, built with the same editor a job
uses. 0 3 * * 1-5 expects a ping on weekday
mornings and stays quiet all weekend instead of alarming every Saturday.
Grace time, and going down
Grace is how late a ping may be before we call it down, so a job that usually takes twenty minutes and sometimes takes two hours does not wake you. A monitor that has never been pinged stays Waiting and never alarms: creating one and not wiring it up yet is not a failure.
Heartbeats do not use your scheduled runs
An inbound ping costs no outbound request and no worker, so monitors have their own plan allowance and never draw on the monthly run reservation. Alerts go to your account email plus any channel you configure, and count against the same daily notification cap as your jobs.
08 · Troubleshooting
When a request fails
No response / network error
Confirm the hostname resolves publicly, the port is reachable, and the URL does not point to localhost, a private network, cloud metadata, a reserved range, or a blocked CronEngine address. Check DNS, TLS certificate validity, and your plan timeout.
HTTP 401 or 403
Check the Bearer token or Basic credentials. Confirm the credential is active and has permission for this endpoint. If a WAF or firewall is involved, allowlist the CronEngine outbound IP.
HTTP 404
Verify the full path and environment. Redirects are followed for scheduled jobs, but a redirect cannot rescue an incorrect final route.
HTTP 405
The endpoint rejected the selected method. Match GET, POST, PUT, PATCH, DELETE, or HEAD to what your server route accepts.
HTTP 415 or body parsing errors
Match the selected Content-Type to the raw body. For JSON, validate the JSON and use application/json. For form data, URL-encode the body.
HTTP 429
Your endpoint is rate-limiting scheduled executions. Reduce the cadence or change the endpoint limit. The job itself is not automatically retried.
HTTP 5xx
The request reached your application. Inspect its server logs using the run timestamp, then use the stored response and CronEngine error explanation for context.
Response looks cut off
Run history and body matching use only the first 10 KiB. Put health or success markers near the start of the response and keep diagnostic payloads concise.
Expected run is missing
Check whether the job is active, whether DST skipped a nonexistent local time, and whether overlap protection skipped an occurrence while the prior execution lease was active.
Firewall allowlisting
Allow inbound requests from CronEngine
Add this single IPv4 address to the allowlist for the exact route and port CronEngine calls. Keep normal authentication enabled; an IP allowlist should be an additional check, not the only credential.
45.9.188.216
Scheduled job requests identify themselves with User-Agent: CronEngine-Dispatcher/2. The fixed IP is the reliable network allowlist value; user-agent strings can be imitated and should only help with logs or routing.
09 · Plan usage
Capacity is reserved up front
Scheduled runs are a reservation model, not a meter. Each active job reserves how often its expression is expected to fire in a fixed 30-day month. CronEngine checks the total when you create, edit, or resume a job. Actual executions are not counted afterward, so capacity never runs out midway through a month and nothing resets on the first.
Feature availability
Plan capacity at a glance
| Plan | Active jobs | Reserved runs | Fastest schedule | Timeout | History per job | Retries per run | One-off requests | Alert channels |
|---|---|---|---|---|---|---|---|---|
| Free | 5 | 3,000 | Every 15 min | 20 sec | 5 runs | None | 5 / month | 1 |
| Starter | 25 | 10,000 | Every 5 min | 60 sec | 20 runs | Up to 1 | 50 / month | 1 |
| Pro | 50 | 30,000 | Every 2 min | 150 sec | 30 runs | Up to 2 | 250 / month | 2 |
| Business | Unlimited | 90,000 | Every minute | 300 sec | 40 runs | Up to 3 | 1,000 / month | 3 |
| Enterprise | Unlimited | 300,000 | Every minute | 600 sec | 50 runs | Up to 5 | 5,000 / month | 3 |
One-off requests are the one allowance that is counted rather than reserved, and they reset at the start of each month: a request that runs once is spent, so there is nothing to hold capacity for. They never draw on your scheduled runs, and a pending one does not use an active-job slot. Every plan includes failure, recovery, consecutive-failure threshold alerts, and weekly or monthly digests. Every-run notifications are available from Starter. Daily notification caps are 5, 20, 50, 100, and unlimited per channel type from Free through Enterprise. Retries are a ceiling, not a default: each job is created with retries off until you raise it, and a retried run still costs a single reserved run.
| Cadence | Reservation | Math |
|---|---|---|
| Every minute | 43,200 | 60 × 24 × 30 |
| Every 5 minutes | 8,640 | 12 × 24 × 30 |
| Every 15 minutes | 2,880 | 4 × 24 × 30 |
| Hourly | 720 | 24 × 30 |
| Daily | 30 | 1 × 30 |
Worked example · Starter
A mixed workload fits
Every 5 min8,640
Hourly720
Daily30
Reserved9,390
Starter limit10,000
Remaining610
Worked example · Free
One more hourly job does not fit
Every 15 min2,880
Proposed hourly720
Would reserve3,600
Free limit3,000
Over by600
Pause another active job, run the new job less often, or change plan. Paused jobs reserve no capacity.
Calendar-specific schedules are estimated across a canonical 365-day year and scaled to 30 days. This correctly charges schedules involving day 31, weekdays, L, and CronEngine’s day-plus-weekday AND behavior. Any valid rare schedule reserves at least one run.
10 · Account settings
Controls you will find after sign-in
Profile and timezone
Update your display name and IANA timezone. A timezone change reschedules active jobs and controls the local dates used for digests and reports.
Notification email
Send alerts to a dedicated address. A new notification address must be verified before it becomes the active destination.
Email digests
Choose off, weekly, or monthly. Weekly summaries are prepared after 08:00 Monday; monthly summaries after 08:00 on the first, in your timezone. Empty digests are not sent.
Login security
Review active sessions and revoke other remembered or logged-in devices. Password changes and account access controls live under Security.
Billing
Paid accounts can open Stripe’s hosted portal to update payment details, review the subscription, and view billing history.
Archive account
Archiving pauses product use while preserving the account. Deletion and other account-management options are explained in Settings.
Ready to configure your account?
The documentation stays public; account controls require a signed-in, verified account.