Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

jonathannet's avatar

Maintenance Mode in Laravel 11 ?

Hi I am trying to exclude my admin panel, when the site is in Maintenance Mode, but cannot getting it to work like in Laravel 10, and I know alot of things have changed in 11, so here is my code so far:

My boostrap\app.php

return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        using: function () {
            Route::middleware('web')
                ->group(base_path('routes/web.php'));

            Route::middleware('web')
                ->prefix('admin')
                ->group(base_path('routes/admin.php'));
        },
        commands: __DIR__.'/../routes/console.php',
    )
    ->withMiddleware(function (Middleware $middleware) {
        $middleware->append(PreventRequestsDuringMaintenance::class);
        $middleware->alias([
            //
        ]);
    })
    ->withExceptions(function (Exceptions $exceptions) {
        //
    })->create();

and then my PreventRequestsDuringMaintenance class:

    /**
     * Handle an incoming request.
     *
     * @param  \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response)  $next
     */
    public function handle(Request $request, Closure $next): Response
    {
        $exceptions = [
            '/admin*',
            '/admin/*',
        ];

        if (app()->isDownForMaintenance() && !$request->is($exceptions)) {
            return response('Be right back!', 503);
        }
        return $next($request);
    }

But it seams like the Maintenance Mode is overriding all middlewares, hope some one can help me to get it working on Laravel 11 :)

0 likes
3 replies
jonathannet's avatar

I found out how, with Github CoPilot, so I post it here, in case any one have same problem:

I made an new ServiceProvider:

use Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance;
use Illuminate\Support\ServiceProvider;
use Closure;

class MaintenanceModeServiceProvider extends ServiceProvider
{
    /**
     * Register services.
     */
    public function register(): void
    {
        $this->app->bind(PreventRequestsDuringMaintenance::class, function ($app) {
            return new class($app) extends PreventRequestsDuringMaintenance {
                public function handle($request, Closure $next)
                {
                    $allowedIPs = config('app.allowed_ips', []);
                    $allowedRoutes = ['route1', 'route2']; // Replace with your allowed routes

                    if (in_array($request->ip(), $allowedIPs) || in_array($request->route()->getName(), $allowedRoutes)) {
                        return $next($request);
                    }

                    return parent::handle($request, $next);
                }
            };
        });
    }

    /**
     * Bootstrap services.
     */
    public function boot(): void
    {
        //
    }
}

And then I edit the bootstrap/providers.php I added:

App\Providers\MaintenanceModeServiceProvider::class,

And then I added the allowed_ips array in config/app.php

puklipo's avatar

Use standard features before customizing.

Bypassing Maintenance Mode

Only admin who know the secret can access it even in maintenance mode. That's all you need. https://laravel.com/docs/11.x/configuration#bypassing-maintenance-mode

[Another way] except path and IP restriction

// bootstrap/app.php

    ->withMiddleware(function (Middleware $middleware) {
        //
        $middleware->preventRequestsDuringMaintenance(except: [
            'admin*',
        ]);
    })
// AppServiceProvider

use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Gate;

    public function boot(): void
    {
        Gate::define('admin-panel', function (Request $request, User $user) {
            return in_array($request->ip(), [
                '127.0.0.1',
                '',
            ]);

            // better
            return in_array($user->id, [
                1,
                2,
            ]);

           return $user->isAdmin();
        });
    }
// routes/

Route::get('admin', function () {
    //
})->can('admin-panel');
1 like
rubens2009's avatar

I did it in a more customizable way, based on a solution I used in L10.

In my case I use akaunting/laravel-setting, and in my admin panel I have fields to specify IP addresses and specific URLs that I can choose not to be affected by Maintenance Mode.

Create a middleware

php artisan make:middleware PreventRequestsDuringMaintenance

Here I manually define that Laravel uses my custom Middleware instead of the default one.

//bootstrap/app.php

->withMiddleware(function (Middleware $middleware) {
        $middleware->use([
            // \Illuminate\Http\Middleware\TrustHosts::class,
            \Illuminate\Http\Middleware\TrustProxies::class,
            \Illuminate\Http\Middleware\HandleCors::class,
            \App\Http\Middleware\PreventRequestsDuringMaintenance::class,
            \Illuminate\Http\Middleware\ValidatePostSize::class,
            \Illuminate\Foundation\Http\Middleware\TrimStrings::class,
            \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
        ]);
    })

App\Http\Middleware\PreventRequestsDuringMaintenance.php

namespace App\Http\Middleware;

use Closure;
use ErrorException;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\IpUtils;
use Symfony\Component\HttpKernel\Exception\HttpException;

class PreventRequestsDuringMaintenance extends \Illuminate\Foundation\Http\Middleware\PreventRequestsDuringMaintenance
{
    /**
     * Custom Allowed IP's
     * @var array
     */
    protected $excluded_ips = [];

    /**
     * Create a new middleware instance.
     *
     * @param Application $app
     * @return void
     */
    public function __construct(Application $app)
    {
        $this->app = $app;

        $urls_allowed = collect(setting('maintenance_excluded_uris'))
            ->mapWithKeys(function ($uri) {
                return explode(',', $uri);
            })->toArray();

        $ips_allowed = collect(setting('maintenance_excluded_ips'))
            ->mapWithKeys(function ($ip) {
                return explode(',', $ip);
            })->toArray();

        // Auto add backend to excluded URIs
        $this->except[] = '/' . config('mycustomconfig.backend_prefix') . '*';
        $this->except = array_merge($this->except, $urls_allowed);
        $this->excluded_ips = $ips_allowed;
    }

    /**
     * Handle an incoming request.
     *
     * @param  Request  $request
     * @param Closure $next
     * @return mixed
     *
     * @throws HttpException
     * @throws ErrorException
     */
    public function handle($request, Closure $next)
    {
        if ($this->inExceptArray($request)) {
            return $next($request);
        }

        if ($this->app->maintenanceMode()->active()) {
            try {
                $data = $this->app->maintenanceMode()->data();
            } catch (ErrorException $exception) {
                if (! $this->app->maintenanceMode()->active()) {
                    return $next($request);
                }

                throw $exception;
            }

            if (isset($data['secret']) && $request->path() === $data['secret']) {
                return $this->bypassResponse($data['secret']);
            }

            if ($this->hasValidBypassCookie($request, $data) || $this->inExceptArray($request) ||
                $this->inExceptIpArray($request)) {
                return $next($request);
            }

            if (isset($data['redirect'])) {
                $path = $data['redirect'] === '/'
                    ? $data['redirect']
                    : trim($data['redirect'], '/');

                if ($request->path() !== $path) {
                    return redirect($path);
                }
            }

            if (isset($data['template'])) {
                return response(
                    $data['template'],
                    $data['status'] ?? 503,
                    $this->getHeaders($data)
                );
            }

            throw new HttpException(
                $data['status'] ?? 503,
                'Service Unavailable',
                null,
                $this->getHeaders($data)
            );
        }

        return $next($request);
    }

    /**
     * @param $request
     * @return bool
     */
    private function inExceptIpArray($request)
    {
        // Check IP's on settings
        return isset($this->excluded_ips) && IpUtils::checkIp($request->ip(), (array)$this->excluded_ips);
    }

If anyone has any corrections or improvements, please comment.

Please or to participate in this conversation.