When PHP struggles at scale, raw language speed is rarely the cause. The trouble is how it handles concurrent requests. Under PHP-FPM, every request holds a full worker process for its entire duration. Any request that spends its time waiting, on a slow query, a third-party call, or a connection that stays open, pins a worker that could be serving someone else. Real-time features like live dashboards, streaming responses, and high-fan-out APIs hit that limit fast. Adding workers or machines pushes the ceiling back, but it's still there.
Go's concurrency model doesn't have that ceiling, and that's the main reason teams move from PHP to Go. This post covers why the model matters, what it does for deployment and cost, what teams reported after moving, and how to migrate one service at a time instead of betting the product on a rewrite.
TL;DR
- Migrate for the concurrency model. PHP's process-per-request design ties up a whole worker for the life of each request. Go's goroutines let one process juggle thousands of concurrent connections. The gap grows as you add real-time and connection-heavy features.
- The memory gap is large. A PHP-FPM worker holds roughly 30 to 60 MB once your framework loads. A goroutine starts at about 8 KB. Fifty concurrent streams can saturate an FPM pool while a single Go process barely notices them.
- Teams that moved report big gains. Engineers from Cloudflare and the fintech Curve describe one service going from around 10 requests per second to thousands after moving to Go, with new engineers productive in one to two weeks (Go Time #316).
- Deployment is simpler. Go compiles to a single static binary. You don't install a runtime, run Composer, or tune FPM, which suits microservices and Kubernetes well.
- You don't have to rewrite everything. Use the strangler pattern. Migrate the one service that hurts, run it beside your PHP, and expand once it has proven itself.
Table of Contents
- The Real Reason to Migrate Is Concurrency
- What Teams Gain After the Move
- Go vs PHP: The Technical Wins
- The Benchmark Truth (and the Real Payoff)
- Faster to Hire, Faster to Ship
- How to Migrate Without Betting the Company
- FAQ
- Ready to Write Production Go?
The Real Reason to Migrate Is Concurrency
Classic PHP is share-nothing and process-per-request. A request arrives, PHP-FPM hands it to a worker, and the worker runs your code from a clean slate to the response, then resets. The design is simple and well isolated. What it costs you is memory.
A single PHP-FPM worker typically holds 30 to 60 MB of resident memory once your framework is loaded. Serving 50 requests at once means 50 workers, roughly 1.5 to 3 GB of RAM spent mostly on waiting.
Now make those 50 requests Server-Sent Events streams or WebSocket connections that each stay open for minutes. Fifty long-lived connections can saturate an entire FPM pool, and the 51st user waits in line. Throwing hardware at this gets expensive quickly, because every concurrent connection costs a whole process.
Go works the other way around. A goroutine, Go's unit of concurrency, starts with a stack of about 8 KB, and the runtime multiplexes thousands of them onto a small pool of OS threads. When a goroutine blocks on I/O, the scheduler parks it and runs another on the same thread. One Go process can hold tens of thousands of open connections while using a fraction of the memory an equivalent FPM fleet would need. A connection handler is just an ordinary function:
example.gogofunc (s *Server) handleStream(w http.ResponseWriter, r *http.Request) { // Each connection is one cheap goroutine. Blocking here parks // this goroutine and frees the thread for others, no pool to exhaust. flusher, ok := w.(http.Flusher) if !ok { http.Error(w, "streaming unsupported", http.StatusInternalServerError) return } w.Header().Set("Content-Type", "text/event-stream") for event := range s.events(r.Context()) { fmt.Fprintf(w, "data: %s\n\n", event) flusher.Flush() } }
The net/http server runs each request in its own goroutine automatically, so this code holds a live streaming connection without reserving a heavyweight worker. If your product is moving toward real-time features, high-fan-out API gateways, or services that hold many concurrent connections, this difference decides a lot. PHP's model works against those workloads, and Go's handles them without special effort. If goroutines and channels are new to you, the Go Concurrency Fundamentals course walks through the model hands-on.
What Teams Gain After the Move
The best first-hand account of a real PHP-to-Go migration is episode 316 of the Go Time podcast. Engineers from Cloudflare and the fintech Curve talked through moving production PHP systems to Go (Go Time #316, Changelog). These are practitioners describing their own systems, which makes it more useful than a vendor case study or a blog post guessing at numbers from the outside.
One service reportedly went from handling around 10 requests per second under the old setup to thousands after the move. They summed up the runtime with "even bad Go is incredibly performant," meaning you get a lot of throughput before you ever need to optimize. New engineers were productive in one to two weeks rather than months, which comes down to how small Go's surface area is.
Curve's Go lineage traces back to Monzo, another fintech that built heavily on Go, and that's part of how the pattern spread in that space.
There are other Go-in-production data points too. HelloFresh built and open-sourced its API gateway, Janus, in Go (Janus on GitHub). A gateway is a high-fan-out routing layer, exactly the kind of workload where goroutines help most. You'll also find plenty of individual engineers reporting that a single batch job rewritten from PHP to Go runs much faster on a fraction of the resources.
Go vs PHP: The Technical Wins
Here is how the two compare on the things that matter for a backend service.
| Dimension | PHP | Go |
|---|---|---|
| Concurrency | Process-per-request, with Fibers adding concurrency but not parallelism | Goroutines on an M:N scheduler, true parallelism across cores |
| Typing | Dynamic, gradual type hints | Static, checked at compile time |
| Deployment | Code + Composer deps + FPM + web server | One static binary, no runtime to install |
| Execution | Interpreted, JIT-assisted | Compiled to native machine code |
Concurrency matters most. PHP 8.1 added Fibers, which are a real improvement, but it's worth being precise about what they do. Fibers give you cooperative concurrency for structuring async I/O. They let one worker interleave waiting tasks. They don't let one PHP process saturate 16 cores with CPU work the way Go's scheduler does.
Go maps many goroutines onto many OS threads, runs them in parallel, and parks them cheaply when they block. The same primitive gives you cheap concurrency and real parallelism. A worker pool is a good example. You reach for one constantly in Go, and classic PHP has no clean single-process equivalent:
example.gogofunc process(jobs <-chan Job, results chan<- Result, wg *sync.WaitGroup) { defer wg.Done() for job := range jobs { results <- job.Run() // each worker pulls the next job when free } } func main() { jobs := make(chan Job, 100) results := make(chan Result, 100) var wg sync.WaitGroup // Fan out to 8 workers sharing one queue, all in one process. for i := 0; i < 8; i++ { wg.Add(1) go process(jobs, results, &wg) } go func() { for _, j := range loadJobs() { // pull the batch from your work source jobs <- j } close(jobs) }() go func() { wg.Wait(); close(results) }() for r := range results { record(r) } }
Deployment gets less attention than it deserves. A Go build produces a single static binary, and shipping it can be as simple as copying that file to a server or into a scratch container. There's no PHP runtime to install, no Composer step, no FPM tuning, and no web server config in front of it. If you run microservices in Kubernetes, you get that small, self-contained artifact on every deploy.
Static typing helps as well. The compiler catches a class of errors at build time that PHP only surfaces at runtime, and that matters more as a service grows and more people work on it.
Then there's the "modern PHP is fast now" objection. PHP did close much of the raw-speed gap, with the PHP 8 JIT and worker runtimes like FrankenPHP and RoadRunner that keep your app booted in memory across requests. But FrankenPHP and RoadRunner are both written in Go. When the PHP ecosystem needed high-concurrency application servers, it built them on Go's runtime. If concurrency is your problem, you can put a Go-powered runtime in front of PHP, or you can write the connection-heavy service in Go directly and skip the middle layer.
The Benchmark Truth (and the Real Payoff)
You'll see headline numbers claiming Go is 10 to 30 times faster than PHP. Those come from synthetic suites like the TechEmpower benchmarks, which test runtimes under idealized, CPU- and framework-bound conditions (TechEmpower Framework Benchmarks). The numbers are real, but they don't predict your production latency, so don't migrate expecting that multiplier.
In production, most of a request's time goes to the database and the network, and Go and PHP wait on those identically. The multiplier shrinks fast. Teams that measure honestly after a migration often report single-digit to low-double-digit percent gains in average end-to-end latency.
The average hides where Go wins, which is tail-latency stability and cost per request. Go's p99 and p99.9 latencies stay flat under load because no request has to wait for a free FPM worker. You also serve the same traffic on far fewer machines, since one process absorbs concurrency that a pool can't. Those are the gains you notice in production and on the bill, and they last long after nobody cares about a synthetic benchmark.
Faster to Hire, Faster to Ship
Hiring and onboarding favor Go too. Its surface area is small, with 25 keywords and one enforced formatter, so a competent engineer coming from PHP or any other language is productive in a week or two. That lines up with the onboarding time the Cloudflare and Curve engineers described.
So you don't need to hire people who already know Go. They pick it up quickly, and the enforced formatting and static typing keep their code consistent from day one.
Go also tends to carry a salary premium in market surveys, which suggests demand is outpacing supply. The exact figures quoted from sources like Glassdoor vary by region and role, so treat them as a hiring tailwind, not a line in a business case. Go's ecosystem is strongest in networking, infrastructure, CLIs, and high-throughput services, which is the work you'd be migrating. Goroutines were built for that work, and the libraries and tooling around it are mature.
How to Migrate Without Betting the Company
Don't rewrite everything at once. Full rewrites are one of the most reliable ways to stall a product, because working code encodes years of bug fixes and edge cases you'd throw away (Joel on Software). The Cloudflare and Curve teams didn't do a big-bang rewrite either. They used the strangler pattern, and it's worth copying.
Start with the one service that actually hurts: the SSE stream saturating your pool, the fan-out API pinning your workers, or the gateway that can't hold enough connections. Rebuild just that service in Go, put it behind the same routing layer, and run it beside your PHP.
Route a slice of traffic to it and watch memory and tail latency before you route more. Your PHP keeps serving everything else the whole time, so there's no flag day and nothing rides on a single cutover. When that first service proves out, which it usually does, you have real numbers and a template for the next one.
That makes migration a per-service decision. You don't have to become "a Go shop." Keep PHP where it fits, like the CMS, the content pages, or the Laravel admin your team ships quickly in, and move the connection-heavy, high-throughput services to Go.
FAQ
Is Go faster than PHP?
On synthetic, CPU-bound benchmarks, yes, often by a large factor (TechEmpower). In production the average-latency gap is usually smaller, because most of a request's time goes to the database and network, and both languages wait on those identically. Go keeps its lead in concurrency and tail latency under heavy load. It holds thousands of connections in one process and keeps p99 latency flat where an FPM pool would run out of workers, so you need fewer machines for the same traffic.
Should I rewrite my whole PHP app in Go?
No. Migrate incrementally. Full rewrites are one of the most reliable ways to stall a product (Joel on Software). Use the strangler pattern: move the one painful, high-concurrency service to Go, run it beside your PHP, shift traffic over gradually, and expand once it proves out. You get the benefits without a risky flag day.
What kinds of services should I migrate first?
Start with the connection-heavy and high-fan-out ones: WebSocket and SSE endpoints, real-time features, API gateways, streaming responses, and any service where many requests sit open waiting on I/O. PHP's process-per-request model runs out of room on these first, and goroutines help right away, so they give you the quickest evidence that the migration is paying off.
Can PHP handle WebSockets and real-time features?
Yes, with help. Classic PHP-FPM ties up a worker per connection, so many long-lived connections exhaust the pool. Runtimes like Swoole and FrankenPHP keep workers alive and add event-driven concurrency to make it work. Go handles the same workload natively, with one cheap goroutine per connection. Connection-heavy services are the most common reason teams migrate.
Is Go harder to learn than PHP?
The syntax isn't. Go has only 25 keywords and one enforced formatting style, and most engineers are productive within one to two weeks. What's new for a PHP developer is the concurrency model (goroutines and channels) and handling errors as returned values instead of exceptions. Those take some practice, and they're also the skills you'll use most in the services you migrate.
Ready to Write Production Go?
The best way to evaluate Go is to write some. On LevelUpGo, every lesson is a hands-on exercise you solve in the browser. Your code runs on a real Go toolchain and is checked against the exercise's tests, so you get immediate feedback on working Go instead of reading about it.
A path for a PHP developer:
- Go Fundamentals covers types, structs, and error handling, the parts that feel different coming from PHP. It's free to start and needs no credit card.
- Composite Types covers slices, maps, and structs, the everyday building blocks for the data you move through a service.
- Go Concurrency Fundamentals teaches goroutines, channels, and
select, the features that let one Go process do what a whole PHP-FPM pool can't. - The roadmap lays out the path from your first program to a concurrent capstone project.
For more comparisons, read Switching From Python to Go, Go vs Rust in 2026, and Go vs Node.js and the supply-chain question.
Sources
Sources cited in this post, with primary practitioner accounts listed first:
- Go Time #316, Changelog (primary-source practitioner account of PHP-to-Go migration at Cloudflare and Curve).
- Janus API Gateway, HelloFresh (GitHub) (a Go API gateway in production).
- FrankenPHP and RoadRunner (modern PHP application servers, both written in Go).
- PHP 8.0 Release Notes, php.net (the JIT compiler).
- TechEmpower Framework Benchmarks (synthetic throughput comparisons).
- Joel on Software: Things You Should Never Do, Part I (the case against full rewrites).
