# For concurrent HTTP calls, should I reach for raw Fibers, ReactPHP/AMPHP, or Swoole?

> Stay in FPM and start with a Guzzle Pool pinned to 20-50 concurrency; move to AMPHP v3 only if the codebase grows async, Swoole only when re-architecting.

- Asked: 2026-09-19
- Answered: 2026-09-25
- Asked by: Berk
- Tags: php, concurrency
- Source: https://muhammetsafak.com/just-ask/concurrent-http-calls-raw-fibers-reactphp-amphp-swoole/
- Language: en-US
- Author: Muhammet Şafak

---
**Question:** We run a Laravel service that, on a single request, has to call about 200 different upstream APIs and merge the results. Right now we do them sequentially and, unsurprisingly, the request latency is terrible.

I want to make these concurrent, but I can't decide whether to use raw Fibers in PHP 8.3, an established async runtime like ReactPHP/AMPHP, or just move to Swoole. Do raw Fibers actually buy me anything over an established runtime?


Short answer: raw Fibers on their own buy you nothing here.

## Short answer

For HTTP fan-out the real choice is curl_multi/Guzzle Pool if you stay in FPM, AMPHP v3 if you want ergonomic async, and Swoole only if you're re-architecting.

## Why

1. **Raw Fibers are a primitive, not a solution.** A Fiber only gives you cooperative scheduling; by itself it provides neither an event loop nor non-blocking I/O. To actually run HTTP calls in parallel you'd have to write the socket layer, the scheduler, and the promise logic yourself — which is exactly what AMPHP and ReactPHP already exist to do. Dropping to raw Fibers is reinventing those libraries.

2. **AMPHP v3 = ergonomic async/await built on Fibers.** It uses Fibers under the hood but gives you a clean `async`/`await` API plus `amphp/http-client`. No callback hell. It runs on a standard PHP install and scales when things get complex (dependent calls, streaming).

3. **ReactPHP is mature but promise/callback-style.** It does the same job and has been solid in production for years. But in new code AMPHP's async/await reads far better; for a greenfield service I'd pick AMPHP.

4. **Swoole is a different world.** It needs the extension, hooks blocking I/O, and requires you to leave the FPM model for a long-running worker. Throughput is high, but so are the operational cost, [the risk of state leaking between requests](/just-ask/debugging-memory-leaks-under-laravel-octane-frankenphp/), and incompatibility with some extensions. I wouldn't pay that price just for 200 calls.

## What to do

1. **The boring option is usually the right one: curl_multi / Guzzle Pool.** If you just need N concurrent HTTP calls, `curl_multi` (or Guzzle's Pool that wraps it) does true parallel requests inside plain PHP-FPM. No new extension to install — it uses the curl extension that's already there — no runtime swap, deployment stays identical.

2. **The real issue isn't the runtime, it's that number 200.** 200 open connections at once punish both you and the upstreams. Set a concurrency cap of 20-50, put a timeout on every call, and handle partial failure (one bad call shouldn't sink the batch).

   ```php
   use GuzzleHttp\Client;
   use GuzzleHttp\Pool;
   use GuzzleHttp\Psr7\Request;

   $client = new Client(['timeout' => 3.0]);
   $requests = static function (array $urls) {
       foreach ($urls as $u) {
           yield new Request('GET', $u);
       }
   };

   $pool = new Pool($client, $requests($urls), [
       'concurrency' => 25, // at most 25 in flight at once
       'fulfilled'   => fn($res, $i) => $results[$i] = (string) $res->getBody(),
       'rejected'    => fn($reason, $i) => $errors[$i] = $reason, // partial failure
   ]);
   $pool->promise()->wait();
   ```

**Bottom line:** personally I'd stay in FPM and start with a Guzzle Pool, pin concurrency to 20-50, and handle timeouts and partial failures properly — it's the simplest thing you can deploy today. If the codebase genuinely grows into async, I'd move to AMPHP v3. I'd only consider Swoole if moving the service to a long-running model is already on the table. And I wouldn't reach for raw Fibers at all unless I were writing a library.

## Related Reading

- [Does my channel buffer size choice hide backpressure problems, and how do I decide it?](https://muhammetsafak.com/just-ask/channel-buffer-size-choice-hide-backpressure-problems-decide/) — Just Ask
- [Should I run several dependent operations with errgroup so the first error cancels the rest?](https://muhammetsafak.com/just-ask/should-i-run-several-dependent-operations-with-errgroup-so-the-first/) — Just Ask
- [How do I cleanly implement "withX" copy methods on readonly value objects?](https://muhammetsafak.com/just-ask/how-do-i-cleanly-implement-withx-copy-methods-on-readonly-value/) — Just Ask
