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

ycsm's avatar
Level 1

Laravel 8 - Hangs Intermittently every minute or so (Local and Live Server)

Hi all.

This is a problem that's been plaguing me recently. I can't seem to nail down what's causing it, it happens locally and on my live server. Here's the gist of what happens.

I perform (for example) an AJAX call. I can keep doing the same call every second and it happens as fast as you'd expect. But every 30-60 seconds the call will hang for around 30 seconds before kicking back into gear.

Note: this isn't just ajax calls, it happens with fresh page loads just the same. It also happens when accessing a route directly (not via the front end) but it is easier for me to test and show you the following video:

https://www.youtube.com/watch?v=Cg-rd8FAAog

This happens on any request, not just specific ones, it appears completely random.

The server opens the request, hangs for around 30 seconds, then eventually it will close it as seen here:

Screenshot of a artisan Screenshot of debugger 1 Screenshot of debugger 1 EDIT: The second photo of the debugger shows an intensive bit of code that I have yet to streamline. I tried it with a test case simply using the following and I'm getting the exact same 30 second-ish lag, with only one query showing on the debugbar.

public function(Request $request){
	return response()->json([], 200);
}

EDIT2: It does the same here, right inside web.php - I have removed all other routes:

<?php



use Illuminate\Support\Facades\Route;

Route::get('/greeting', function () {
    return 'Hello World';
});

I've checked on a few specific examples and none of the code executes in the controller function for the entire (roughly) 30 seconds, then, when it decides to, it just executes it all in milliseconds - like it should.

As stated above, this happens on my local machine and my live server.

  • The local machine typically hangs for roughly 30 seconds.
  • The live server is faster in general and typically hangs for 12 seconds.

I've done a composer dump-autoload and php artisan optimize but this makes no difference.

What's confusing is, the site is nice and quick when it's working - but it just randomly hangs.

Does anyone have a clue what's happening here? Have you experienced anything like this before? Is there any more debugging I can do to find out where/why it hangs?

Here is my composer file:

{
    "name": "laravel/laravel",
    "type": "project",
    "description": "The Laravel Framework.",
    "keywords": ["framework", "laravel"],
    "license": "MIT",
    "require": {
        "php": "^7.3|^8.0",
        "barryvdh/laravel-dompdf": "^2.0",
        "fruitcake/laravel-cors": "^2.0",
        "guzzlehttp/guzzle": "7.4.1",
        "intervention/image": "^2.7",
        "laravel/framework": "^8.65",
        "laravel/sanctum": "^2.11",
        "laravel/socialite": "^5.5",
        "laravel/tinker": "^2.5",
        "laravel/ui": "^3.3",
        "league/flysystem-aws-s3-v3": "^1.0",
        "league/flysystem-cached-adapter": "~1.0",
        "league/flysystem-sftp": "~1.0",
        "maatwebsite/excel": "^3.1"
    },
    "require-dev": {
        "barryvdh/laravel-debugbar": "3.6.6",
        "facade/ignition": "^2.5",
        "fakerphp/faker": "^1.9.1",
        "laravel/sail": "^1.0.1",
        "mockery/mockery": "1.4.4",
        "nunomaduro/collision": "5.10",
        "phpunit/phpunit": "9.5.10"
    },
    "autoload": {
        "psr-4": {
            "App\": "app/",
            "Database\Factories\": "database/factories/",
            "Database\Seeders\": "database/seeders/"
        }
    },
    "autoload-dev": {
        "psr-4": {
            "Tests\": "tests/"
        }
    },
    "scripts": {
        "post-autoload-dump": [
            "Illuminate\Foundation\ComposerScripts::postAutoloadDump",
            "@php artisan package:discover --ansi"
        ],
        "post-update-cmd": [
            "@php artisan vendor:publish --tag=laravel-assets --ansi"
        ],
        "post-root-package-install": [
            "@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
        ],
        "post-create-project-cmd": [
            "@php artisan key:generate --ansi"
        ]
    },
    "extra": {
        "laravel": {
            "dont-discover": []
        }
    },
    "config": {
        "optimize-autoloader": true,
        "preferred-install": "dist",
        "sort-packages": true
    },
    "minimum-stability": "dev",
    "prefer-stable": true
}

