Async PHP in 2026: Fibers, ReactPHP, and When You Actually Need It

You need async PHP only when one request waits on many slow I/O calls at once, like fanning out dozens of API or database queries. Fibers in PHP 8.1+ and ReactPHP handle that. For normal CRUD and WordPress work, plain synchronous PHP is faster to build and easier to debug.
Part of the complete web development guide.
On this page
- Why PHP is synchronous, and why that’s usually fine
- Fibers in PHP 8.1: what they actually are
- Event loop libraries: ReactPHP and Amp
- Swoole and OpenSwoole: a different model entirely
- Async HTTP with Guzzle promises
- When async PHP actually pays off
- When it’s over-engineering
- A rough decision framework
- Conclusion
- FAQ
- Is async PHP faster than regular PHP?
- Can I use Fibers with Laravel or Symfony right now?
- Does Swoole work with WordPress?
Every few months someone asks me why PHP still doesn’t have “real” async, as if the language owes them a Node.js runtime. The honest answer is that PHP’s synchronous, request-per-process model is a feature, not a bug, and it’s the reason a $6 VPS can serve a WordPress site to a few thousand visitors a day without breaking a sweat. But there are real cases where blocking I/O costs you, and PHP 8.1 and beyond give you actual tools to deal with that. Here’s what those tools do, what they don’t do, and when reaching for them is the right call.
Why PHP is synchronous, and why that’s usually fine
PHP was built for the web’s original shape: a request comes in, the server spins up a process (or reuses one via PHP-FPM), the script runs top to bottom, it sends a response, and it dies. No shared state, no memory leaks between requests, no event loop to reason about. Each request is a fresh sandbox.
That model is boring and that’s the point. When you fetch a row from MySQL in a normal PHP script, the process blocks until the row comes back. It doesn’t matter, because that process has nothing else to do. It only exists to serve this one request. Blocking is free when you have nothing to block.
You get real advantages from this. Debugging is straightforward: stack traces go in one direction. You can’t accidentally leak a database connection into the next request because there is no next request. Deploys are just replacing files. The whole operational story is simple enough that a solo developer can run a production site.
The cost shows up in exactly one place: when a single request needs to do a lot of waiting at once. Say you need to hit twelve external APIs and each takes 300ms. Synchronously, that’s 3.6 seconds of a worker sitting idle, waiting on network sockets. Under load, you run out of PHP-FPM workers and requests queue. That’s the actual problem async PHP solves. Not “PHP is slow.” Waiting is slow.
Fibers in PHP 8.1: what they actually are
Fibers landed in PHP 8.1 and got misunderstood immediately. People called them “async/await for PHP.” They’re not. A Fiber is a way to pause and resume a block of code from the outside, cooperatively. You can suspend execution mid-function and hand control back to whatever started the fiber, then resume it later.
What Fibers do not do: they don’t make anything non-blocking. If you call file_get_contents() inside a fiber, the whole process still blocks. A fiber is a control-flow primitive. It’s the plumbing that lets a library like Amp or ReactPHP write code that looks synchronous while actually yielding at I/O boundaries.
Here’s what one looks like:
<?php
$fiber = new Fiber(function (): void {
$value = Fiber::suspend('paused once');
echo "Resumed with: $value\n";
});
$first = $fiber->start(); // runs until suspend(), returns 'paused once'
echo "Got: $first\n";
$fiber->resume('hello'); // continues inside the fiber
// Output:
// Got: paused once
// Resumed with: hello
That’s the whole feature. The value is that a framework can now suspend your function at the exact point it’s waiting on a socket, run other work, and resume you when data arrives, without you writing callback pyramids or generator gymnastics. Before 8.1, async libraries had to use generators and yield, which leaked into your code. Fibers hide it. That’s the win.
Event loop libraries: ReactPHP and Amp
Two mature libraries own this space in PHP.
ReactPHP is the older one and the more conservative. You create an event loop, register streams and timers, and the loop runs until there’s nothing left to do. Its style is promise-based and callback-oriented, though modern ReactPHP supports async/await syntax through fibers in recent versions.
Amp is the more opinionated sibling. It leans hard into fibers and gives you a synchronous-looking API: you write code that reads top to bottom, and Amp’s internals suspend the fiber whenever you call an async operation. Amp also ships a solid set of async I/O primitives (HTTP client, MySQL, Redis, filesystem) through its amphp packages.
Here’s the shape of a ReactPHP HTTP server, which is the canonical “why would I do this” example:
<?php
require __DIR__ . '/vendor/autoload.php';
use React\Http\HttpServer;
use React\Http\Message\Response;
use React\Socket\SocketServer;
$server = new HttpServer(function (Psr\Http\Message\ServerRequestInterface $request) {
$path = $request->getUri()->getPath();
return new Response(200, ['Content-Type' => 'text/plain'], "You hit $path\n");
});
$socket = new SocketServer('127.0.0.1:8080');
$server->listen($socket);
echo "Listening on http://127.0.0.1:8080\n";
Run that and you have a single-process HTTP server handling many concurrent connections without spinning up a worker per request. That’s genuinely different from PHP-FPM. It’s also a different operational animal: one bad blocking call anywhere in the codebase stalls the entire server for every connected client. There’s no process isolation to save you.
Swoole and OpenSwoole: a different model entirely
Swoole is a PHP extension written in C that turns PHP into a long-running, coroutine-based runtime. It’s not a library you composer require; you install it as a PHP extension, usually alongside a Swoole-aware framework like Hyperf, Swoole-based Laravel (Laravel Octane), or a custom setup.
When you run PHP under Swoole, your application boots once and stays in memory. Requests are handled as coroutines inside a worker pool. You get connection pooling, no per-request bootstrap cost, and true concurrency without the process-per-request model. Benchmarks are impressive and real. So are the footguns.
The big ones: state leaks between requests. If your framework’s container holds onto request-scoped data and you don’t reset it, users see each other’s data. Global variables, static properties, singletons, all of it becomes hazardous. Memory leaks accumulate over days instead of dying with the request. If you’re running WordPress under Swoole, understand that WordPress’s core assumes request-per-process in a hundred small ways. It can be made to work; it will not be pleasant.
OpenSwoole is a fork of Swoole maintained by a separate team after some community friction around Swoole’s governance. Both are viable. Pick one, read its docs carefully, and treat the framework that runs on top of it (Octane, Hyperf) as the thing that actually needs to be correct.
Async HTTP with Guzzle promises
This is where most people should start, because it’s the smallest possible change with real payoff. Guzzle has supported async requests via promises for years, and it works without fibers, without Swoole, without an event loop library. It uses cURL’s multi-handle interface under the hood.
<?php
use GuzzleHttp\Client;
use GuzzleHttp\Promise;
$client = new Client(['timeout' => 5]);
$promises = [
'users' => $client->getAsync('https://api.example.com/users'),
'orders' => $client->getAsync('https://api.example.com/orders'),
'invoices' => $client->getAsync('https://api.example.com/invoices'),
];
$results = Promise\Utils::settle($promises)->wait();
foreach ($results as $name => $result) {
if ($result['state'] === 'fulfilled') {
echo "$name: " . $result['value']->getStatusCode() . "\n";
} else {
echo "$name failed: " . $result['reason']->getMessage() . "\n";
}
}
Three calls that would take 900ms sequentially now take roughly the slowest one, maybe 350ms. No new runtime, no extension, no event loop to babysit. If your slow endpoint is “I call six third-party APIs in a row,” this is the fix, and it’s a ten-line fix.
When async PHP actually pays off
- Long-running workers. Queue consumers, cron replacements, background job processors. These already run outside the request cycle, so the “one request, one process” assumption doesn’t apply. If a worker processes 500 jobs an hour and each job hits three APIs, async HTTP inside the worker is a clear win.
- Fan-out I/O in a single request. The Guzzle promise case above. Anytime one user action triggers many independent network calls, parallelize them.
- WebSockets and real-time features. Chat, live dashboards, collaborative editing. You need a process that holds thousands of open connections and reacts to messages on any of them. PHP-FPM cannot do this. ReactPHP, Amp, or Swoole can.
- High-throughput APIs with tight latency budgets. If you’re serving tens of thousands of requests per second and per-request bootstrap cost is measurable, Swoole or Octane will move the needle. Measure first.
- Streaming and proxying. Long-lived connections that relay data between services. Again, FPM’s model fights you here.
When it’s over-engineering
A normal WordPress site does not need async PHP. Neither does a typical Laravel app. If your page loads in 200ms and your database queries are indexed, adding an event loop will cost you more in debugging time than it saves in milliseconds. I’ve watched teams rewrite a working FPM app onto Swoole and end up with state-leak bugs and memory creep that took months to shake out, for a 15% throughput gain they didn’t need.
Other places to stop and reconsider:
- You’re doing it because it’s interesting. That’s fine as a learning project. Don’t put it in production for that reason.
- Your bottleneck is the database, not the network. Async won’t fix a missing index or an N+1 query.
- Your traffic is modest. Ten requests per second does not need Swoole.
- You rely heavily on libraries that assume blocking I/O. Async contaminates the whole stack. One blocking call in a fiber-based server stalls everyone.
- Your team doesn’t know the model. Async bugs (race conditions, unhandled rejections, state leaks) are harder to diagnose than synchronous ones. Budget for that.
If you’re weighing this kind of architectural decision for a real product and want a second opinion, this is the sort of thing I do in my custom software engineering work, and it comes up often on the custom web development side too when a client’s site has outgrown its original shape.
A rough decision framework
Start synchronous. Measure. If your profile shows time spent waiting on network I/O, and that wait is inside a single request or a worker loop, apply the smallest fix: Guzzle promises for HTTP fan-out. If you need persistent connections and genuinely concurrent clients, look at ReactPHP or Amp. If you need maximum throughput and you have the operational maturity to run a long-lived PHP runtime safely, evaluate Swoole or OpenSwoole with a framework like Octane or Hyperf. Never skip a step because the next one is more fun.
Conclusion
PHP’s synchronous model is the right default for the vast majority of web work, and it’s not going anywhere. Fibers gave async libraries a much cleaner way to express themselves, ReactPHP and Amp cover the event-loop use cases, Swoole and OpenSwoole offer a genuinely different runtime for high-throughput systems, and Guzzle promises give you 80% of the practical benefit for about 5% of the complexity. Reach for async when you have a specific, measured reason. Otherwise, ship the simpler thing.
FAQ
Is async PHP faster than regular PHP?
Not inherently. Async PHP is faster only when your code spends meaningful time waiting on I/O and you can overlap those waits. For CPU-bound work, async changes nothing, and for a simple page render with a couple of fast queries, it’s often slower because of the added overhead. “Faster” depends entirely on what your code is doing while it waits.
Can I use Fibers with Laravel or Symfony right now?
Yes, but usually indirectly. Symfony has fiber-aware components, and Laravel Octane can run on Swoole or RoadRunner. You generally don’t write raw Fiber code in an application; you use a framework that uses them underneath. Writing raw fibers in business logic is a smell. Let the library own that layer.
Does Swoole work with WordPress?
Technically, yes, with effort. Practically, I’d push back hard. WordPress core, and most plugins, assume a fresh process per request. Running it under a long-lived runtime means auditing every global, static, and singleton for state leakage. The performance gain rarely justifies the risk for a content site. If you need WordPress to be faster, cache it, put a CDN in front, and tune the database before you consider Swoole.


