• Open source
  • Self-hosted
  • MIT

Your scheduled jobs shouldn't be invisible.

Cron Pilot is an open-source, self-hosted control panel for scheduling, running and monitoring jobs on your servers.

Stop SSHing into production to edit crontabs, hunt through logs, and figure out why last night's job failed.

# the problem

Cron runs your jobs. It doesn't help you look after them.

A job that processed recurring donations stopped running. Nobody noticed for three days. By then, hundreds of thousands of dollars in donations had been missed.

Cron itself was fine. The job failed silently, on a server nobody was watching, with its output going nowhere.

$ crontab -la line you've probably written
0 2 * * *1 cd /var/www/app && php artisan donations:process >/dev/null 2>&12 # last successful run: unknown3
  1. 12 AM in the server's timezone. Hopefully the one you meant.
  2. 2Output and errors, discarded. The exit status goes nowhere either.
  3. 3When it stops running, nothing tells you. You find out from a support ticket.

What changes when you stop managing jobs by hand

With crontabWith Cron Pilot
SSH into each server to edit jobsManage every job from one web UI
Hand-write */15 9-17 * * 1-5Build schedules visually and preview the next runs
grep through logs for outputEvery run's output, exit status and duration, kept
"Did that actually run last night?"SuccessfulFailedSkipped at a glance
Wait for the schedule to test a jobRun it now
Comment out a line to stop a jobPause and resume with a toggle
# the product

See what happened on every run.

These are real screenshots from the demo data. Nothing here is a mockup.

See exactly what went wrong.

Every run keeps its full output and exit status. No SSH, no grep, no guessing which server wrote the log.

SQLSTATE[HY000]: General error: 1021 Disk full
Command failed with exit status: 1
Run duration: 41 seconds

View Run page for the Weekly reports task. Status Failed, run duration 41 seconds. The output shows 'Building revenue report... done (4 charts)', then 'Building usage report...' followed by an Illuminate Database QueryException: SQLSTATE HY000 General error 1021 Disk full, and '[ERROR] Command failed with exit status: 1'. The same failed Weekly reports run in dark mode, showing the Disk full SQL error and exit status 1.
Runs list. Customer import runs every 15 minutes: one Running, several Successful with durations between 6 and 18 minutes, and one Skipped at 20:39 with a duration of 0 seconds. An Invoice sync run finished successfully in 5 seconds. The same runs list in dark mode, with Running, Successful and Skipped statuses and durations.

Know what ran.

Running, Successful, Failed and Skipped, with start times and durations. That 20:39 Skipped is the overlap guard at work: the previous import was still going.

Schedule builder for Trial reminder emails. Frequency Weekly, every 1 week, by weekday: Monday to Friday. Start date January 5, 2026 at 9:00, timezone Eastern Time (New York). Upcoming run times: Monday, Tuesday and Wednesday, September 28 to 30, 2026, at 9:00 AM. Rrule preview: FREQ=WEEKLY;DTSTART=20260105T090000;INTERVAL=1;BYDAY=MO,TU,WE,TH,FR. The same schedule builder in dark mode: weekly on Monday to Friday at 9:00 AM Eastern Time, with the next three run times and the Rrule preview.

Schedule visually.

Weekdays at 9:00 AM, New York time, with the next runs previewed as you edit. No more squinting at 0 9 * * 1-5 and hoping.

Red badge to stack trace in two clicks.

Task list, then the failed task, then its run output. That's the whole investigation.

# how it works

A scheduler, a queue, and your servers.

Cron Pilot is a Laravel app. Two long-running processes do the work, and everything they see ends up in the run history.

  1. 1 · every minute

    Scheduler

    schedule:work checks which tasks are due.

  2. 2 · queue

    Queue

    Due tasks are queued. If the last run is still going, the new one is recorded as Skipped.

  3. 3 · worker

    Worker

    queue:work picks up the job and connects over SSH with the task's credentials.

  4. 4 · your infrastructure

    Your server

    The command runs where it always did. Nothing to install on it.

  5. 5 · stored

    Run history

    Output, exit status and duration come back and are kept with the run.

Then the scheduler checks again a minute later.

# features

What it does today.

This list is what ships in the repo right now. Planned work is further down, labelled as such.

  • Visual schedule builder

    Every second to once a year, specific weekdays, "the second Tuesday", start and end dates, and a timezone per task. A 9 AM task stays at 9 AM through daylight saving changes.

  • Runs on your servers

    Tasks run over SSH with credentials you choose per task. Private keys and passphrases are stored encrypted.

  • Run history

    The output, exit status and duration of every run, kept and searchable from the UI.

  • Run now

    Trigger any task on demand. You get a warning if it's already running.

  • No accidental overlaps

    A task never runs twice at once unless you allow it. A run that comes due mid-run is recorded as Skipped instead of piling up.

  • Pause and resume

    One toggle in the task list. A resumed task waits for its next scheduled time rather than firing immediately.

  • Tenants

    Keep teams, clients or projects apart, each with its own servers, credentials, tasks and history.

  • Open source, self-hosted

    MIT licensed. Commands, output and credentials stay on your own infrastructure.

# quick start

Running tonight.

It's a standard Laravel app. If you've deployed one before, you know most of this already.

bash
git clone https://github.com/cronpilot/cronpilot.git
cd cronpilot
composer install
npm install && npm run build
cp .env.example .env        # then set your DB_* values
php artisan key:generate
php artisan migrate

# Cron Pilot needs both of these running:
php artisan schedule:work   # checks for due tasks every minute
php artisan queue:work      # runs them

Requirements

  • PHP 8.4+
  • Composer
  • Node.js 18+
  • MySQL or MariaDB

Want something to click on?

php artisan db:seed --class=DemoSeeder creates a fictional "Acme" tenant with realistic tasks and a week of run history. It's what the screenshots above show.

Coming next

A one-command Docker install (docker compose up -d) is on the roadmap. It isn't here yet.

# why I built it

Cron isn't evil. We just keep making the same mistakes with it.

I've run production scheduled jobs for years. The one I remember is a job that processed recurring donations. It stopped running, and nobody noticed for three days. We found out when someone contacted support. By then, hundreds of thousands of dollars in donations had been missed.

Nothing was wrong with cron. The job failed silently, on a server nobody was watching, with its output going nowhere.

Cron runs your jobs, but it doesn't help you look after them.

I turned what I learned into a conference talk about the mistakes teams keep making. Cron Pilot is the tool built from those lessons.

# coming next

Not built yet. Being built.

None of these are in the current release. They're listed so you know where the project is going.

Next up

Failure alerts

Email and chat alerts when a job fails, or when it doesn't run at all. This is the lesson of the donations job, and the most important thing on the list.

Planned

One-command Docker install

docker compose up -d and you're done. No PHP or Node on the host.

Planned

Cron Pilot Agent

A small open-source agent on each server that connects out to Cron Pilot, so Cron Pilot never holds SSH credentials.

Follow along, or help out, on GitHub.

# cron pilot cloud · waitlist

A hosted Cron Pilot is coming.

Same open-source core, no servers to run. It doesn't exist yet. Join the waitlist and we'll tell you when it does.

This is a waitlist for a product that isn't available yet. We'll only email you about the Cloud launch.

Screenshot