Schedule recurring jobs and one-off tasks from your nself backend. Uses Redis-backed queues for reliable execution with configurable retry policies, run history, and HTTP action support.
nself-cron is part of the ɳClaw bundle and requires the Redis service. Enable Redis in your .env with ENABLE_REDIS=true before installing.
# Redis is required, enable it first
echo "ENABLE_REDIS=true" >> .env
nself license set nself_pro_...
nself plugin install cron
nself build
nself start| Variable | Required | Default | Description |
|---|---|---|---|
PLUGIN_CRON_REDIS_URL | No | redis://127.0.0.1:6379 | Redis connection URL for job queue storage |
The Redis URL defaults to the nself-managed Redis instance. Override only if you are using an external Redis.
# Run a webhook every day at 9am
curl -X POST http://127.0.0.1:3713/cron/jobs \
-H "Content-Type: application/json" \
-d '{
"name": "daily-digest",
"schedule": "0 9 * * *",
"action": {
"type": "http",
"url": "https://your-app.com/api/send-digest",
"payload": { "template": "daily" }
},
"max_retries": 3,
"retry_delay_seconds": 60
}'# List all jobs
curl http://127.0.0.1:3713/cron/jobs
# View run history
curl http://127.0.0.1:3713/cron/runs
# Get a specific job
curl http://127.0.0.1:3713/cron/jobs/job-id-hereStandard 5-field cron syntax: minute hour day month weekday
| Expression | Meaning |
|---|---|
* * * * * | Every minute |
0 * * * * | Every hour at :00 |
0 9 * * * | Every day at 9:00 AM UTC |
0 9 * * 1 | Every Monday at 9:00 AM UTC |
0 0 1 * * | First day of every month at midnight |
*/15 * * * * | Every 15 minutes |
All times are UTC. The plugin does not support timezone-aware cron expressions.
{
"name": "string", // unique job name
"schedule": "0 9 * * *", // cron expression
"action": {
"type": "http", // only "http" supported currently
"url": "https://...", // URL to POST to when job fires
"payload": {}, // optional JSON body for the request
"headers": {} // optional HTTP headers
},
"max_retries": 3, // retry count on failure (default: 0)
"retry_delay_seconds": 60, // wait between retries (default: 30)
"enabled": true // set false to pause without deleting
}| Endpoint | Method | Description |
|---|---|---|
/cron/jobs | GET | List all scheduled jobs |
/cron/jobs | POST | Create a new job |
/cron/jobs/:id | GET | Get a specific job by ID |
/cron/jobs/:id | DELETE | Delete a job |
/cron/runs | GET | List run history (paginated, most recent first) |
/cron/health | GET | Check plugin and Redis connectivity |
When a job action fails (non-2xx HTTP response or network error), the plugin retries according to max_retries and retry_delay_seconds. After all retries are exhausted, the run is recorded as failed and visible in /cron/runs.
# View failed runs
curl "http://127.0.0.1:3713/cron/runs?status=failed"The plugin creates a np_cron schema. Tables are auto-tracked by Hasura and accessible via GraphQL.
| Table | Key Columns | Purpose |
|---|---|---|
np_cron.jobs | id, name, schedule, action, enabled, max_retries, retry_delay_seconds, last_run_at, next_run_at, created_at | Job definitions. One row per scheduled job. |
np_cron.runs | id, job_id, status, attempt, started_at, finished_at, response_status, error_message | Execution history. One row per job execution attempt (including retries). |
# All jobs with their next scheduled run
query ScheduledJobs {
np_cron_jobs(where: { enabled: { _eq: true } }) {
name
schedule
next_run_at
last_run_at
}
}
# Recent failures for a specific job
query JobFailures($jobId: uuid!) {
np_cron_runs(
where: {
job_id: { _eq: $jobId }
status: { _eq: "failed" }
}
order_by: { started_at: desc }
limit: 10
) {
attempt
response_status
error_message
started_at
finished_at
}
}These events are published to the nself event bus and can be consumed by nself-mux rules.
| Event | Trigger | Payload fields |
|---|---|---|
cron.job.fired | A scheduled job starts executing | job_id, job_name, scheduled_at |
cron.job.success | Job action completes with 2xx response | job_id, job_name, response_status, duration_ms |
cron.job.failed | Job exhausts all retries | job_id, job_name, attempts, last_error |
cron.job.retrying | Job failed but has retries remaining | job_id, job_name, attempt, next_retry_at |
| Option | Cost | Data ownership | Self-hostable |
|---|---|---|---|
| Inngest | Free up to 50k runs/mo, then $25–$150/mo | Inngest holds job state | No (cloud only) |
| cron-job.org | Free tier, then $3–9/mo | cron-job.org logs all runs | No |
| Quirrel (self-hosted) | Free (MIT), but requires separate Redis + deployment | You own everything | Yes |
| nself-cron | $0.99/mo (ɳClaw bundle) — Redis already included | Your Postgres + Redis | Yes — on your VPS |
Check Redis connectivity first. The plugin queues due runs in Redis — if Redis is unavailable the scheduler halts silently. Run curl http://127.0.0.1:3713/cron/health and look for "redis": "ok" in the response.
status: failed immediatelyYour action URL returned a non-2xx status or timed out. Check np_cron.runs for response_status and error_message. Make sure the target URL is reachable from your VPS — it must be an absolute URL, not localhost.
All schedules run in UTC. 0 9 * * * fires at 09:00 UTC. If you expect a local time, convert first: for UTC-5, use 0 14 * * * to fire at 9:00 AM Eastern.
DELETE /cron/jobs/:id removes the job from np_cron.jobs and cancels any pending Redis entries. If a Redis flush happened outside nself, recreate the job — it will pick up from the next scheduled time.
# Confirm Redis is enabled
nself config get | grep ENABLE_REDIS
# Enable and restart
echo "ENABLE_REDIS=true" >> .env
nself build && nself startnself plugin remove cronPort: 3713 | Bundle: ɳClaw ($0.99/mo) or ɳSelf+ ($3.99/mo) | Requires: Redis | Last Updated: May 2026 | Plugin Version 1.1.0