0 likes
19 replies
krisi_gjika's avatar

sounds like you have a background process that runs periodically and overloads your server from time to time. I would suggest installing htop and monitoring your server during the hangs.

ycsm's avatar
Level 1

@krisi_gjika Thanks for the reply!

Is there a good equivalent for artisan running on windows (my local machine)? My live server hosting is Linux cpanel so things can be a little limited there.

Snapey's avatar

using artisan serve, its single threaded (can only service one request at once). if you have something else making an expensive query could hang your ajax request whilst it waits for the single thread to be available.

ycsm's avatar
Level 1

@Snapey Thanks snapey. So it seems my next step is to try to find a way to monitor queries/calls. I don't believe debugbar does this? From what I can see it only monitors from actions performed at the front end...?

ycsm's avatar
Level 1

Ok - so I've added this to AppServiceProvider.php, so I can log any queries that happen...

<?php

namespace App\Providers;

use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Register any application services.
     *
     * @return void
     */
    public function register()
    {
        //
    }

    /**
     * Bootstrap any application services.
     *
     * @return void
     */
    public function boot()
    {
        DB::listen(function ($query) {
            Log::info($query->sql);
        });
    }
}

I have this very basic function in the web.php route file which performs just one query:

Route::get('/greeting', function () {
    Log::error('hello world::::');
    return response()->json([FinanceBudgetingPSTSetGroup::find(1)], 201);
});

Screenshot of output Screenshot of network log

I am logging to the laravel.log file. Here is what I see:

[2024-01-26 08:14:00] local.ERROR: hello world::::  
[2024-01-26 08:14:00] local.INFO: select * from `finance_budgeting_pst_set_groups` where `finance_budgeting_pst_set_groups`.`id` = ? order by `ord` asc limit 1  
[2024-01-26 08:14:02] local.ERROR: hello world::::  
[2024-01-26 08:14:02] local.INFO: select * from `finance_budgeting_pst_set_groups` where `finance_budgeting_pst_set_groups`.`id` = ? order by `ord` asc limit 1  
[2024-01-26 08:14:03] local.ERROR: hello world::::  
[2024-01-26 08:14:03] local.INFO: select * from `finance_budgeting_pst_set_groups` where `finance_budgeting_pst_set_groups`.`id` = ? order by `ord` asc limit 1  
[2024-01-26 08:14:05] local.ERROR: hello world::::  
[2024-01-26 08:14:05] local.INFO: select * from `finance_budgeting_pst_set_groups` where `finance_budgeting_pst_set_groups`.`id` = ? order by `ord` asc limit 1  
[2024-01-26 08:14:06] local.ERROR: hello world::::  
[2024-01-26 08:14:06] local.INFO: select * from `finance_budgeting_pst_set_groups` where `finance_budgeting_pst_set_groups`.`id` = ? order by `ord` asc limit 1  
[2024-01-26 08:14:08] local.ERROR: hello world::::  
[2024-01-26 08:14:08] local.INFO: select * from `finance_budgeting_pst_set_groups` where `finance_budgeting_pst_set_groups`.`id` = ? order by `ord` asc limit 1  
[2024-01-26 08:14:10] local.ERROR: hello world::::  
[2024-01-26 08:14:10] local.INFO: select * from `finance_budgeting_pst_set_groups` where `finance_budgeting_pst_set_groups`.`id` = ? order by `ord` asc limit 1  
[2024-01-26 08:14:39] local.ERROR: hello world::::  
[2024-01-26 08:14:39] local.INFO: select * from `finance_budgeting_pst_set_groups` where `finance_budgeting_pst_set_groups`.`id` = ? order by `ord` asc limit 1  

Notice the hang after 2024-01-26 08:14:10 - no other queries were executed, just the one I intended.

I'm very confused

ycsm's avatar
Level 1

