# Laravel 13.35's Scheduler Upgrades: One Flag, Safe Interrupts

By Dinesh Uprety
Published: 2026-10-16
Updated: 2026-10-16
Canonical: https://laranepal.com/blog/laravel-1335s-scheduler-upgrades-one-flag-safe-interrupts


Laravel 13.35 landed on October 6 — a weekly minor, but one with genuinely practical upgrades for apps running in production. A new video walks through the highlights: QUERY route support plus two scheduler improvements that make multi-server fleets and deploys calmer. This article goes deep on the scheduler pair, because that's where the real day-to-day wins are.

---

## 1. `Schedule::alwaysOnOneServer()` — one flag for the whole fleet

If you run `schedule:run` on more than one server, every scheduled task runs once per server. That's fine for clearing a local cache. It's a disaster for the daily report email that just went out three times.

Laravel has had `onOneServer()` for years — the first server to start the task grabs a cache lock, and the rest skip it for that run:

```php
Schedule::command('reports:send')->daily()->onOneServer();
```

The problem was never the API. It was remembering it. Every new task needed the call, and the one you forgot only announced itself when the duplicates showed up in production. The workaround was wrapping the whole schedule in a group:

```php
Schedule::onOneServer()->group(function () {
    Schedule::command('reports:send')->daily();
    Schedule::command('invoices:generate')->monthly();
});
```

13.35 gives you a global switch. Drop it in a service provider and every task in the schedule gets `onOneServer()` behavior:

```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 — including ones added after the call in `routes/console.php` — are still covered. It's off by default, and `Schedule::alwaysOnOneServer(false)` turns it back off.

Three caveats worth knowing:

- **Shared cache is mandatory.** Like `onOneServer()`, it needs a cache store every server can reach (Redis, Memcached, DynamoDB…). File cache on each box won't cut it.
- **Unnamed closure tasks are skipped.** The lock is built from the task's name, so a closure without one can't participate — give it a name if you want it covered.
- **It's all-or-nothing.** If one task genuinely needs to run per server (local cache clearing), you'll need to restructure around that.

Bonus: `schedule:list --json` now includes an `onOneServer` field, so you can audit which tasks hold locks straight from the terminal.

---

## 2. Long-running commands can finally hear `schedule:interrupt`

Deploys and long-running scheduled commands have always been awkward together. `php artisan schedule:interrupt` tells `schedule:run` to stop its sub-minute loop — useful when a deploy is rolling out — but a command that was already running never saw the signal. It just kept churning, sometimes against half-deployed code.

13.35 changes the mechanism. The interrupt is now stored as a **timestamp** — the same trick `queue:restart` has used for years — and a new method lets your command check it at safe points:

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

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

    foreach ($this->batches() as $batch) {
        if ($schedule->hasBeenInterruptedSince($startedAt)) {
            $this->info('Deploy interrupt received — stopping at a safe point.');
            return;
        }

        $this->process($batch);
    }
}
```

The pattern is simple: record when the command started, then ask "has anyone interrupted the schedule since then?" at points where stopping is safe — between batches, between pages of a cursor, between files. The command finishes its current unit of work and exits cleanly instead of being killed mid-write.

One subtle fix rides along with this: `schedule:run` itself now uses the same timestamp check, so an interrupt fired earlier in the minute no longer stops a fresh run that starts after it.

---

![](https://laranepal.com/storage/pef2xqsC2GQpNdXXDxGH4cqJgKGM5ldzpe08oReZ.png)

## 3. Quick corrections and extras from 13.35

A few details from this release deserve a second look:

- **`queue:work --memory=75%` is a percentage of PHP's `memory_limit`, not the machine's RAM.** With `memory_limit` at `512M`, the worker restarts after a job once it crosses roughly 307 MB. Watch out: the PHP CLI often ships with `memory_limit` set to `-1`, where the percentage resolves to a negative number and the worker never stops for memory. Set `memory_limit` explicitly on the worker process if you use a percentage.
- **Model `defaults()` in Laravel 13 needs the `HasDefaultAttributes` trait** to run — it's opt-in, not a plain method override. Values from `defaults()` win over `$attributes`, and they're raw: they skip casts and mutators.
- **`response()->markdown($post->markdown)`** is new — it returns a `text/markdown` response and pairs with the request's existing `wantsMarkdown()` check. Handy for LLM-friendly content endpoints.
- **`lazy_root_creation`** on local disks delays creating the root directory until the first file is actually written, instead of when the disk is built.

---

## ⬆️ Upgrading

Same as always for a weekly minor:

```bash
composer update laravel/framework
```

No breaking changes, everything opt-in. If you're on the 13.x line, 13.35 is the boring, safe kind of upgrade — the best kind.

---

**Sources:** [Laravel News — Running Laravel's Scheduler on Multiple Servers](https://laravel-news.com/laravel-scheduler-multiple-servers), [Laravel News — Laravel 13.35](https://laravel-news.com/laravel-13-35-0), [laravel/framework v13.35.0 changelog](https://github.com/laravel/framework/blob/HEAD/CHANGELOG.md)
