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()

   [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()
=========================================================================

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

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

ShareCopy linkCopied

 ![Running Laravel's Scheduler on Multiple Servers with alwaysOnOneServer()](https://cdn.msaied.com/760/fce56c9f7c15f70c3313c8faf2c9f4c1.png) 

  On this page +1. [The Problem: Duplicate Scheduled Tasks Across Servers](#the-problem-duplicate-scheduled-tasks-across-servers)
2. [What Laravel 13.35 Adds](#what-laravel-1335-adds)
3. [Schedule::alwaysOnOneServer()](#schedulealwaysononeserver)
4. [A Shared Cache Is Required](#a-shared-cache-is-required)
5. [Stopping Long Commands During a Deploy](#stopping-long-commands-during-a-deploy)
6. [Schedule::hasBeenInterruptedSince()](#schedulehasbeeninterruptedsince)
7. [Key Takeaways](#key-takeaways)

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

When schedule:run executes on more than one server, every scheduled task fires once per server. That is harmless for something like clearing a local cache, but it means a daily report email goes out multiple times — once per server.

Laravel has long offered onOneServer() to solve this for individual tasks. The first server to claim the task takes 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.

What Laravel 13.35 Adds
-----------------------

### Schedule::alwaysOnOneServer()

Contributed by Jack Bayliss, Schedule::alwaysOnOneServer() applies the one-server constraint to every task in the schedule automatically. Call it once in your service provider:

```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 covered — including ones added after the call. It is off by default, and Schedule::alwaysOnOneServer(false) turns it back off.

#### Name Your Closure Tasks

The lock key is built from the task's name. Artisan command tasks have one automatically; closure tasks only have one if you call name(). Without a name, alwaysOnOneServer() silently skips the closure and it runs on every server:

```php
// Runs on every server — no name
Schedule::call(fn () => Report::sendDaily())->daily();

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

```

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

### A Shared Cache Is Required

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

If your app's 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');
}

```

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

Stopping Long Commands During a Deploy
--------------------------------------

### Schedule::hasBeenInterruptedSince()

Contributed by cyppe, this method lets a long-running background command check whether schedule:interrupt was called after the command started, and exit cleanly between batches:

```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 the time you pass in, so a stale interrupt 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, etc.
php artisan queue:restart

```

The interrupt time is stored in the default cache store, not the store passed to useCache(). On a multi-server setup, the default store must be shared.

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

- Schedule::alwaysOnOneServer() replaces the need to add onOneServer() to every task individually.
- Closure tasks must have a name() call to be covered; unnamed closures are silently skipped.
- All servers must share the same cache store for locks to work; use Schedule::useCache() if the default store is local.
- Schedule::hasBeenInterruptedSince() lets long background commands exit gracefully between batches when a deploy runs schedule:interrupt.
- Both features shipped in Laravel 13.35.

- [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 one-server constraint to every scheduled task globally, so you no longer need to chain onOneServer() on each individual task. The first server to claim a task takes a cache lock; all other servers skip it for that run.

   Why do unnamed 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 call -&gt;name() on them. Without a name, alwaysOnOneServer() cannot build a lock key and silently skips the constraint, so the closure runs on every server.

   How does Schedule::hasBeenInterruptedSince() help during deployments?It lets long-running commands check whether schedule:interrupt was called after the command started. The command can call hasBeenInterruptedSince() between batches and exit cleanly when a deploy-time interrupt is detected, preventing abrupt termination mid-process.

   ![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. [What Laravel 13.35 Adds](#what-laravel-1335-adds)
3. [Schedule::alwaysOnOneServer()](#schedulealwaysononeserver)
4. [A Shared Cache Is Required](#a-shared-cache-is-required)
5. [Stopping Long Commands During a Deploy](#stopping-long-commands-during-a-deploy)
6. [Schedule::hasBeenInterruptedSince()](#schedulehasbeeninterruptedsince)
7. [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/759/bf983fc4e6fdc02c324b1620b8621706.png) Laravel · 3 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-1) 

  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)