Update: Using app.inspector.dev, the only information I can see that's different is the memory usage on the requests that hang are 20.16mb, compared to 19.96mb.

Screenshot of inspector

ycsm's avatar
Level 1

Another update:

Logging in the RouteServiceProvider:

    public function boot()
    {
        Log::alert('ROUTESERVICEPROVIDER'); <------------------------------
        $this->configureRateLimiting();

        $this->routes(function () {
            Route::prefix('api')
                ->middleware('api')
                ->namespace($this->namespace)
                ->group(base_path('routes/api.php'));

            Route::middleware('web')
                ->namespace($this->namespace)
                ->group(base_path('routes/web.php'));
        });
    }

and then in my web.php function:

Route::get('/greeting', function () {
    Log::alert('WEB.PHP'); <------------------------------
    return response()->json([FinanceBudgetingPSTSetGroup::find(1)], 201);
});

Reveals that it hits RouteServiceProvider instantly, then hangs before it gets to web.php:

Screenshot of network log

[2024-01-26 11:18:14] local.ALERT: ROUTESERVICEPROVIDER  
[2024-01-26 11:18:14] local.INFO: select * from `users` where `id` = ? and `users`.`deleted_at` is null order by `last_name` asc, `first_name` asc limit 1  
[2024-01-26 11:18:14] local.ALERT: WEB.PHP  
[2024-01-26 11:18:14] local.INFO: select * from `finance_budgeting_pst_set_groups` where `finance_budgeting_pst_set_groups`.`id` = ? order by `ord` asc limit 1  
[2024-01-26 11:18:16] local.ALERT: ROUTESERVICEPROVIDER  
[2024-01-26 11:18:16] local.INFO: select * from `users` where `id` = ? and `users`.`deleted_at` is null order by `last_name` asc, `first_name` asc limit 1  
[2024-01-26 11:18:16] local.ALERT: WEB.PHP  
[2024-01-26 11:18:16] local.INFO: select * from `finance_budgeting_pst_set_groups` where `finance_budgeting_pst_set_groups`.`id` = ? order by `ord` asc limit 1  
[2024-01-26 11:18:17] local.ALERT: ROUTESERVICEPROVIDER  
[2024-01-26 11:18:17] local.INFO: select * from `users` where `id` = ? and `users`.`deleted_at` is null order by `last_name` asc, `first_name` asc limit 1  
[2024-01-26 11:18:17] local.ALERT: WEB.PHP  
[2024-01-26 11:18:17] local.INFO: select * from `finance_budgeting_pst_set_groups` where `finance_budgeting_pst_set_groups`.`id` = ? order by `ord` asc limit 1  
[2024-01-26 11:18:19] local.ALERT: ROUTESERVICEPROVIDER  
[2024-01-26 11:18:19] local.INFO: select * from `users` where `id` = ? and `users`.`deleted_at` is null order by `last_name` asc, `first_name` asc limit 1  
[2024-01-26 11:18:19] local.ALERT: WEB.PHP  
[2024-01-26 11:18:19] local.INFO: select * from `finance_budgeting_pst_set_groups` where `finance_budgeting_pst_set_groups`.`id` = ? order by `ord` asc limit 1  
[2024-01-26 11:18:21] local.ALERT: ROUTESERVICEPROVIDER  
[2024-01-26 11:18:21] local.INFO: select * from `users` where `id` = ? and `users`.`deleted_at` is null order by `last_name` asc, `first_name` asc limit 1  
[2024-01-26 11:18:21] local.ALERT: WEB.PHP  
[2024-01-26 11:18:21] local.INFO: select * from `finance_budgeting_pst_set_groups` where `finance_budgeting_pst_set_groups`.`id` = ? order by `ord` asc limit 1  

------- It hits the routeserviceprovider instantly ----------

[2024-01-26 11:18:22] local.ALERT: ROUTESERVICEPROVIDER  

-------          Hangs for around 30 seconds         ----------

