EDEvangelos Dimitriadis
  • About me
  • Blog
  • Services
  • Projects
  • OpenSource
  • Uses
  • Contact
← Back to the blog

Go in Production: When a Service Does Not Belong in PHP

ED
Evangelos Dimitriadis
June 22, 2026
Go in Produktion: wann ein Service nicht in PHP gehört

Photo by Jens Lelie on Unsplash

A Laravel app that has run reliably for years gets a new requirement: a live dashboard meant to hold thousands of browsers open at once over WebSocket. The first setup with PHP-FPM falls over long before the thousand is reached, because every process holds one connection and the memory runs away. That is not Laravel's fault. It is the wrong tool for exactly this one job.

I ship most of my work in PHP and Laravel, and that is meant to stay that way. That is exactly why this article is not a case for a rewrite and not a "Go is better" sermon. It is about the narrow set of workloads where PHP fights its own runtime model, and why Go is often the better choice there. The list is short, and that is on purpose.

Versions are Go 1.26, as of June 2026.

What PHP and Laravel Are the Right Choice For

Let us start with the truth that tends to get lost in articles like this: for most of what a mid-sized company builds, Laravel is the right choice, not a compromise. Request-response web apps, CRUD, business logic, admin areas, customer portals. That is Laravel's home, and it is very good at it.

The lever is the ecosystem. Eloquent, queues, auth, validation, the whole framework battery pack is there and well thought out. A feature that stands up in Laravel in an afternoon is often days of handwork in Go, because you have to build half the batteries yourself. That development speed is not a luxury, it is the reason small teams can ship at all.

Rebuilding most of that in Go would be more expensive and slower, with no gain. Anyone planning it should first read what a rewrite really costs. The interesting question is not whether to replace Laravel, but where a second stack has its place alongside it.

Where PHP Fights the Runtime Model

PHP-FPM is process-per-request. A request comes in, a process handles it, returns the response, and is free again. For web apps this model is ideal, because it is simple and robust. For a handful of workloads, that exact model becomes the problem.

Many Concurrent Connections

WebSockets, server-sent events, anything real-time. Here the connection stays open, sometimes for hours, and does nothing most of the time. In the process-per-request model, every open connection binds a process and its memory. At a thousand concurrent connections, that is a thousand processes, and the server buckles. Octane with Swoole is PHP's own answer to this and shifts the limit considerably, but it does not solve the fundamental model problem. Go holds those same thousand connections with a thousand goroutines that together need less memory than a handful of PHP processes.

High-Concurrency IO Fan-Out

A service that fires thousands of outbound calls in parallel, for example an aggregator that queries many APIs at once and merges the responses. In PHP, true parallelism for this is a fight, with tricks via process forking or async extensions. In Go it is what the language was built for: a few lines with goroutines, and a thousand calls run at once.

CPU-Bound Processing

Image and video processing, large parsing, cryptographic work. PHP is slow per core, and as long as a worker computes, it is blocked. An image-processing job that eats ten seconds of CPU holds the entire worker for ten seconds. Go is considerably faster per core and spreads the work across multiple cores without contortions.

Binaries to Distribute

CLI tools, agents, and daemons meant to run on someone else's machine, without a PHP runtime installed there. A Go program compiles to a single static binary you copy and start. No runtime setup, no dependencies, no version conflicts. For a tool a customer is supposed to run on their server, that is an enormous difference.

The common denominator of all these cases: none are request-response web apps. Exactly where Laravel is strong, Go is unnecessary. And exactly where PHP fights its model, Go plays to its strength.

Why Go for These Cases

Goroutines are the core. They are so cheap that hundreds of thousands at once are feasible, where processes or operating-system threads give up long before. The Go scheduler spreads them across the available cores without you having to care. Concurrency that is a project in other languages is a few lines in Go:

package main
 
import (
	"context"
	"golang.org/x/sync/errgroup"
)
 
func fetchAll(ctx context.Context, urls []string) ([]Result, error) {
	results := make([]Result, len(urls))
	g, ctx := errgroup.WithContext(ctx)
	g.SetLimit(100) // at most 100 at once
 
	for i, url := range urls {
		i, url := i, url
		g.Go(func() error {
			r, err := fetch(ctx, url)
			if err != nil {
				return err
			}
			results[i] = r
			return nil
		})
	}
 
	if err := g.Wait(); err != nil {
		return nil, err
	}
	return results, nil
}

A thousand parallel calls, cleanly limited, with error handling. That is the code you need a whole library and a lot of caution for in PHP.

On top comes the deployment model. Go compiles to a single static binary you put into a tiny container image, often a few megabytes instead of hundreds. No runtime setup, fast startup, low memory. That fits many small Fargate tasks and scale-to-zero patterns. The standard library brings HTTP, concurrency, and encoding, without needing a framework.

And Go is a mature platform, not a bet. Version 1.26 from February 2026 has the Green Tea garbage collector as default, which brings 10 to 40 percent less GC overhead depending on the program, plus around 30 percent less cgo overhead. The language has been carefully evolved for over ten years without losing its simplicity.

The Honest Counterpoint

