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 with 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 with alwaysOnOneServer() and hasBeenInterruptedSince()
=======================================================================================================

 Laravel 13.35 adds Schedule::alwaysOnOneServer() to prevent duplicate task execution 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 12 Oct 2026 · 4 min read

ShareCopy linkCopied

 ![Running Laravel's Scheduler on Multiple Servers with alwaysOnOneServer() and hasBeenInterruptedSince()](https://cdn.msaied.com/768/6b65210b14d1b96e0fcbe3020ad587a1.png) 

  On this page +1. [The Problem with Running schedule:run on Multiple Servers](#the-problem-with-running-codescheduleruncode-on-multiple-servers)
2. [Schedule::alwaysOnOneServer() — Apply the Lock Globally](#schedulealwaysononeserver-apply-the-lock-globally)
3. [Name Your Closure Tasks](#name-your-closure-tasks)
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 with Running `schedule:run` on Multiple Servers
-----------------------------------------------------------

When you run `php artisan schedule:run` on more than one server, every scheduled task fires once per server. That is fine for a task that clears a local file cache, but it is a real problem for a task that sends a daily report email — your users end up receiving it multiple times.

Laravel has long offered `onOneServer()` to handle this by taking a distributed cache lock, but you had to remember to add it to every task. Miss one and duplicates only surface in production. **Laravel 13.35** ships two improvements that close these gaps.

Schedule::alwaysOnOneServer() — Apply the Lock Globally
-------------------------------------------------------

Contributed by [Jack Bayliss](https://github.com/jackbayliss), `Schedule::alwaysOnOneServer()` applies the single-server constraint to every task in your schedule without touching each task definition individually.

Call it once in your `AppServiceProvider`:

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

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

```

The flag is evaluated when the schedule reads its events, so tasks defined later in `routes/console.php` are covered automatically. It is off by default, and you can disable it again with `Schedule::alwaysOnOneServer(false)`.

### Name Your Closure Tasks

The lock key is derived from the task name. Artisan commands have a name automatically; closure tasks do not unless you call `name()`.

```php
// Runs on every server — no name, lock is skipped
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();

```

Unnamed closures are silently skipped by `alwaysOnOneServer()` with no warning, so audit your closure tasks when you enable 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). If your default `CACHE_STORE` is local to each server (e.g., `file`), point the scheduler at a shared store:

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

```

`useCache()` only affects scheduler locks (`onOneServer()` and `withoutOverlapping()`); the rest of your application continues using its default store.

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

Contributed by [cyppe](https://github.com/cyppe), this method lets a long-running background command check whether `schedule:interrupt` was called after the command started and stop cleanly between batches.

Previously, `schedule:interrupt` only stopped the `schedule:run` loop for sub-minute tasks. Commands started with `runInBackground()` ran in their own process and never saw the interrupt signal.

Now `schedule:interrupt` stores the interrupt timestamp (mirroring how `queue:restart` works), and your command can compare that timestamp against its own start time:

```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;
    }
}

```

```php
Schedule::command('inventory:sync')->hourly()->runInBackground();

```

`hasBeenInterruptedSince()` returns `true` only for an interrupt that occurred at or after the time you pass in, so a stale interrupt from a previous deploy does not stop a fresh run.

A typical deploy script now looks like:

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

```

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

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

- `Schedule::alwaysOnOneServer()` in `AppServiceProvider::boot()` prevents duplicate task execution across all servers globally.
- Unnamed closure tasks are silently excluded from the single-server lock — always call `->name()` on them.
- Every server must share the same cache store for locks to work; use `Schedule::useCache('redis')` if your default store is local.
- `Schedule::hasBeenInterruptedSince($startedAt)` lets long-running background commands stop gracefully between batches when a deploy fires `schedule:interrupt`.
- Both features shipped in Laravel 13.35 (PRs [\#61789](https://github.com/laravel/framework/pull/61789) and [\#61764](https://github.com/laravel/framework/pull/61764)).

---

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

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

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

  What is the difference between onOneServer() and alwaysOnOneServer() in Laravel?`onOneServer()` must be chained onto each individual task definition to prevent it from running on more than one server. `alwaysOnOneServer()`, introduced in Laravel 13.35, is called once on the Schedule facade (typically in AppServiceProvider::boot()) and applies the single-server constraint to every task in the schedule automatically.

   Why do unnamed closure tasks still run on every server even when alwaysOnOneServer() is enabled?The distributed cache lock key is derived from the task's name. Artisan command tasks have a name built from their command signature, but closure tasks only have a name if you explicitly call -&gt;name() on them. Because an unnamed closure has no stable lock key, alwaysOnOneServer() skips those closures and they continue to run on every server. Adding -&gt;name('your-task-name') to the closure task resolves this.

   How does Schedule::hasBeenInterruptedSince() differ from the old schedule:interrupt behavior?Before Laravel 13.35, schedule:interrupt set a short-lived flag that only the schedule:run loop checked, meaning commands started with runInBackground() never saw the signal. In 13.35, schedule:interrupt stores the interrupt timestamp instead. A long-running command can call $schedule-&gt;hasBeenInterruptedSince($startedAt) inside its processing loop; the method returns true only if an interrupt occurred after the command started, allowing the command to exit cleanly between batches.

   ![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 with Running schedule:run on Multiple Servers](#the-problem-with-running-codescheduleruncode-on-multiple-servers)
2. [Schedule::alwaysOnOneServer() — Apply the Lock Globally](#schedulealwaysononeserver-apply-the-lock-globally)
3. [Name Your Closure Tasks](#name-your-closure-tasks)
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/767/f058bebd1a0c6f504e123afc581fbd66.png) Laravel · 4 min read### Running Laravel's Scheduler on Multiple Servers with alwaysOnOneServer() and hasBeenInterruptedSince()

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

  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)
