Leaving PHP, Part 7: the shared-hosting mental model still leaks into the language
Part 1 looked at hiring. Part 2 looked at the runtime. Part 3 looked at types. Part 4 looked at the ecosystem. Part 5 looked at the type seam. Part 6 looked at concurrency. Part 7 looks at where the code lives. PHP grew up on cPanel and that heritage is still encoded in the language's defaults.
The defaults are from 2005
PHP grew up on shared hosting. Every cPanel account on a budget host ran mod_php inside Apache, configured by php.ini at the system level and .htaccess at the directory level. A request hit the web server, the web server forked or threaded into a PHP interpreter, the interpreter loaded the script, ran it top to bottom, sent the response, and dropped everything on the floor. The defaults of the language were chosen to make that one shape easy.
You can read the heritage anywhere you look. $_GET, $_POST, $_SERVER, $_SESSION are global because in a fork-per-request world there is no ambiguity about whose request they belong to. session_start() writes to a file in /tmp because the host filesystem was assumed to be the only state store. ini_set mutates process-global configuration mid-request because in 2005 the process was the request. register_shutdown_function exists because the script returning was the only termination signal anyone needed.
None of this is broken. All of it works. And every one of these defaults is now slightly off-shape for what people actually deploy in 2026.
Everything modern is an adapter
The PHP deployment story today is a stack of adapters that paper over the shared-hosting assumptions. php-fpm moved interpretation into a long-lived process pool, but the request shape stayed the same. Containers with nginx + php-fpm are now the standard production target, but the container has two processes for what other ecosystems do with one. Bref runs PHP on AWS Lambda by wrapping the runtime in a custom Lambda layer that synthesises the cPanel request shape on top of the Lambda event. FrankenPHP embeds the PHP runtime inside a Go binary so you can ship a single executable and skip the FPM step entirely. RoadRunner hosts a long-lived PHP worker that talks to a Go server over a binary protocol. Octane does similar work for Laravel specifically.
Every one of these projects is high-quality, well-engineered, and absolutely necessary. And every one of them exists because the language's defaults do not match the deployment shape. The adapter has to keep the script-per-request fiction alive while the runtime that calls it is doing something completely different. You can deploy PHP to Lambda, to Cloud Run, to a container, to Kubernetes, to the edge. You will be running an adapter every time.
The cost is rarely dramatic. It shows up as: extra processes per pod, extra moving parts in the diagram, more surface area for production incidents, an answer of "well, it depends on which SAPI you are using" to a lot of operational questions, and a steady tax on the time it takes to get a new service from a clean repo to a healthy production deployment.
JavaScript, Go, and Rust are native to this world
Node was built around an event loop and a single long-lived process. Drop the same code on a VM, in a container, on Lambda, on Cloud Run, or on a Cloudflare Worker, and the runtime model is roughly the same: an event loop accepts connections, dispatches handlers, returns responses. The deployment target changes the orchestration, not the runtime shape. Go is the same story with goroutines instead of an event loop. Rust's tokio looks similar from the outside. There is no equivalent of php-fpm and there does not need to be.
The result is that JavaScript, Go, and Rust deployments are direct. Build a binary or a bundle, ship it, run it. PHP deployments are translated. Build an artifact, pair it with a SAPI, pair the SAPI with a process supervisor, pair the supervisor with a web server, pair the web server with a container, and decide which of those layers you are willing to skip. The work is not insurmountable. It is steady, and there is more of it than there is in the alternatives.
A scorecard for the deployment surface
Deployment cost is the kind of thing that does not block any single decision and quietly shapes every one of them. Picking a container layout is harder. Picking a serverless platform is harder. Picking an edge story is harder. None of these are dealbreakers; all of them are friction the alternatives do not have.
Up next in the series
Three posts to go. The remaining arguments are about security defaults, the developer-tooling story (LSPs, debuggers, profilers, the shape of the IDE experience in 2026), and the actual migration playbook: what you do on day one, what you do in month six, and how Pext fits in.
If you want to start the migration before Part 10 lands, book a demo and we will walk you through what Pext does to your codebase.