[2024-01-26 11:18:50] local.INFO: select * from `users` where `id` = ? and `users`.`deleted_at` is null order by `last_name` asc, `first_name` asc limit 1  
[2024-01-26 11:18:50] local.ALERT: WEB.PHP  
[2024-01-26 11:18:50] local.INFO: select * from `finance_budgeting_pst_set_groups` where `finance_budgeting_pst_set_groups`.`id` = ? order by `ord` asc limit 1  
ycsm's avatar
Level 1

Apologies for yet another update. This seems to be as far as I can get with nailing the issue down during the process...

public/index.php:

file_put_contents('logs.txt', "1".PHP_EOL , FILE_APPEND | LOCK_EX);
require __DIR__.'/../vendor/autoload.php';

/*
|--------------------------------------------------------------------------
| Run The Application
|--------------------------------------------------------------------------
|
| Once we have the application, we can handle the incoming request using
| the application's HTTP kernel. Then, we will send the response back
| to this client's browser, allowing them to enjoy our application.
|
*/
file_put_contents('logs.txt', "2".PHP_EOL , FILE_APPEND | LOCK_EX);
$app = require_once __DIR__.'/../bootstrap/app.php';
file_put_contents('logs.txt', "3".PHP_EOL , FILE_APPEND | LOCK_EX);
$kernel = $app->make(Kernel::class);
file_put_contents('logs.txt', "4".PHP_EOL , FILE_APPEND | LOCK_EX);
$response = $kernel->handle(
    $request = Request::capture()
)->send();
file_put_contents('logs.txt', "6".PHP_EOL , FILE_APPEND | LOCK_EX);
$kernel->terminate($request, $response);
file_put_contents('logs.txt', "7".PHP_EOL , FILE_APPEND | LOCK_EX);

