Cron jobs
Schedule HTTP requests to your app and SSH commands on your server, with retries, run history and failure notifications.
Cron jobs run scheduled work for your apps and servers: call an endpoint in your app, or run a shell command on the server over SSH. DollarDeploy schedules every run, retries failures, keeps the run history and notifies you when a run fails. There is nothing to install on the server and no crontab to maintain.
Open an app or a server (host), go to the Cron tab and click Add cron.
Two kinds of cron jobs
| Type | Available on | What runs |
|---|---|---|
| HTTP request | Apps | DollarDeploy calls https://<app hostname><path> with a bearer secret |
| SSH command | Apps and servers | DollarDeploy connects over SSH and runs a shell command on the server |
Use an HTTP request when the job already lives in your app code, for example /api/cron/cleanup in a Next.js app. Use an SSH command for CLI tasks such as php artisan schedule:run, python manage.py clearsessions or npm run cleanup, and for server maintenance such as cleaning up old files.
Schedule
The schedule is a standard 5-field cron expression: minute hour day month weekday. Expressions with seconds are not supported.
| Expression | Runs |
|---|---|
*/15 * * * * |
Every 15 minutes |
0 * * * * |
Every hour, on the hour |
0 3 * * * |
Every day at 03:00 |
0 9 * * 1 |
Every Monday at 09:00 |
0 0 1 * * |
On the 1st of every month at midnight |
The timezone is UTC by default. Pick any IANA timezone, for example Europe/Berlin, and the schedule follows it, including daylight saving time changes. While you type, the editor shows the schedule in plain words and the next run times.
HTTP request cron jobs
DollarDeploy sends a GET (or POST) request to your app hostname and path, with these headers:
GET /api/cron HTTP/1.1
Authorization: Bearer <CRON_SECRET>
User-Agent: DollarDeploy-Cron/1
X-DollarDeploy-Cron-Id: <cron id>
When you create the first HTTP cron job on an app, DollarDeploy generates a random CRON_SECRET and adds it to the app environment variables. Redeploy the app once so the running app gets the value. To use a secret you already have, set Auth env var to the name of an existing app env variable instead.
Your endpoint should reject any request without the right secret, because the URL is public:
// Next.js App Router: app/api/cron/route.ts
export async function GET(request: Request) {
if (!process.env.CRON_SECRET) {
return new Response("Internal server error", { status: 500 });
}
if (request.headers.get("authorization") !== `Bearer ${process.env.CRON_SECRET}`) {
return new Response("Unauthorized", { status: 401 });
}
// ... do the scheduled work ...
return Response.json({ ok: true });
}
// Express
app.get("/api/cron", (req, res) => {
if (!process.env.CRON_SECRET) {
return res.status(500).send("Internal server error");
}
if (req.get("authorization") !== `Bearer ${process.env.CRON_SECRET}`) {
return res.status(401).send("Unauthorized");
}
// ... do the scheduled work ...
res.json({ ok: true });
});
To test the endpoint yourself:
curl "https://myapp.example.com/api/cron" \
-H "Authorization: Bearer $CRON_SECRET"
How the result is judged:
- A
2xxresponse is a success. The first 4 KB of the response body is saved with the run. - Redirects are not followed. A
3xxcounts as a failure, and the run log shows where it pointed. 5xxresponses and network errors are retried.4xxresponses are not, because a retry would get the same answer.- The app needs a public hostname. For an app that is only reachable inside your network, use an SSH command cron job instead.
SSH command cron jobs
DollarDeploy connects to the server over SSH and runs the command in a bash login shell (bash -l -c), the same way your app starts. Tools on the PATH from the shell profile, such as node, php or python, work as usual.
- App cron job: runs in the app directory, as the app user (
appwhen the server login isroot), with the app environment variables loaded. - Server cron job: runs as the server SSH user in its home directory, with the server environment variables loaded.
On servers with systemd, each run is a transient systemd unit named cron-<cron id>, so systemd stops it at the time limit even if the SSH connection drops, and journalctl -u cron-<cron id> on the server shows when it started and stopped. The unit has no extra sandboxing: the command can do anything its user can do on the server. The command output is shown in the run log in DollarDeploy.
Exit code 0 is a success; any other exit code is a failure. The last 4 KB of output is saved with the run.
# Laravel scheduler
php artisan schedule:run
# Django: remove expired sessions
python manage.py clearsessions
# Server: free disk space taken by unused Docker images
docker image prune -af --filter "until=168h"
Limits and behavior
- 60 second limit per run, including retries. Longer runs are stopped and marked as
timeout. For longer jobs, have the cron job start the work in the background, for example by queueing it in your app. - Retries: 0 to 3, default 1. Failed attempts are retried after 10 seconds, within the same 60 second limit.
- No overlapping runs: if the previous run of a cron job is still running, the next one is skipped.
- Missed runs: if runs were missed, for example during maintenance, the job runs once and continues on schedule. Missed runs are not run one by one.
- History: the Cron tab shows the last run, its status, HTTP status or exit code and duration. Runs and their logs are kept for 7 days.
- Run now: click the play button to run a cron job immediately, outside its schedule.
- Disable: a disabled cron job keeps its settings but does not run.
Notifications
By default you get a notification when a run fails. You can also turn on notifications for successful runs. Notifications go to the channels configured in Notifications: Slack, Telegram, Discord, email or a webhook.
Plans
| Plan | Cron jobs per app | Cron jobs per server | Most frequent schedule |
|---|---|---|---|
| Free | - | - | - |
| Hobby | 1 | 1 | Every 10 minutes |
| Pro and Enterprise | 5 | 5 | Every minute |
If you move to a plan with lower limits, the cron jobs over the limit are paused, not deleted. They start running again when you upgrade.
CLI, API and AI agents
Cron jobs can be managed from the DollarDeploy CLI:
ddc cron list --app <app-id>
ddc cron create --app <app-id> --type http --schedule "*/15 * * * *" --path /api/cron
ddc cron create --app <app-id> --type command --schedule "0 3 * * *" --command "npm run cleanup"
ddc cron create --host <host-id> --type command --schedule "0 * * * *" --command "df -h /"
ddc cron run <cron-id> --wait
ddc cron runs <cron-id>
The same operations are available in the DollarDeploy API and as MCP tools, so you can ask your AI agent to "run the cleanup every night at 03:00" and it creates the cron job for you.