Muhammad Yasir Amin

Loading websiteโ€ฆ

More from Muhammad Yasir Amin

Build Along With Me

Practical walkthroughs on Laravel, React, APIs and deployment workflows.

Subscribe on YouTube

How to Restrict User Access by IP Address in Laravel 13 with Middleware

๐Ÿ› ๏ธ Laravel Tutorials5 min read

How to Restrict User Access by IP Address in Laravel 13 with Middleware

Muhammad Yasir Amin

Muhammad Yasir Amin

Senior Full Stack Engineerโ€ขNov 12, 2026
How to Restrict User Access by IP Address in Laravel 13 with Middleware

To restrict user access by IP address in Laravel 13, create a middleware that compares $request->ip() with an allowlist and aborts with a 403 response when the address is not on it. Register the middleware as an alias in bootstrap/app.php and attach it to the routes you want to protect, such as an admin panel or an internal dashboard.

In this guide I build a small, config-driven allowlist that supports single addresses and CIDR ranges, works correctly behind a load balancer, and is covered by feature tests. The same pattern works for blocklists with a one-line change.

Prerequisites

  • Laravel 13.x (requires PHP 8.3 or newer; PHP 8.4 and 8.5 are also supported).
  • A route or group of routes you want to lock down, for example /admin.
  • Know whether your app runs behind a proxy, load balancer or CDN. This decides how the client IP is detected.

Step 1: Store the allowed IPs in configuration

Hard-coding addresses inside the middleware makes every change a deployment. I prefer a small config file that reads a comma-separated environment variable. Create config/access.php:

JavaScript
<?php

return [
    // Comma-separated list, e.g. "203.0.113.10,198.51.100.0/24"
    'admin_ips' => array_values(array_filter(array_map(
        'trim',
        explode(',', (string) env('ADMIN_ALLOWED_IPS', '127.0.0.1'))
    ))),
];

Then add the variable to .env:

Code
ADMIN_ALLOWED_IPS="127.0.0.1,203.0.113.10,198.51.100.0/24"

Reading env() only inside config files matters: once you run php artisan config:cache in production, env() calls outside config files return null.

Step 2: Create the middleware

Generate the class with Artisan:

Code
php artisan make:middleware RestrictIpAddress

Open app/Http/Middleware/RestrictIpAddress.php and replace its contents. I use Symfony's IpUtils::checkIp(), which ships with Laravel and understands both exact addresses and CIDR notation for IPv4 and IPv6:

JavaScript
<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\IpUtils;
use Symfony\Component\HttpFoundation\Response;

class RestrictIpAddress
{
    /**
     * Allow the request only when the client IP is in the named list.
     */
    public function handle(Request $request, Closure $next, string $list = 'admin_ips'): Response
    {
        $allowed = config("access.{$list}", []);
        $ip = $request->ip();

        if ($ip === null || ! IpUtils::checkIp($ip, $allowed)) {
            abort(403, 'Access from your network is not allowed.');
        }

        return $next($request);
    }
}

The optional $list parameter lets you keep several allowlists (for example one for the admin area and one for a metrics endpoint) and choose one per route. The check returns abort(403) early, so no controller code, database query or session work runs for a blocked visitor. A null IP is treated as blocked, which keeps the middleware safe in unusual environments such as some CLI-driven requests.

Step 3: Register a middleware alias

Laravel 13 configures middleware in bootstrap/app.php. Add a short alias inside withMiddleware:

JavaScript
use App\Http\Middleware\RestrictIpAddress;
use Illuminate\Foundation\Configuration\Middleware;

->withMiddleware(function (Middleware $middleware): void {
    $middleware->alias([
        'ip.allow' => RestrictIpAddress::class,
    ]);
})

Step 4: Protect your routes

Apply the alias to a route group in routes/web.php. Parameters follow a colon:

CSS
use App\Http\Controllers\Admin\DashboardController;
use Illuminate\Support\Facades\Route;

Route::middleware(['auth', 'ip.allow:admin_ips'])
    ->prefix('admin')
    ->name('admin.')
    ->group(function () {
        Route::get('/', [DashboardController::class, 'index'])->name('dashboard');
    });