Go is not a better PHP, it is a different tool. For CRUD and business logic it is more cumbersome: more boilerplate, no Eloquent, no validation and auth package out of the box. What is a Composer package in Laravel is often a custom build in Go. The ecosystem for business apps is smaller, because Go comes from the infrastructure world, not the web-app world.

And there is the team question. A second language nobody on the team knows is a risk, not an advantage. Whoever introduces Go needs at least two people who know it, otherwise the one Go service becomes a bus-factor-of-one problem.

The consequence is simple: you do not build the admin area, the customer portal, the billing logic in Go just because a service next door runs in Go. The language follows the workload, not the fashion.

Polyglot Without Sprawl

A second stack is a running tax. Two CI pipelines, two hiring profiles, two operations runbooks, constant context switching in the developers' heads. You do not pay this tax once, but every month. It only pays off when the workload justifies it.

The rule that prevents sprawl: decide per service, not per preference. A Go service needs a concrete reason and a clear seam. In practice that means the Go service has a narrow, defined task, and Laravel stays the system of record. Only contracts are shared, not code. An HTTP API with an OpenAPI schema, a shared queue, or the same database only across clearly drawn boundaries. What you avoid is the tight coupling of the Go service to Laravel's internal models, because then you have built yourself a distributed monolith that combines the downsides of both worlds.

The actual anti-goal is splitting microservices by language. Not every service needs its own language. Most belong together in one Laravel codebase, and only the one workload that truly stands out gets its own Go service.

Concrete Decision Scenarios

This table assigns real workloads to the right language:

WorkloadChoiceWhy
Admin area, CRUD, business logicLaravelecosystem, speed, system of record
WebSocket or SSE gateway, many open connectionsGocheap concurrency, low memory
Aggregator with thousands of parallel API callsGogoroutines, IO fan-out
Image or video processingGoCPU-bound, would otherwise block the worker
Scheduled report that runs at nightLaravelqueue job, no reason for Go
CLI tool to distribute to customersGosingle binary, no runtime
API gateway or proxy in the hot pathGolatency and throughput
Standard webhook receiverLaravelfits the existing system

As soon as many connections, high concurrency, CPU load, or a binary to distribute are in play, the scale tips toward Go. For everything else, Laravel stays the obvious choice.

How Both Coexist on AWS

The Go service runs as its own ECS Fargate service, alongside the Laravel services from the Fargate setup. Same VPC, own task definition, own scaling. Communication runs either over HTTP, internally behind the load balancer or via service discovery, or over a shared queue like SQS or Redis, depending on how tight the coupling should be. A WebSocket gateway talks directly to the browsers and reports events to Laravel via a queue. An image-processing service pulls jobs from the same queue Laravel puts them into.

The container image is the part that brings joy. A multi-stage build compiles the binary and puts it into an empty image:

# build stage
FROM golang:1.26 AS build
WORKDIR /src
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 go build -ldflags="-s -w" -o /app ./cmd/gateway
 
# final stage: empty image, only the binary
FROM scratch
COPY --from=build /app /app
COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
EXPOSE 8080
ENTRYPOINT ["/app"]

The result is often a few megabytes instead of the hundreds of a PHP image. Startup is nearly instant, which matters on scale-out and scale-to-zero. The deploy fits into the same pipeline: image to ECR, rolling service update. On the platform level, the Go service is simply one more container sharing the container base.

Anti-Patterns

Rewriting a working Laravel app in Go. Because Go is faster. Expensive, risky, and the gain is almost always smaller than the cost. If the app has a performance problem, it rarely lies with PHP and almost always with a query or a missing cache layer.

Splitting microservices by language. Artificial boundaries, more operations, no gain. You split services along domain boundaries, not by programming language.

Go for CRUD. Lots of boilerplate, slow development, no ecosystem leverage. That is Laravel's home game.

Two languages for a team that can barely staff one. If you struggle to find enough PHP developers, you do not double the problem with a Go service. That is risk sold as breadth.

Premature polyglot. Introducing a second stack before there is a workload that really needs it. The second stack follows the need, not the curiosity.

Tightly coupling the Go service to Laravel's models. Instead of to a clear contract. That builds a distributed monolith that combines the complexity of microservices with the coupling of a monolith.

Conclusion

The honest answer to "Go or PHP" is "both, in the right place". Laravel stays the default for most of what a mid-sized company builds, and for good reasons: ecosystem, speed, a proven system of record. Go takes on the narrow set of workloads where PHP fights its runtime model, meaning many connections, high concurrency, CPU load, and binaries to distribute.

The trigger is never the language itself, but a concrete workload. Whoever introduces a second stack because a specific service no longer holds up in Laravel makes a good decision. Whoever does it because Go sounds good at conferences buys a running tax with no return. A second stack only pays off with a clear seam and a clear reason, and you decide per service, not per preference.


Are you hitting a wall with your PHP or Laravel app and wondering whether a second stack is the answer? Contact me for an architecture and stack-fit review that checks where a second stack truly makes sense and where it would only be complexity.

About meBlogProjectsContactImprintPrivacy Policy

Made in Gerlingen, 2026