Laravel Scheduler on Multiple Servers (Laravel 13.35) | Mohamed Said       [Skip to content](#main)  [ ![](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png) Mohamed SaidLaravel Backend Engineer ](https://www.msaied.com) - [Home](https://www.msaied.com)
- [Projects](https://www.msaied.com/projects)
- [Articles](https://www.msaied.com/articles)
- [Certificates](https://www.msaied.com/certificates)
- [About](https://www.msaied.com#about)

           [  Contact](https://www.msaied.com#contact) Menu 

Menu
----

Close 

 - [HomeStart here](https://www.msaied.com)
- [ProjectsCase studies](https://www.msaied.com/projects)
- [ArticlesEngineering notes](https://www.msaied.com/articles)
- [CertificatesCredentials](https://www.msaied.com/certificates)
- [AboutHow I work](https://www.msaied.com#about)
- [ContactGet in touch](https://www.msaied.com#contact)

  [Start a conversation](https://www.msaied.com#contact) [WhatsApp](https://wa.me/201094619204) [Email](mailto:hello@msaied.com) 

 1. [Home](https://www.msaied.com)
2. /
3. [Articles](https://www.msaied.com/articles)
4. /
5. [Laravel](https://www.msaied.com/articles?category=laravel)
6. /
7. Running Laravel's Scheduler on Multiple Servers: alwaysOnOneServer() and hasBeenInterruptedSince()

   [Laravel](https://www.msaied.com/articles?category=laravel) [Tips &amp; Tricks](https://www.msaied.com/articles?category=tips-tricks) 

 Running Laravel's Scheduler on Multiple Servers: alwaysOnOneServer() and hasBeenInterruptedSince()
===================================================================================================

 Laravel 13.35 adds Schedule::alwaysOnOneServer() to prevent duplicate task runs across servers, and Schedule::hasBeenInterruptedSince() so long-running commands can stop cleanly during a deploy.

 ![](https://cdn.msaied.com/01M22N44A70A5MC2S599JP0MPH.webp) [Mohamed Said](https://www.msaied.com#person) Published 8 Oct 2026 · Updated 10 Oct 2026 · 4 min read

ShareCopy linkCopied

 ![Running Laravel's Scheduler on Multiple Servers: alwaysOnOneServer() and hasBeenInterruptedSince()](https://cdn.msaied.com/762/8e418dc4329ddd0a05f25fbbd45cb5b7.png) 

  On this page +1. [The Problem: Duplicate Scheduled Tasks Across Servers](#the-problem-duplicate-scheduled-tasks-across-servers)
2. [Schedule::alwaysOnOneServer() — New in Laravel 13.35](#schedulealwaysononeserver-new-in-laravel-1335)
3. [Closure Tasks Must Have a Name](#closure-tasks-must-have-a-name)
4. [Shared Cache Is Required](#shared-cache-is-required)
5. [Schedule::hasBeenInterruptedSince() — Graceful Deploy Interrupts](#schedulehasbeeninterruptedsince-graceful-deploy-interrupts)
6. [Key Takeaways](#key-takeaways)

 The Problem: Duplicate Scheduled Tasks Across Servers
-----------------------------------------------------

When you run `schedule:run` on more than one server, every scheduled task fires once per server. That is fine for clearing a local cache, but it is a real problem for tasks like sending a daily report email — you do not want that going out three times.

Laravel has long offered `onOneServer()` to handle this. The first server to start the task acquires a cache lock; the others skip it. The catch is that you have to remember to add it to every task, and a missed call only surfaces when the duplicates appear in production.

Schedule::alwaysOnOneServer() — New in Laravel 13.35
----------------------------------------------------

[Laravel 13.35](https://laravel-news.com/laravel-13-35-0) ships `Schedule::alwaysOnOneServer()`, contributed by [Jack Bayliss](https://github.com/jackbayliss). Call it once in your service provider and every task in the schedule is automatically covered:

```php
// app/Providers/AppServiceProvider.php
use Illuminate\Support\Facades\Schedule;

public function boot(): void
{
    Schedule::alwaysOnOneServer();
}

```

The flag is applied when the schedule reads its events, so tasks defined later in `routes/console.php` are included — even ones added after the call. Pass `false` to turn it back off.

### Closure Tasks Must Have a Name

`onOneServer()` builds its cache lock key from the task's name. Artisan commands have one automatically; closure tasks do not unless you call `name()`. With `alwaysOnOneServer()` enabled, unnamed closures are silently skipped and still run on every server:

```php
// Runs on every server — no name, so the lock cannot be keyed
Schedule::call(fn () => Report::sendDaily())->daily();

// Runs on one server — name provides the lock key
Schedule::call(fn () => Report::sendDaily())
    ->name('send-daily-report')
    ->daily();

```

There is no warning when a closure is skipped, so audit your closure tasks before enabling the flag.

### Shared Cache Is Required

The lock lives in the cache, so every server must share the same store — Redis, Memcached, DynamoDB, or the database driver. The `file` and `array` stores are local to each server and will not work.

If your default cache store is local, point the scheduler at a shared store with `Schedule::useCache()`:

```php
public function boot(): void
{
    Schedule::alwaysOnOneServer();
    Schedule::useCache('redis'); // named store from config/cache.php
}

```

`useCache()` only affects the scheduler's locks (`onOneServer()` and `withoutOverlapping()`). The rest of your application keeps using its default store.

Schedule::hasBeenInterruptedSince() — Graceful Deploy Interrupts
----------------------------------------------------------------

Before 13.35, `php artisan schedule:interrupt` set a flag that only `schedule:run` checked. A command started with `runInBackground()` ran in its own process and never saw the flag, so it could finish on stale code or be killed mid-run.

[cyppe](https://github.com/cyppe) changed `schedule:interrupt` to store the interrupt *time* instead — the same pattern `queue:restart` uses. A long-running command can now check `Schedule::hasBeenInterruptedSince($startedAt)` between batches and exit cleanly:

```php
use Illuminate\Console\Command;
use Illuminate\Console\Scheduling\Schedule;

class SyncInventory extends Command
{
    protected $signature = 'inventory:sync';

    public function handle(Schedule $schedule): int
    {
        $startedAt = now();

        foreach (Product::lazyById(500) as $product) {
            if ($schedule->hasBeenInterruptedSince($startedAt)) {
                $this->info('Interrupted by a deploy, stopping.');
                return self::SUCCESS;
            }

            $this->syncProduct($product);
        }

        return self::SUCCESS;
    }
}

```

The method returns `true` only for an interrupt that occurred *at or after* `$startedAt`, so a signal from an earlier deploy does not stop a fresh run.

A typical deploy script now looks like this:

```bash
php artisan schedule:interrupt
# switch release, run migrations…
php artisan queue:restart

```

> **Note:** The interrupt time is stored in the *default* cache store, not the one passed to `useCache()`. If you call `Schedule::withoutInterruptionPolling()`, `hasBeenInterruptedSince()` always returns `false`.

Key Takeaways
-------------

- `Schedule::alwaysOnOneServer()` applies `onOneServer()` globally — no more per-task boilerplate.
- Unnamed closure tasks are silently skipped; always call `->name()` on closures.
- All servers must share a cache store (Redis, database, etc.) for locks to work correctly.
- `Schedule::useCache('redis')` scopes the shared store to the scheduler only.
- `hasBeenInterruptedSince($startedAt)` lets background commands exit cleanly between batches on deploy.
- Store the interrupt time before starting the loop so the check is accurate for the current run.

---

*Source: [Running Laravel's Scheduler on Multiple Servers — Laravel News](https://laravel-news.com/laravel-scheduler-multiple-servers)*

- [Laravel](https://www.msaied.com/articles?search=Laravel)
- [Scheduler](https://www.msaied.com/articles?search=Scheduler)
- [Laravel 13.35](https://www.msaied.com/articles?search=Laravel%2013.35)
- [Multiple Servers](https://www.msaied.com/articles?search=Multiple%20Servers)
- [Task Scheduling](https://www.msaied.com/articles?search=Task%20Scheduling)

 Frequently asked questions 
---------------------------

  What does Schedule::alwaysOnOneServer() do in Laravel 13.35?It applies the onOneServer() constraint to every scheduled task globally, so you no longer need to add it to each task individually. The first server to start a task acquires a cache lock and the other servers skip it for that run.

   Why do closure tasks still run on every server even with alwaysOnOneServer() enabled?The lock key is derived from the task's name. Closure tasks have no name unless you explicitly call -&gt;name() on them. Without a name, alwaysOnOneServer() cannot build a lock key, so it skips those closures and they run on every server. Add -&gt;name('your-task-name') to fix this.

   How does Schedule::hasBeenInterruptedSince() differ from the old schedule:interrupt behavior?Previously, schedule:interrupt set a flag that only schedule:run checked, so background commands never saw it. In Laravel 13.35, the interrupt time is stored instead, and long-running commands can call hasBeenInterruptedSince($startedAt) between batches to detect a deploy signal and exit cleanly.

   ![Mohamed Said](https://cdn.msaied.com/01M22N44A70A5MC2S599JP0MPH.webp)About the author
----------------

[Mohamed Said](https://www.msaied.com#person)Senior Backend Engineer specializing in Laravel, scalable SaaS platforms, APIs, and cloud infrastructure. I build secure, high-performance web applications that help businesses grow.

[About](https://www.msaied.com#about) [GitHub ↗](https://github.com/EG-Mohamed) [LinkedIn ↗](https://www.linkedin.com/in/msaiedm/) [WhatsApp ↗](https://wa.me/201094619204) [Email Address ↗](mailto:hello@msaied.com) [My CV ↗](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)  

   [Previous articlePostgreSQL JSONB in Laravel: Indexing, Querying, and Casting Without the Mess](https://www.msaied.com/articles/postgresql-jsonb-in-laravel-indexing-querying-and-casting-without-the-mess) [Next articleEloquent at Scale: Chunked Iteration, Lazy Collections, and Cursor Pagination](https://www.msaied.com/articles/eloquent-at-scale-chunked-iteration-lazy-collections-and-cursor-pagination)  

   On this page
-------------

1. [The Problem: Duplicate Scheduled Tasks Across Servers](#the-problem-duplicate-scheduled-tasks-across-servers)
2. [Schedule::alwaysOnOneServer() — New in Laravel 13.35](#schedulealwaysononeserver-new-in-laravel-1335)
3. [Closure Tasks Must Have a Name](#closure-tasks-must-have-a-name)
4. [Shared Cache Is Required](#shared-cache-is-required)
5. [Schedule::hasBeenInterruptedSince() — Graceful Deploy Interrupts](#schedulehasbeeninterruptedsince-graceful-deploy-interrupts)
6. [Key Takeaways](#key-takeaways)

 ###  Have a technical challenge?

 Tell me what you’re building. I reply within two working days.

[Start a conversation](https://www.msaied.com#contact) 

   Related articles
-----------------

 [ ![](https://cdn.msaied.com/757/f990951a1a0e14b4fece312bce644c29.png)  · 4 min read### Read/Write Splitting and Sticky Reads in Laravel: A Production Guide

9 Oct 2026 ](https://www.msaied.com/articles/readwrite-splitting-and-sticky-reads-in-laravel-a-production-guide-1) [ ![](https://cdn.msaied.com/756/69efdf9ac9ec1e1f61378055f1cd6e92.png)  · 4 min read### Eloquent at Scale: Chunked Iteration, Lazy Collections, and Cursor Pagination

9 Oct 2026 ](https://www.msaied.com/articles/eloquent-at-scale-chunked-iteration-lazy-collections-and-cursor-pagination) [ ![](https://cdn.msaied.com/761/6a7f8db8e2e564efda9852eac56f6801.png) Laravel · 4 min read### Running Laravel's Scheduler on Multiple Servers with alwaysOnOneServer()

8 Oct 2026 ](https://www.msaied.com/articles/running-laravels-scheduler-on-multiple-servers-with-alwaysononeserver-3) 

  Have a technical challenge?
----------------------------

Tell me what you’re building. I reply within two working days.

 [Discuss your project ↗](https://www.msaied.com#contact) 

  © 2026 Mohamed Said · Built with Laravel, meant to last.Senior Backend Engineer specializing in Laravel, scalable SaaS platforms, APIs, and cloud infrastructure. I build secure, high-performance web applications that help businesses grow.

 - [Home](https://www.msaied.com)
- [Articles](https://www.msaied.com/articles)
- [Certificates](https://www.msaied.com/certificates)
- [GitHub](https://github.com/EG-Mohamed)
- [LinkedIn](https://www.linkedin.com/in/msaiedm/)
- [WhatsApp](https://wa.me/201094619204)
- [Email Address](mailto:hello@msaied.com)
- [My CV](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)
- [Sitemap](https://www.msaied.com/sitemap.xml)
