Skip to content
Carl Victor Fontanos
Carl Victor Fontanos

Carl Victor Fontanos

Software Engineer

I build web applications and share what I learn along the way.

© 2026

The PHP Session Lock That's Serializing Your AJAX Calls

C
Carlo Fontanos
· 2 min read

This is my favorite "senior developer discovers it after ten years" bug, because the symptom points everywhere except the cause. A dashboard fires six AJAX requests on load. Each endpoint takes ~300ms in isolation. Total load time: 1.8 seconds. The requests run sequentially, and nothing in your code says they should.

The cause

PHP's default file-based session handler takes an exclusive lock on the session file the moment session_start() runs, and holds it until the script ends. Request two for the same session can't start its session until request one finishes. Your parallel AJAX becomes a polite single-file queue - per user. (Other users are unaffected, which is why load testing with many virtual users looks fine while real single-user UX crawls.)

Prove it to yourself:

// slow.php
session_start();
sleep(3);
echo json_encode(['done' => microtime(true)]);

Open DevTools, call it twice in parallel from the console - the second response arrives at ~6 seconds, not ~3. That waterfall staircase in the Network panel is the lock made visible.

The fix: release the lock the moment you're done writing

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

session_write_close();   // lock released - other requests proceed NOW

// ... the actual slow work: queries, API calls, rendering ...
// $_SESSION data read above is still available as plain variables

Most endpoints only read the session (who is logged in?), and that takes microseconds. Everything after session_write_close() runs lock-free and truly parallel. If you need to write later, session_start() again briefly - reads of $_SESSION still work after closing; it's writes that silently stop persisting, which is the one trap to respect.

Framework and API notes

  • Read-only endpoints can declare it upfront: session_start(['read_and_close' => true]) - opens, reads, releases in one call.
  • Laravel's default database/redis session drivers don't lock this way (which is why many devs never meet the bug until they touch vanilla PHP or a legacy app). File-session apps - WordPress plugins doing session_start(), classic LAMP codebases - are the natural habitat.
  • Long-running downloads and SSE endpoints MUST close the session or they'll freeze every other tab the user has open. That one's not a performance bug, it's a site-appears-down bug.

One function call, and the dashboard that took 1.8s loads in 400ms. The best performance fixes are the ones where the work was already fast and something invisible was making it wait its turn.

C
Written by Carlo Fontanos

Full-stack web developer sharing practical tutorials and building tools that ship.

Got something on your mind?

My inbox is open - no forms disappearing into the void here.

  • Just say hello Found a tutorial useful? Spotted a mistake? Tell me.
  • Hire me for a project Have something custom in mind? Let's talk scope and timelines.
  • Product support Bought something here? I'll help you get it running.

I usually reply within 1-2 business days.

Message sent!

Your details are only used to reply to you.

Keep reading