NewSchokr 13 is here. A whole new UI, seven locations, snapshots and more. Read more →

How to run scheduled (cron) jobs for your app

Run tasks on a timetable on Schokr using node-cron, APScheduler, or Schokr Schedules to restart your server or send commands.

Updated · 1 min read

There are two ways to run something on a schedule on Schokr: use a scheduler library inside your always-running app (node-cron, APScheduler), or use your server's Schedules to restart it or send a command to it at set times. Servers do not sleep, so in-app schedulers keep firing.

Option 1: node-cron (Node.js and Bun)

npm install node-cron
const cron = require('node-cron')

// Every day at 03:00
cron.schedule('0 3 * * *', async () => {
  console.log('Running nightly cleanup')
  await cleanup()
}, { timezone: 'Europe/London' })

Without a timezone option, times follow the server's clock, which is usually UTC. Set the timezone explicitly to avoid surprises.

Cron syntax refresher:

ExpressionMeaning
*/5 * * * *Every 5 minutes
0 * * * *Every hour on the hour
0 3 * * *Daily at 03:00
0 9 * * 1Mondays at 09:00

Option 2: APScheduler (Python)

APScheduler==3.10.4
from apscheduler.schedulers.blocking import BlockingScheduler

sched = BlockingScheduler(timezone="UTC")

@sched.scheduled_job("cron", hour=3, minute=0)
def nightly():
    print("Running nightly cleanup", flush=True)

sched.start()

Inside a web app (Flask, FastAPI), use BackgroundScheduler instead so it does not block the server. If you run several gunicorn or uvicorn workers, each worker starts its own scheduler and jobs run multiple times. Run the scheduler in one process only, or in a separate small server.

Option 3: Schokr Schedules

Your server's Schedules let you automate actions at set times, such as restarting the server or sending a command to the console. Useful patterns:

  • Daily restart to clear memory leaks in a long-running app.
  • Send a command that your app reads from standard input, for example a cleanup line your code listens for:
process.stdin.on('data', (data) => {
  if (data.toString().trim() === 'cleanup') cleanup()
})
import sys, threading

def read_commands():
    for line in sys.stdin:
        if line.strip() == "cleanup":
            cleanup()

threading.Thread(target=read_commands, daemon=True).start()

Schedules are a good fit for coarse jobs. For precise or frequent jobs, an in-app scheduler is simpler.

Missed runs

If your server is stopped or restarting at the scheduled time, in-app schedulers will not run the job until the next occurrence. For critical tasks, record the last run time (for example in SQLite) and catch up on startup.

Still stuck?

Ask in the Schokr Discord with your schedule and log output.