Skip to content
question

Anyone running Laravel Octane on FrankenPHP in production? Memory and singleton horror stories wanted

Ayesha Siddiqui@laravel_ayesha

September 24, 2026 · 1 min read

We are moving one high traffic client API (about 4M requests a day, mostly reads, Redis cached) from php-fpm to Octane with FrankenPHP. Staging benchmarks look great, p95 from 48ms to 14ms. But I have done this long enough to know staging lies.

Setup: PHP 8.3, Laravel 11, Docker, 2 containers behind a load balancer.

Questions for people who actually run it in production:

  1. Memory growth: do you set --max-requests, and to what?
  2. Singletons holding request state: we already found one (a service that cached the current tenant in a property, so request 2 saw tenant 1, uff). How did you audit the rest?
  3. Packages that broke under Octane?
  4. Any reason to choose Swoole or RoadRunner over FrankenPHP today?

Real numbers welcome, opinions also welcome, "just use Go" not welcome 😄

Accepted answer

Imran Hussain#3 in PHP Still Pays7 likes

We've had Octane on FrankenPHP in prod since spring for an ecommerce API, about 2.5M requests a day, so a similar shape. What we learned: 1. --max-requests=500 to start. We went to 1000 after a month of watching memory. A PDF library leaked about 2MB per 100 requests, max-requests hides that nicely. Set a container memory limit as well, belt and braces. 2. Auditing singletons: grep every ->singleton( in your providers and ask "does this closure touch request(), auth(), session or anything tenant-ish". If yes, make it ->scoped( instead. Scoped bindings get reset between requests under Octane. Then grep app/ for static properties that aren't constants. That found two more for us. 3. Anything left over goes in the flush array in config/octane.php. 4. Write one test that sends two requests as two different tenants through the same app instance and asserts nothing bleeds. Cheap, and it caught a regression in July. 5. FrankenPHP over Swoole because there's no PHP extension to compile and the Docker image just works. You lose Octane::concurrently, which is Swoole only. We never missed it. Staging lies, aye, but mostly about memory. Run it for a week at 10% of traffic behind the load balancer before you switch everything.

View in the thread

Get the best of #php_still_pays weekly

One email each Monday with the most useful posts. No spam; unsubscribe in one click.

Comments

7

Sign in to join the conversation.

Sign in to comment
Bayu Setiawan@bayu_laravel#2 in PHP Still Pays
Not production, but in staging we found a package that keeps the locale in a static property. Users saw random language. Very funny bug, client did not laugh
2 net votes

Accepted answer

Imran Hussain@imran_builds#3 in PHP Still Pays
We've had Octane on FrankenPHP in prod since spring for an ecommerce API, about 2.5M requests a day, so a similar shape. What we learned: 1. --max-requests=500 to start. We went to 1000 after a month of watching memory. A PDF library leaked about 2MB per 100 requests, max-requests hides that nicely. Set a container memory limit as well, belt and braces. 2. Auditing singletons: grep every ->singleton( in your providers and ask "does this closure touch request(), auth(), session or anything tenant-ish". If yes, make it ->scoped( instead. Scoped bindings get reset between requests under Octane. Then grep app/ for static properties that aren't constants. That found two more for us. 3. Anything left over goes in the flush array in config/octane.php. 4. Write one test that sends two requests as two different tenants through the same app instance and asserts nothing bleeds. Cheap, and it caught a regression in July. 5. FrankenPHP over Swoole because there's no PHP extension to compile and the Docker image just works. You lose Octane::concurrently, which is Swoole only. We never missed it. Staging lies, aye, but mostly about memory. Run it for a week at 10% of traffic behind the load balancer before you switch everything.
7 net votes
Ayesha Siddiqui@laravel_ayesha#1 in PHP Still Pays
Imran this is gold, thank you. scoped is exactly the fix for our tenant service. the 10% behind the LB plan goes into the runbook today. 2MB per 100 requests from a PDF lib, I would never have guessed
1 net votes
Guillaume Mercier@guillaume_rale#10 in PHP Still Pays
Bon. We run FrankenPHP worker mode with Symfony, not Octane, same lessons. One more: anything that opens a connection in a constructor (Redis, SOAP client, LDAP) keeps it open across requests and dies when the server times it out. Reconnect on failure or create it lazily. Bref, long-running PHP means you think like a Go dev now.
1 net votes
Ayesha Siddiqui@laravel_ayesha#1 in PHP Still Pays
think like a Go dev. don't tell emeka, he will be unbearable
2 net votes
Emeka Nwosu@emeka_golang3 rep in PHP Still Pays
Too late, I saw it 😂 Welcome to long-running processes, PHP family. We have been waiting for you
2 net votes
Oluwatobi Adeyemi@tobi_freelance#6 in PHP Still Pays
Bookmarked for when I'm brave. My clients are happy with fpm and I'm happy when my clients are happy
1 net votes