Resultant file when it hangs (it gets to just before #4, then 30 seconds later it logs the rest):

Screenshot of network log

ycsm's avatar
ycsm
OP
Best Answer
Level 1

Ok, I seemed to have finally nailed down the issue. The storage/framework/sessions folder had over 220,000 files in it! I cleared all these out and now everything seems fine. I have also amended the following in the session config file to stop this happening again:

'expire_on_close' => false,
Snapey's avatar

'expire_on_close' => false,

only affects cookies and tells the browser via a flag on the cookie that it should discard the cookie if the user closes their browser. It won't make any difference to session files.

Sessions should be cleared by session sweeping, and the default config is;

'lottery' => [2, 100],

check you have this?

Also check the rate that the session files are being created, and that the session lifetime is a sensible value.

ycsm's avatar
Level 1

Thanks for that Snapey.

Here is my session config, it appears I do have it

<?php

use Illuminate\Support\Str;

return [

    /*
    |--------------------------------------------------------------------------
    | Default Session Driver
    |--------------------------------------------------------------------------
    |
    | This option controls the default session "driver" that will be used on
    | requests. By default, we will use the lightweight native driver but
    | you may specify any of the other wonderful drivers provided here.
    |
    | Supported: "file", "cookie", "database", "apc",
    |            "memcached", "redis", "dynamodb", "array"
    |
    */

    'driver' => env('SESSION_DRIVER', 'file'),

    /*
    |--------------------------------------------------------------------------
    | Session Lifetime
    |--------------------------------------------------------------------------
    |
    | Here you may specify the number of minutes that you wish the session
    | to be allowed to remain idle before it expires. If you want them
    | to immediately expire on the browser closing, set that option.
    |
    */

    'lifetime' => env('SESSION_LIFETIME', 120),

    'expire_on_close' => true,

    /*
    |--------------------------------------------------------------------------
    | Session Encryption
    |--------------------------------------------------------------------------
    |
    | This option allows you to easily specify that all of your session data
    | should be encrypted before it is stored. All encryption will be run
    | automatically by Laravel and you can use the Session like normal.
    |
    */

    'encrypt' => false,

    /*
    |--------------------------------------------------------------------------
    | Session File Location
    |--------------------------------------------------------------------------
    |
    | When using the native session driver, we need a location where session
    | files may be stored. A default has been set for you but a different
    | location may be specified. This is only needed for file sessions.
    |
    */

    'files' => storage_path('framework/sessions'),

    /*
    |--------------------------------------------------------------------------
    | Session Database Connection
    |--------------------------------------------------------------------------
    |
    | When using the "database" or "redis" session drivers, you may specify a
    | connection that should be used to manage these sessions. This should
    | correspond to a connection in your database configuration options.
    |
    */

    'connection' => env('SESSION_CONNECTION', null),

    /*
    |--------------------------------------------------------------------------
    | Session Database Table
    |--------------------------------------------------------------------------
    |
    | When using the "database" session driver, you may specify the table we
    | should use to manage the sessions. Of course, a sensible default is
    | provided for you; however, you are free to change this as needed.
    |
    */

    'table' => 'sessions',

    /*
    |--------------------------------------------------------------------------
    | Session Cache Store
    |--------------------------------------------------------------------------
    |
    | While using one of the framework's cache driven session backends you may
    | list a cache store that should be used for these sessions. This value
    | must match with one of the application's configured cache "stores".
    |
    | Affects: "apc", "dynamodb", "memcached", "redis"
    |
    */

    'store' => env('SESSION_STORE', null),

    /*
    |--------------------------------------------------------------------------
    | Session Sweeping Lottery
    |--------------------------------------------------------------------------
    |
    | Some session drivers must manually sweep their storage location to get
    | rid of old sessions from storage. Here are the chances that it will
    | happen on a given request. By default, the odds are 2 out of 100.
    |
    */

    'lottery' => [2, 100],

    /*
    |--------------------------------------------------------------------------
    | Session Cookie Name
    |--------------------------------------------------------------------------
    |
    | Here you may change the name of the cookie used to identify a session
    | instance by ID. The name specified here will get used every time a
    | new session cookie is created by the framework for every driver.
    |
    */

    'cookie' => env(
        'SESSION_COOKIE',
        Str::slug(env('APP_NAME', 'laravel'), '_').'_session'
    ),

    /*
    |--------------------------------------------------------------------------
    | Session Cookie Path
    |--------------------------------------------------------------------------
    |
    | The session cookie path determines the path for which the cookie will
    | be regarded as available. Typically, this will be the root path of
    | your application but you are free to change this when necessary.
    |
    */

    'path' => '/',

    /*
    |--------------------------------------------------------------------------
    | Session Cookie Domain
    |--------------------------------------------------------------------------
    |
    | Here you may change the domain of the cookie used to identify a session
    | in your application. This will determine which domains the cookie is
    | available to in your application. A sensible default has been set.
    |
    */

    'domain' => env('SESSION_DOMAIN', null),

    /*
    |--------------------------------------------------------------------------
    | HTTPS Only Cookies
    |--------------------------------------------------------------------------
    |
    | By setting this option to true, session cookies will only be sent back
    | to the server if the browser has a HTTPS connection. This will keep
    | the cookie from being sent to you when it can't be done securely.
    |
    */

    'secure' => env('SESSION_SECURE_COOKIE'),

    /*
    |--------------------------------------------------------------------------
    | HTTP Access Only
    |--------------------------------------------------------------------------
    |
    | Setting this value to true will prevent JavaScript from accessing the
    | value of the cookie and the cookie will only be accessible through
    | the HTTP protocol. You are free to modify this option if needed.
    |
    */

    'http_only' => true,

    /*
    |--------------------------------------------------------------------------
    | Same-Site Cookies
    |--------------------------------------------------------------------------
    |
    | This option determines how your cookies behave when cross-site requests
    | take place, and can be used to mitigate CSRF attacks. By default, we
    | will set this value to "lax" since this is a secure default value.
    |
    | Supported: "lax", "strict", "none", null
    |
    */

    'same_site' => 'lax',

];

I realised I had set my session timeout value to something silly when I was doing some testing and it got overlooked. I have now put a more sensible number in

ycsm's avatar
Level 1

Another update to this for anyone that this occurs to in the future - The session files weren't being overwritten, new ones were being created. Commenting out SessionStart::class from $middleware seems to have solved this issue (left it alone in $middlewareGroups)

<?php

namespace App\Http;

use Illuminate\Foundation\Http\Kernel as HttpKernel;

class Kernel extends HttpKernel
{

    /**
     * The application's global HTTP middleware stack.
     *
     * These middleware are run during every request to your application.
     *
     * @var array
     */
    protected $middleware = [
        // \Illuminate\Session\Middleware\StartSession::class,  <------ commented this out
        // \App\Http\Middleware\TrustHosts::class,
        \App\Http\Middleware\TrustProxies::class,
        \Fruitcake\Cors\HandleCors::class,
        \App\Http\Middleware\PreventRequestsDuringMaintenance::class,
        \Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
        \App\Http\Middleware\TrimStrings::class,
        \Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull::class,
    ];

    /**
     * The application's route middleware groups.
     *
     * @var array
     */
    protected $middlewareGroups = [
        'web' => [
            \App\Http\Middleware\EncryptCookies::class,
            \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
            \Illuminate\Session\Middleware\StartSession::class,
            // \Illuminate\Session\Middleware\AuthenticateSession::class,
            \Illuminate\View\Middleware\ShareErrorsFromSession::class,
            \App\Http\Middleware\VerifyCsrfToken::class,
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],

        'api' => [
            // \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class,
            'throttle:api',
            \Illuminate\Routing\Middleware\SubstituteBindings::class,
        ],
    ];

    /**
     * The application's route middleware.
     *
     * These middleware may be assigned to groups or used individually.
     *
     * @var array
     */
    protected $routeMiddleware = [
        'auth' => \App\Http\Middleware\Authenticate::class,
        'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
        'cache.headers' => \Illuminate\Http\Middleware\SetCacheHeaders::class,
        'can' => \Illuminate\Auth\Middleware\Authorize::class,
        'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
        'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
        'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
        'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
        'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
        'checkPermission' => \App\Http\Middleware\CheckPermission::class,
    ];
}

Snapey's avatar

@ycsm looking at a default project, startSession should not be in the middlewares section at all, only the web middleware

Snapey's avatar

make sure you are not calling web middleware in your routes file

ycsm's avatar
Level 1

@Snapey - what are the repercussions of this? And what would be best practice as an alternative?

Snapey's avatar

@ycsm once upon a time you had to wrap your routes with web middleware, but then it became the default and some people carried on using web middleware in their routes file, leading to it being applied twice

A quick check is to run a verbose route list and see what middlewares are applied to routes

ycsm's avatar
Level 1

@Snapey Thanks for that.

Here is a typical route (php artisan route:list):

|        | POST     | app/finance/budgeting/userTLRChange                                | generated::nYFARfQzDAexjGDR | App\Http\Controllers\FinanceBudgetingController@userTLRChange                              | web                                                                                                                        |
|        |          |                                                                    |                             |                                                                                            | App\Http\Middleware\CheckPermission:finance_budgeting_admin                                                                |

Appears it is only applying once?

Here is the route in web.php:

 Route::post('userTLRChange', [FinanceBudgetingController::class, 'userTLRChange'])->middleware('checkPermission:finance_budgeting_admin');

Snapey's avatar

@ycsm yes its only once, which is ok. Perhaps look in git blame for kernel.php to see when and possibly why a second start session was added to the default middleware group?

ycsm's avatar
Level 1

@Snapey Yes it's worth an investigation. I found the solution on stackoverflow:

https://stackoverflow.com/questions/36656078/laravel-creates-a-new-session-on-every-request

Quote:

after the upgrade from 5.1 to laravel 5.4 I had a similar issue, I found that I didn't upgraded app/Http/Kernel.php correctly.

\Illuminate\Session\Middleware\StartSession::class was declared twice in protected $middleware array and in

protected $middlewareGroups after removing it from $middleware array it started to work correctly

$middleware applied to all routes and $middlewareGroups applied to specific groups

Please or to participate in this conversation.