Orbit
Orbit Cron Jobs
Cron jobs schedule recurring HTTP requests to your deployed project, so a nightly cleanup, an hourly sync or a weekly digest runs on time without you standing up a separate scheduler.
Where Cron Jobs Live
Open Orbit, click the project, and choose Crons under the Configure group in the project tab strip. The page is titled Cron jobs and describes what it does: schedule HTTP requests to your production deployment, using standard five-field cron syntax in UTC, or the aliases @hourly, @daily, @weekly and @monthly.
The page shows the Target host it will call, so you can confirm at a glance that it is pointing at the right deployment.

How It Works
Orbit does not run your code in a scheduler. It calls a URL on your own project on a schedule, and your code does the work.
That means the thing you schedule is an ordinary route in your application, for example /api/cron/cleanup. Anything your app can do in response to a request, it can do on a schedule.
Creating a Cron Job
- Click New cron.
- Give it a Name, up to 120 characters.
- Set the Path on your project, starting with a slash.
- Choose a Schedule from the presets or type an expression.
- Pick a Method.
GETis the default. - Add a Request body if the method is POST, PUT or PATCH.
- Set a Timeout between 1 and 300 seconds. The default is 30.
- Leave the Generate a Bearer secret option ticked unless you have your own auth.
- Click Create cron.
Schedule Presets
| Preset | Expression |
|---|---|
| Every 5 min | */5 * * * * |
| Every 15 min | */15 * * * * |
| Hourly | @hourly |
| Daily 09:00 UTC | 0 9 * * * |
| Daily midnight | @daily |
| Weekly Mon 09:00 | 0 9 * * 1 |
| Monthly 1st | @monthly |
Or write your own five-field expression: minute, hour, day of month, month, day of week.
All schedules are UTC, with no daylight saving adjustment. A job set for 0 9 * * * runs at 9am UTC year round, which drifts by an hour against New Zealand time twice a year. If a job must run at a specific local time, pick the UTC hour deliberately and note which half of the year you optimised for.
Authenticating the Call
Leaving the Bearer secret option ticked generates a random token that is sent as an Authorization header on every execution. It is shown once, immediately after creation, with the note that it will not be shown again.
Copy it and check it in your handler:
export async function GET(req) {
const auth = req.headers.get('authorization');
if (auth !== `Bearer ${process.env.CRON_SECRET}`) {
return new Response('Unauthorized', { status: 401 });
}
// do the work
}
Store the secret using the project's environment variables: see Environment Variables in Orbit.
Without a check like this, your cron path is a public URL that anyone can call as often as they like. That is fine for something harmless and serious for anything that writes, sends email or costs money. Add the check before the first run, not after someone finds the endpoint.
You can also send your own headers instead, if your application already has an authentication scheme.
Reading the Job List
Each job shows:
- Schedule, the expression it runs on.
- Next, when it will run again.
- Last, when it last ran and how that went.
- An ok / fail counter.
- Last error, where the most recent failure left a message.
- A PAUSED badge when it is switched off.
Four actions sit on each row: Run now, Pause or Resume, and Delete.
Run now executes the job immediately, regardless of its schedule, and reports the outcome. It is the right way to test a new job rather than waiting for the next tick.
Execution Outcomes
| Status | Meaning |
|---|---|
| OK | Your endpoint returned a success response |
| FAILED | Your endpoint returned an error, or the request could not be made |
| TIMEOUT | Your endpoint did not respond within the timeout |
| SKIPPED | The execution did not run |
Each execution is recorded with its status, response code, duration, error and what triggered it, so a job that fails intermittently leaves a trail you can read rather than a single "last error".
Choosing a Timeout
The timeout is per execution, between 1 and 300 seconds, defaulting to 30.
Set it a little above the job's real worst case, not far above. A generous timeout on a job that has hung means five minutes of a builder waiting on nothing. A tight timeout on a job that legitimately takes two minutes means a permanent failure and a misleading alert.
Better still, keep the handler fast: have it enqueue work and return immediately, rather than doing the work inline. A cron job that returns in 200 milliseconds never times out.
Limits
A project can hold up to 50 cron jobs. That is per project, so an account with several projects has more in total.
If you need to schedule something against staging rather than production, use Cron triggers in Settings instead. That card lets you pick the environment, and is capped at ten triggers per project. See Orbit Project Settings.
Deleting a Job
Click Delete and confirm. The confirmation notes that execution history will also be removed, so if you want a record of how a job behaved, capture it before deleting.
Pause rather than delete when you are temporarily stopping a job. Pausing keeps the configuration, the secret and the history intact.
Practical Advice
Make handlers idempotent. A cron call can be retried, and Run now can be pressed while a scheduled run is already in progress. Your handler should cope with running twice without doing the work twice.
Do not schedule everything on the hour. 0 * * * * on every job means every job competing at the same moment. Spread them: 7 * * * *, 23 * * * *, and so on.
Log inside your handler. The execution record tells you the response code and duration. What actually happened is your application's business, and you will want it when a job silently does nothing.
Troubleshooting
Every execution is FAILED with a 401. Your handler is rejecting the request. Check that the secret stored in your environment variables matches the one generated here, including the Bearer prefix in the comparison.
Every execution is FAILED with a 404. The path does not exist on the deployed project. Test it in a browser against the target host shown on the page.
Executions TIMEOUT. The handler is doing too much inline. Split the work, or raise the timeout if the work genuinely takes that long and is not a runaway.
Next never advances. The job is paused. Look for the PAUSED badge.
The job runs at the wrong time. Check UTC against your local time. This is the single most common surprise with scheduled jobs.
Where To Go Next
- Environment Variables in Orbit for storing the cron secret.
- Orbit Project Settings for per-environment cron triggers.
- Orbit Webhooks to get told when things go wrong.