Early Response

oxphp_finish_request() sends the complete HTTP response to the client immediately and lets your PHP script keep running for background work. It is the OxPHP equivalent of fastcgi_finish_request() in PHP-FPM.

How it works

  1. Build the response. Your script sets headers, status code, and echoes the response body as usual.
  2. Finish the request. Call oxphp_finish_request(). OxPHP flushes all output buffers, marks the request as finished, and delivers the complete HTTP response to the client.
  3. Run background work. The script continues executing for background work such as sending emails, writing cache entries, or dispatching webhooks.
  4. Later output is discarded. Any output produced after the call (echo, print, var_dump) is silently discarded.
  5. The call is idempotent. oxphp_finish_request() returns true on the first call and false on any subsequent call within the same request.

Use cases

Early response helps whenever you want to acknowledge a request immediately while deferring non-critical work:

  • To send email, return "accepted" immediately and send the message in the background.
  • To warm a cache, respond with cached data, then regenerate the cache entry.
  • For analytics and logging, acknowledge the request, then write detailed analytics records.
  • To dispatch webhooks, confirm receipt to the caller, then fan out webhook deliveries.
  • To process images, return a URL immediately, then process the full-size image.

PHP examples

Basic usage

php
<?php header('Content-Type: application/json'); echo json_encode(['status' => 'accepted', 'id' => uniqid()]); // Send response now — client receives the full response at this point oxphp_finish_request(); // Background work runs here; client is no longer waiting file_put_contents('/tmp/audit.log', date('c') . " request processed\n", FILE_APPEND); send_notification_email($user);

Guard against double invocation

oxphp_finish_request() returns false on the second and subsequent calls. Check the return value in middleware-heavy applications where multiple layers might call the function:

php
<?php function finish_and_cleanup(): void { if (!oxphp_finish_request()) { // Already finished — background work was already scheduled return; } // First call — safe to run cleanup flush_metrics_buffer(); close_external_connections(); }

Conditional background work

php
<?php header('Content-Type: application/json'); $payload = json_decode(file_get_contents('php://input'), true); $result = handle_request($payload); echo json_encode($result); if ($result['needs_sync']) { oxphp_finish_request(); sync_to_external_service($result); } // No early finish if sync is not needed — script exits normally

Worker mode

In worker mode, the PHP worker remains occupied until the entire script (including all background work) completes. The worker does not accept a new request until the callback returns.

php
<?php oxphp_worker(function () { $order = json_decode(file_get_contents('php://input'), true); $result = process_order($order); header('Content-Type: application/json'); echo json_encode(['order_id' => $result['id'], 'status' => 'accepted']); oxphp_finish_request(); // Worker is still occupied during this background work send_confirmation_email($result); update_inventory($result); notify_warehouse($result); // Worker becomes available after this point });
Note

Account for background processing time when sizing your worker pool. A worker that spends 3 seconds on post-response work after every request effectively handles fewer concurrent requests.

Troubleshooting

Background work is not completing

PHP's max_execution_time continues to apply after oxphp_finish_request() is called. If the total script execution time, including background work, exceeds the limit, the request is cancelled with a Request cancelled (timeout) fatal.

Fix: Raise max_execution_time (in php.ini or via set_time_limit() from the script), or move long background tasks to a message queue:

php
set_time_limit(300); oxphp_finish_request(); // ... long-running work ...

For work that regularly takes more than a few seconds, publish a message to Redis, RabbitMQ, or a similar queue and let a dedicated consumer handle it asynchronously.

A session cannot be started after the response has gone

oxphp_finish_request() sends the response as it stands (status, headers and body), and nothing added to it afterwards is sent. Sending it also means the headers have been sent, and PHP refuses to start a session once that has happened: session_start() raises Session cannot be started after headers have already been sent and returns false, and session_regenerate_id() refuses in the same way. The session is not started at all, so $_SESSION writes after that point go into an ordinary array that nothing saves. Under a framework that turns warnings into exceptions, the refusal becomes an exception thrown inside a request that has already been answered.

Fix: start the session before the call, and close it before the call as well, so the write and the unlock happen at a point you control:

php
<?php session_start(); // Before the response goes out, not after $_SESSION['last_seen'] = time(); session_write_close(); // Persist the session and release the save handler's lock oxphp_finish_request(); // Send response // ... work that no longer has a session ...

In worker mode this is worth doing whether or not the request finishes early. A worker closes a session at the end of the request only when no other request it is carrying is in that session; one that is shared is closed only when that worker next has an idle moment to give it back. Until then the save handler is still holding whatever it locked, which for the default handler is an exclusive lock that blocks anyone else reading that session. See Worker Mode for the full boundary.

Response body is empty after calling oxphp_finish_request()

If you call oxphp_finish_request() before any echo output, the client receives an empty body. Build and output the response first, then call the function.

Notes

  • oxphp_finish_request() returns true on the first call and false on subsequent calls within the same request.
  • All output (echo, print, var_dump) after the first call is silently discarded.
  • In worker mode, the worker remains occupied until the entire callback completes, including all post-response code.
  • The request timeout continues to apply to background code running after oxphp_finish_request().
  • oxphp_finish_request() and oxphp_stream_flush() are mutually exclusive: calling oxphp_finish_request() before starting a stream prevents streaming, and calling it after oxphp_stream_flush() closes the stream.

Prompts for your agent

These prompts assume an agent with the OxPHP documentation MCP server connected. The agent reads the pages it needs itself.

prompt
Move my post-response work behind `oxphp_finish_request()`. Read the `features/early-response` page through the OxPHP documentation MCP server. Find the work in this repository the client is waiting on but does not need (mail, webhook fan-out, analytics writes) and show me those handlers rewritten so the response goes out first, with any session started and closed before the call.

See also

  • Worker Mode: persistent PHP workers and how early response interacts with the request loop
  • Timeouts: how the request timeout applies to background work
  • PHP Functions: full reference for oxphp_finish_request() and other built-in functions
Found a mistake? Report it →