If the whole application should be private, for example a staging site, append the middleware globally with $middleware->append(RestrictIpAddress::class); instead.

How do I get the real client IP behind a proxy or load balancer?

Behind a proxy, $request->ip() returns the proxy's address unless you tell Laravel to trust it. Without that, every visitor looks the same and the allowlist either blocks everyone or lets everyone in. Configure trusted proxies in bootstrap/app.php:

PHP
->withMiddleware(function (Middleware $middleware): void {
    $middleware->trustProxies(at: [
        '10.0.0.0/8', // your load balancer's private range
    ]);
})

Only trust proxies you control. Trusting '*' on a server that is directly reachable lets anyone spoof the X-Forwarded-For header and bypass the restriction.

Can I block specific IPs instead of allowing them?

Yes. Flip the condition and read from a different list:

PHP
if ($ip !== null && IpUtils::checkIp($ip, config('access.blocked_ips', []))) {
    abort(403);
}

For abuse protection, though, rate limiting with the built-in throttle middleware is usually a better first tool, because attackers change addresses easily.

Common mistakes

  • Using env() inside the middleware. It breaks after config:cache. Always go through config().
  • Forgetting trusted proxies. On Cloudflare, AWS ELB or a Docker reverse proxy you will see the proxy IP, not the user.
  • Locking yourself out. Keep 127.0.0.1 or your VPN range in the list and test on staging first.
  • Treating IP checks as authentication. IP restriction is an extra layer. Keep the auth middleware on admin routes too.
  • Ignoring IPv6. If your server accepts IPv6, add the IPv6 address or range of your office or VPN as well.

How to verify it works

Write a feature test. Laravel's test client lets you set REMOTE_ADDR, so you can simulate allowed and blocked visitors. With Pest:

JavaScript
<?php

use App\Models\User;

beforeEach(function () {
    config(['access.admin_ips' => ['203.0.113.10', '198.51.100.0/24']]);
});

it('allows an address from the list', function () {
    $this->actingAs(User::factory()->create())
        ->withServerVariables(['REMOTE_ADDR' => '198.51.100.42'])
        ->get('/admin')
        ->assertOk();
});

it('blocks an unknown address', function () {
    $this->actingAs(User::factory()->create())
        ->withServerVariables(['REMOTE_ADDR' => '192.0.2.1'])
        ->get('/admin')
        ->assertForbidden();
});

Run php artisan test. You can also run php artisan route:list --path=admin to confirm the ip.allow middleware is attached to the routes.

FAQ

Does this work with CIDR ranges?

Yes. IpUtils::checkIp() accepts entries like 198.51.100.0/24 or 2001:db8::/32 alongside single addresses.

Should I restrict IPs in Laravel or in the web server?

Both are valid. Nginx or a firewall blocks requests earlier and cheaper. Laravel middleware is easier to test, can be applied per route and lives in your codebase.

Which status code should a blocked user get?

403 Forbidden is the standard choice. Some teams return 404 to avoid revealing that the route exists; abort(404) does that.

References

Article Index & Topic Tags
LaravelSecurityMiddlewareIp-Address

YouTube Channel

Build Along With Me

Practical walkthroughs on Laravel, React, APIs and deployment workflows.

Muhammad Yasir Amin
Available for Contracts
Author & Solutions Architect

Senior Full Stack Engineer

Independent Senior Full Stack Engineer working on Laravel, React and Next.js applications, APIs and delivery workflows.

Laravel & PHP ApplicationsReact & Next.js InterfacesREST APIs & IntegrationsDatabase Queries & DebuggingDocker Development WorkflowsClear Scope & Handover
Discuss Your Next Step

What Does Your Application Need Next?

Share your current system, the problem you want to solve and what a useful result looks like. We can identify the right starting point and agree on a practical scope.

Technical Audit

Find the Next Useful Fix

Application Development

Scoped, Reviewable Milestones

Ongoing Support

Agreed Capacity and Priorities

Share your goals, current stack and requirements before the call.