Jobs & Scheduling
Long-running operations run as Laravel queue jobs. The scheduler in routes/console.php triggers backup, healthcheck, update, integrity, metrics, and domain-whois commands on a cron.
Queues
Two Redis queues, two supervisor programs (docs/DEPLOYMENT.md, "Queue Worker Setup"):
| Queue | Workers | Carries |
|---|---|---|
default | wpworks-queue ×2, --queue=default | Seconds-long, latency-sensitive jobs: WP-Cron, healthchecks, cache purges, provisioning, certificates, DNS, SFTP. |
heavy | wpworks-queue-heavy ×1, --queue=heavy,default --timeout=2700 | Minutes-long jobs: backups, updates, clones, restores, integrity checks. Never blocks default; drains it when idle. |
A job picks its queue in its constructor with $this->onQueue(self::QUEUE), so every dispatch path is covered; tests/Feature/QueueRoutingTest.php guards the routing. Depths of both queues are shown on the Capacity & insights page, and wphoster:monitor-queues raises a queue_backlog alert when default stays above 200 waiting jobs for 10 minutes.
Queue jobs
| Job | Queue | Path | Purpose |
|---|---|---|---|
| ProvisionSiteJob | default | app/Jobs/ProvisionSiteJob.php | Provisions new site via Provisioner |
| CloneSiteJob | heavy | app/Jobs/CloneSiteJob.php | Clones site via CloneEngine |
| BackupSiteJob | heavy | app/Jobs/BackupSiteJob.php | Creates DB or uploads backup |
| RestoreSiteJob | heavy | app/Jobs/RestoreSiteJob.php | Restores from Backup |
| RunUpdateJob | heavy | app/Jobs/RunUpdateJob.php | Runs UpdateService for site(s) |
| IntegrityCheckJob | heavy | app/Jobs/IntegrityCheckJob.php | Runs IntegrityCheckService for a site |
| RunWpCronJob | default | app/Jobs/RunWpCronJob.php | wp cron event run --due-now for one site (sites have DISABLE_WP_CRON) |
| RunHealthcheckJob | default | app/Jobs/RunHealthcheckJob.php | Runs HealthcheckService for site(s) |
| PurgeSiteCacheJob | default | app/Jobs/PurgeSiteCacheJob.php | Purges page cache + nginx + Cloudflare, warms origin |
| IssueCertificateJob | default | app/Jobs/IssueCertificateJob.php | Let's Encrypt certificate for a customer domain |
| SyncCloudflareDnsJob | default | app/Jobs/SyncCloudflareDnsJob.php | Syncs Cloudflare A records |
| SyncSftpUserJob | default | app/Jobs/SyncSftpUserJob.php | Creates/updates/removes an SFTP user and jail |
| CheckDomainWhoisJob | default | app/Jobs/CheckDomainWhoisJob.php | Domain WHOIS checks (if used) |
Schedule (routes/console.php)
| Command | Schedule |
|---|---|
wphoster:run-backups db | Daily at 02:00 |
wphoster:run-backups uploads | Daily at 03:00 |
wphoster:purge-expired-backups | Daily at 04:00 |
wphoster:run-healthchecks | Every 10 minutes |
wphoster:run-wp-cron | Every wphoster.wp_cron.interval_minutes minutes (default 2, */2 * * * *), withoutOverlapping |
wphoster:run-wp-cron --every-minute-sites | The minutes in between, only for sites with "Run WP-Cron every minute" (metadata.wp_cron.every_minute) |
wphoster:monitor-queues | Every minute |
wphoster:sync-sftp | Every 5 minutes |
wphoster:run-updates --staging-first | Daily at 01:00 |
wphoster:rebuild-file-manifest --all --only-missing | Daily at 05:45 |
wphoster:run-integrity-checks | Daily at 06:00 |
wphoster:collect-metrics | Hourly (disk/DB + php-fpm max_children hits per site) |
wphoster:check-domains-whois | Daily at 05:00 |
wphoster:send-renewal-reminders | Mondays at 09:00 |
WP-Cron interval
WordPress core and plugin cron events are hourly, twice-daily or daily, and WordPress itself only runs them "on the next page load after they are due", so a 2-minute grain changes nothing visible while halving the ~17k jobs/day that one-minute ticks produced on 13 sites. Shops that rely on one-off timers (order-email retries, abandoned-cart reminders, Action Scheduler) can opt into every-minute runs on their edit page. Change the fleet interval with WP_HOSTER_WP_CRON_INTERVAL_MINUTES; above 5 minutes busy shops start to fall behind.
Artisan commands
Commands live under app/Console/Commands/. Key ones:
wphoster:run-backups db|uploads— Run database or uploads backups for all siteswphoster:run-healthchecks— Run healthcheckswphoster:run-updates --staging-first— Run updates (staging before production)wphoster:run-integrity-checks— Core/plugin/theme checksums, manifest diff, webshell signatureswphoster:rebuild-file-manifest {site}|--all— Re-hash a site's plugins/themes/config into the file manifestwphoster:purge-expired-backups— Delete expired backup records/fileswphoster:verify-backups— Verify backup integritywphoster:collect-metrics— Collect disk/DB metrics and php-fpmmax_childrenhits (24h) per sitewphoster:monitor-queues— Sample Redis queue depths; raise/resolvequeue_backlogwphoster:run-wp-cron [--site=] [--every-minute-sites]— Dispatch WP-Cron runswphoster:regenerate-nginx-configs— Regenerate Nginx configs for all siteswphoster:regenerate-php-fpm-pools [--site=] [--dry-run]— Re-render per-site PHP-FPM pools (defaultpm.max_children6, per-site override 2–16 from the site's Capacity settings) and reload php-fpm once afterphp-fpm8.3 -t
Ensure the scheduler runs (e.g. php artisan schedule:work in dev or cron * * * * * php /path/artisan schedule:run in production) and queue workers are running (in development one php artisan queue:work --queue=heavy,default covers both queues; in production see docs/DEPLOYMENT.md).