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 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 with alwaysOnOneServer and hasBeenInterruptedSince](https://cdn.msaied.com/763/f0add725c7e924dc0417ce1d05dab9d7.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()](#schedulealwaysononeserver)
3. [Name Your Closure Tasks](#name-your-closure-tasks)
4. [Shared Cache Is Required](#shared-cache-is-required)
5. [Schedule::hasBeenInterruptedSince()](#schedulehasbeeninterruptedsince)
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 clearing a local cache, but it means a daily report email goes out multiple times. Laravel has long offered `onOneServer()` to handle this per task, but forgetting it on a single new task is enough to cause duplicates in production.

Laravel 13.35 ships two improvements that address this directly.

---

Schedule::alwaysOnOneServer()
-----------------------------

Contributed by [Jack Bayliss](https://github.com/jackbayliss), `Schedule::alwaysOnOneServer()` applies the single-server lock to every task in your schedule automatically. 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 too. 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()`. With `alwaysOnOneServer()` enabled, unnamed closures are silently skipped and still run on every server:

```php
// Runs on every server — no name, no lock
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. Every server must point to the same cache store — Redis, Memcached, DynamoDB, or the database. The `file` and `array` drivers are local to each server and will not work.

If your default `CACHE_STORE` is local (e.g., `file`), 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 scheduler locks (`onOneServer()` and `withoutOverlapping()`). The rest of your application continues using its default store.

> **Note:** If some tasks must run on every server (e.g., clearing local disk files), leave `alwaysOnOneServer()` off and call `onOneServer()` only on the tasks that should run once.

---

Schedule::hasBeenInterruptedSince()
-----------------------------------

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 old code or be killed mid-execution during a deploy.

Contributed by [cyppe](https://github.com/cyppe), the new approach stores the *time* of the interrupt (mirroring how `queue:restart` works). 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;
    }
}

```

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

```

The method returns `true` only for an interrupt that occurred *at or after* `$startedAt`, 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...
php artisan queue:restart

```

> **Important:** 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. Calling `Schedule::withoutInterruptionPolling()` disables polling, which causes `hasBeenInterruptedSince()` to always return `false`.

---

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

- `Schedule::alwaysOnOneServer()` in `AppServiceProvider::boot()` prevents duplicate task execution across all servers without touching every task definition.
- Closure tasks must have a `name()` call to participate in the single-server lock; unnamed closures are silently skipped.
- All servers must share the same cache store for locks to work; use `Schedule::useCache('redis')` if your default store is local.
- `Schedule::hasBeenInterruptedSince($startedAt)` lets background commands stop gracefully between batches when `schedule:interrupt` is called during a deploy.
- The interrupt timestamp is stored in the default cache store, which must also be shared across servers.

---

*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)
- [Multi-Server](https://www.msaied.com/articles?search=Multi-Server)
- [Task Scheduling](https://www.msaied.com/articles?search=Task%20Scheduling)

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

  What cache drivers work with Laravel's onOneServer() and alwaysOnOneServer()?You need a shared cache store that every server can access, such as Redis, Memcached, DynamoDB, or the database driver. The file and array drivers are local to each server, so each server would acquire its own lock and the task would still run on all of them.

   Why does alwaysOnOneServer() skip unnamed closure tasks?The lock key is built from the task's name. Artisan commands have a name automatically, but a closure task only has one if you call -&gt;name() on it. Because calling onOneServer() on an unnamed closure throws an exception, alwaysOnOneServer() skips those closures instead of crashing. They continue to run on every server.

   How does hasBeenInterruptedSince() differ from the old schedule:interrupt behavior?Previously, schedule:interrupt stored a simple flag that only schedule:run checked, so background commands never saw it. In Laravel 13.35 the interrupt time is stored instead, and any command can call Schedule::hasBeenInterruptedSince($startedAt) to check whether an interrupt occurred after the command started. This prevents stale interrupts from stopping new runs and allows long-running background commands 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()](#schedulealwaysononeserver)
3. [Name Your Closure Tasks](#name-your-closure-tasks)
4. [Shared Cache Is Required](#shared-cache-is-required)
5. [Schedule::hasBeenInterruptedSince()](#schedulehasbeeninterruptedsince)
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/762/8e418dc4329ddd0a05f25fbbd45cb5b7.png) Laravel · 4 min read### Running Laravel's Scheduler on Multiple Servers: alwaysOnOneServer() and hasBeenInterruptedSince()

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

  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)
