sinakl / laravel-inline-scheduler
Run Laravel's task scheduler on hosts where proc_open is disabled (common on shared hosting/cPanel), with zero changes to your existing schedule() definitions.
Package info
github.com/sinakl/laravel-inline-scheduler
pkg:composer/sinakl/laravel-inline-scheduler
Requires
- php: ^8.2
- illuminate/console: ^11.0|^12.0
- illuminate/contracts: ^11.0|^12.0
- illuminate/support: ^11.0|^12.0
Requires (Dev)
- orchestra/testbench: ^9.0|^10.0
- phpunit/phpunit: ^10.5|^11.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-09-23 11:29:10 UTC
README
Run Laravel's task scheduler on hosts where proc_open is disabled — most commonly shared hosting with a cPanel cron job — without changing a single line of your existing schedule() definitions.
The problem
On many shared hosts, proc_open (and proc_close) are disabled for security reasons. Laravel's scheduler relies on it: every time you write
$schedule->command('inventory:sync')->daily();
schedule:run spawns that command as a separate subprocess via Symfony's Process, which needs proc_open. Without it, you get:
The Process class relies on proc_open, which is not available on your PHP installation
and none of your ->command(...) scheduled tasks run — only ->call(...) closures do, since those already run in-process.
The usual workarounds are to ask your host to enable proc_open (often refused), rewrite every scheduled task from ->command() to ->call(fn () => Artisan::call(...)) by hand, or write a one-off script that manually re-implements command dispatch. All three either aren't in your control or require maintaining a parallel, hand-written version of your schedule.
What this package does
It swaps Laravel's Schedule/Event classes for two subclasses:
InlineSchedule— identical to Laravel'sSchedule, exceptcommand()/exec()build anInlineEventinstead of the stockEvent.InlineEvent— identical to Laravel'sEvent, except the one protected method that shells out toProcess::fromShellCommandline(...)is overridden to run the command in-process viaArtisan::call()instead.
Everything else — withoutOverlapping(), before()/after()/onSuccess()/onFailure(), sendOutputTo(), environment/maintenance checks, onOneServer() — is inherited unchanged from Laravel's own classes, so it keeps working exactly as documented.
The package's service provider replaces the framework's Schedule::class container binding with one that builds an InlineSchedule and feeds it through your app's own Kernel::schedule() method (or, on Laravel 11+ apps using routes/console.php, lets the Schedule facade populate it the normal way). Either way, your scheduling code is untouched.
Net effect: composer require this package, and your existing cron entry —
* * * * * php /home/youruser/your-project/artisan schedule:run
— just starts working, even with proc_open disabled. schedule:list and schedule:test work too, since they go through the same Schedule::class binding.
Note: You don't need to append
>> /dev/null 2>&1(or any other shell redirection) to this command — it has nothing to do with how this package works, it's just a common convention for silencing cron's output emails. Some cPanel hosts even reject it outright with a "Command chaining operators are not allowed" error. If you want to silence cron's output notifications, use your host's own cron email setting (e.g. cPanel's Cron Jobs → Cron Email field) instead of shell operators.
Installation
composer require sinakl/laravel-inline-scheduler
Nothing else to configure. The service provider is auto-discovered.
Caveats
- Shared process, not isolated processes. Every due command now runs sequentially inside the same PHP process instead of its own subprocess. A command that leaks state (a stuck DB transaction, a mutated singleton,
config()changes) could in principle affect the next command in the same run. This is rare in practice, but it's the one real tradeoff for working aroundproc_open. runInBackground()runs synchronously instead. True backgrounding needs a detached subprocess, which is exactly what we're avoiding. Backgrounded events still run, just not concurrently with the rest of the schedule.- Non-Artisan
exec()calls still needproc_open. If you call$schedule->exec('some-other-binary --flag')directly (not->command()), there's no Artisan command to run in-process, so it falls back to the normal subprocess path — the same as Laravel's default behavior today, not a regression. - Requires Laravel 11 or 12. The package overrides a protected method (
Event::execute()) that was introduced in this form for Laravel 11+. Earlier versions structure that method differently and aren't supported yet.
Requirements
- PHP 8.2+
- Laravel 11.x or 12.x
Testing
composer install ./vendor/bin/phpunit
The test suite boots a real Laravel app via Orchestra Testbench and verifies, against the actual framework classes, that: the Schedule::class binding resolves to InlineSchedule; ->command() produces an InlineEvent; running that event executes the target command's handle() inside the same PHP process (a real subprocess couldn't mutate this process's static state, which is what the test checks); exit codes, onFailure(), sendOutputTo(), withoutOverlapping(), and a legacy Kernel::schedule() method all behave exactly as they do with stock Laravel.
License
MIT