Configuration
Crons
Call a route of your production deployment on a schedule.
Declare crons
Add them to si.json in the project's root directory and push to the production branch:
{
"crons": [{ "path": "/api/cron/cleanup", "schedule": "cron(0 3 * * ? *)" }]
}When the deployment becomes production, each entry becomes a schedule. Crons need a server, so they're for Next.js projects.
Schedule expressions
Schedules use EventBridge Scheduler expressions, in UTC:
| Expression | Runs |
|---|---|
rate(5 minutes) | Every 5 minutes |
rate(1 hour) | Every hour |
rate(1 day) | Every day |
cron(0 3 * * ? *) | Every day at 03:00 |
cron(0/15 * * * ? *) | Every 15 minutes, on the quarter hour |
cron(0 12 ? * MON-FRI *) | Weekdays at 12:00 |
cron(0 9 1 * ? *) | 09:00 on the first of each month |
cron(…) takes six fields: minutes, hours, day of month, month, day of week and year. One of day of month and day of week must be ?. rate(…) takes a number and minute(s), hour(s) or day(s).
A run can start up to 5 minutes after its scheduled time.
The request
At each run, the platform calls your current production deployment at its own deployment hostname:
POST /api/cron/cleanup HTTP/1.1
Host: <project>-<id>-<org>.deployments.gov.vin
x-si-cron: <SI_CRON_SECRET>
user-agent: si-cron- There's no body.
- The platform waits up to 120 seconds, but the server function stops after 30.
- A response of 500 or above is retried up to 2 more times. Any other status counts as done.
- Nothing is called while the project has no production deployment.
Check the secret
The route is public like the rest of your deployment, so check the x-si-cron header against SI_CRON_SECRET, which the platform sets on every server function:
import { timingSafeEqual } from "node:crypto"
function fromCron(request: Request) {
const sent = Buffer.from(request.headers.get("x-si-cron") ?? "")
const secret = Buffer.from(process.env.SI_CRON_SECRET ?? "")
return secret.length > 0 && sent.length === secret.length && timingSafeEqual(sent, secret)
}
export async function POST(request: Request) {
if (!fromCron(request)) return new Response("Unauthorized", { status: 401 })
// … the scheduled work
return Response.json({ ok: true })
}The secret belongs to the project and stays the same across deployments.
Crons follow production
Every deployment records its crons. When a deployment becomes production (a production push, a promote or a rollback), its crons replace the project's schedules, so schedules always match the code that's serving. Preview deployments never receive cron requests.
Planned
- Pausing a project's crons in a way that survives deploys, and seeing the next run and last result. Coming soon