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

Answer First, Work Later: Post-Response Processing in PHP

C
Carlo Fontanos
· 2 min read

A checkout endpoint I inherited took 2.4 seconds. The order insert took 40 milliseconds. The other 2.3 seconds were a confirmation email over SMTP, a Slack webhook and a stock-sync API call - none of which the customer needed to wait for. They just needed "Order confirmed".

On PHP-FPM: fastcgi_finish_request()

If you run nginx + FPM (or Apache with FPM, which is most modern hosting), one function flushes the response to the client and closes the connection while your script keeps running:

echo json_encode(['ok' => true, 'order' => $order->number]);

fastcgi_finish_request();   // customer has their answer NOW

// Everything below happens after the response - user is gone
sendConfirmationEmail($order);      // 1.8s - nobody waits
notifySlack($order);                // 300ms
syncInventory($order);              // 200ms

The browser sees a 40ms response. Your logs see the full runtime. Everyone wins.

Things to know once the request is finished: you can't echo anything anymore, the session should be closed first (you're holding the lock otherwise), and set_time_limit generously because the work now runs unattended. Wrap the after-work in try/catch and log failures - there's no user left to show errors to.

On mod_php: the best-effort version

Apache with mod_php (classic XAMPP included) has no exact equivalent, but you can get close for small payloads:

ignore_user_abort(true);            // keep running if the client disconnects
ob_start();
echo $json;
header('Content-Length: ' . ob_get_length());
header('Connection: close');
ob_end_flush();
flush();                            // push it out the door

// Browser *usually* releases now; slow work follows
sendConfirmationEmail($order);

The Content-Length + Connection: close combo tells the browser the response is complete so it stops spinning. It's less bulletproof than FPM (gzip/output buffering at the server layer can interfere), so test on your actual stack.

register_shutdown_function: the portable hook

register_shutdown_function(function () use ($order) {
    writeAuditLog($order);
});

Shutdown functions run after script execution on every runtime - and on FPM, after fastcgi_finish_request they run post-response automatically. They're also the classic place to catch fatal errors for logging (error_get_last() inside a shutdown function sees them).

Know when to graduate to a real queue

This pattern is "poor man's async" - brilliant for shaving seconds off checkout with zero infrastructure, wrong for anything needing retries, ordering or visibility. When the after-work starts failing silently or takes 30+ seconds, that's your cue for a proper job queue (database-driven is fine). Until then, one function call buys you the UX of async with none of the moving parts.

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