PHP Session Locking: Why AJAX Requests Run Sequentially

Lecture



Asynchronous requests allow the browser to call several API endpoints at the same time without blocking the user interface. But on the PHP side, this parallelism can unexpectedly disappear.

The reason is the session locking mechanism. If several requests from the same user use one PHP session, each of them may wait for the session lock to be released, so requests sent simultaneously via AJAX or fetch are actually executed sequentially.

As a result, a simple session_start() can become a hidden performance bottleneck: the longer one request runs, the longer the other requests from the same user are kept waiting.

In this article we will look at why PHP session locking occurs, how it affects asynchronous requests, and what ways there are to solve the problem, from calling session_write_close() early to dropping session-based authentication in the API entirely.

PHP has a very characteristic problem with sessions and parallel AJAX/fetch requests: a session can effectively turn asynchronous requests into sequential ones.

What the problem is

Imagine the browser sends simultaneously:

 GET /api/user
GET /api/products
GET /api/messages
GET /api/notifications

You expect:

 ┌─ /user ───────┐
├─ /products ───┤
START ─┼─ /messages ───┼─> simultaneously
└─ /notifications

But if every PHP script does:

 session_start();

then PHP usually locks the session data for that user. The next request with the same PHPSESSID waits until the previous request finishes working with the session.

What you get is roughly:

 Request 1: session_start()
└────── work 1 ──────┘
session closed
Request 2: WAIT ──────────┘
└── work 2 ───
Request 3: WAIT ─────────────────────────┘
└─ work 3

That is, AJAX is technically asynchronous on the browser side, but PHP requests can be serialized because of the session lock. This is especially noticeable if one request runs for a long time.

For example

Suppose each endpoint takes 1 second.

Without locking:

 4 requests × 1 second
≈ 1 second total time

With a single PHP session lock:

 request 1 → 1 sec
request 2 → 1 sec
request 3 → 1 sec
request 4 → 1 sec
≈ 4 seconds

And this can happen only for one user/one session, so in server load tests the problem is sometimes not noticeable at all.

Solution #1 — session_write_close()

If the session is needed only at the beginning of the request, for example:

 session_start();
$userId = $_SESSION['user_id'];

and then heavy work follows:

 // database queries
// API
// processing
// calculations
// sleep()
// etc.

it is better to do:

 session_start();
$userId = $_SESSION['user_id'];
session_write_close();
// long-running work follows

session_write_close() saves the session data and releases the lock, after which other parallel requests from this user can continue.

An important nuance

After:

 session_write_close();

you must not rely on later changes to $_SESSION being saved.

For example:

 session_start();
$_SESSION['user_id'] = 123;
session_write_close();
$_SESSION['something'] = 'abc'; // this will no longer be saved properly

So the correct pattern is:

 session_start();
$userId = $_SESSION['user_id'];
$someValue = $_SESSION['someValue'];
session_write_close();
// all the rest of the work

Especially relevant for Laravel

If you mean Laravel specifically, the problem is relevant there too.

For example, suppose your React app makes these requests simultaneously:

 /api/services
/api/products
/api/messages
/api/profile
/api/notifications

and Laravel uses ordinary session-based authentication.

If a middleware opens the session and the endpoint then performs a long operation, other requests from the same user may wait for the session lock to be released.

That is why a very strange situation sometimes arises:

"I send 5 AJAX requests at the same time, so why does the server process them one after another?"

And the first thing worth checking is PHP session locking.

This is not a problem with async/await, React, Axios, or the browser itself. It is server-side synchronization of session access.

If the first solution is session_write_close(), then the second fundamental solution is not to use PHP sessions for API requests at all and to switch to stateless authentication, for example Bearer tokens.

2. Remove session-based authentication from the API

Instead of:

 Browse
│
│ PHPSESSID cookie
▼
PHP Session
│
└── session lock

use:

 Browser
│
│ Authorization: Bearer 
▼
API
│
└── token verification

Then no request opens the user's shared PHP session and none of them blocks other parallel requests.

For example:

 GET /api/messages
Authorization: Bearer abc123

simultaneously:

 GET /api/services
Authorization: Bearer abc123

and:

 GET /api/profile
Authorization: Bearer abc123

All three requests can run independently on the server.

For Laravel

For APIs, the following are usually used:

  • Laravel Sanctum — a good option for SPA/API;
  • Passport — if you need full OAuth2;
  • custom Bearer tokens — possible, but there is usually no point in inventing your own system.

Judging by your previous questions, you already had an option with a Bearer token in localStorage and an Axios interceptor. In that case this is exactly the approach that lets you avoid depending on the PHP session lock.

But there is an important point

Do not think:

"A Bearer token automatically makes requests fast."

It only removes the PHP session locking problem. If all 5 requests hit the same slow database at once or perform a heavy operation, they can still be slow.

Summary:

Approach Session lock Parallel API requests
PHP Session + session_start() Yes +- may be blocked
Session + session_write_close() Only briefly +
Bearer Token / stateless API No +
Laravel Sanctum API Depends on the authentication method +for token-based API

If your API is mostly REST + React, I would prefer the second approach: a stateless API with a Bearer token.

Conclusion

PHP sessions are convenient, but their locking mechanism is important to take into account when developing applications with many parallel requests.

If several AJAX requests use one session, a long request can hold the session lock and force the other requests to wait. Outwardly it looks as if the asynchronous requests are executed sequentially, even though the browser sent them at the same time.

If the session is needed only to read data at the beginning of the request, a simple solution is to fetch the required values and call session_write_close() as early as possible. This releases the lock and lets other requests continue.

For APIs, especially in SPAs and mobile applications, it is even better to consider stateless authentication with Bearer tokens. With this approach the user's state is not stored in a shared PHP session, so requests do not compete for a single session lock.

The main principle is simple: do not hold the PHP session longer than is really necessary. Client-side asynchrony makes sense only when the server is also able to process requests in parallel.

created: 2026-08-22
updated: 2026-09-29
1



Was this answer useful?
Choose a quick rating so we can improve the next answer for you.
How satisfied are you?


Comments

To leave a comment

If you have any suggestion, idea, thanks or comment, feel free to write. We really value feedback and are glad to hear your opinion.
To reply

Lectures and tutorial on "Running server side scripts using PHP as an example (LAMP)"

Terms: Running server side scripts using PHP as an example (LAMP)