PHP to Go migration · assessment, planning, implementation

Modernize Your PHP Monolith, Without a Big-Bang Rewrite

Your PHP monolith does not necessarily need to become Go. I identify where PHP should stay, where modernization is enough, and where extracting a workload into Go produces a measurable engineering benefit.

Senior PHP and Go backend engineering: architecture assessment, migration planning, implementation and production rollout.

Serbia-based · CET/CEST · B2B contract · EUR invoicing · English communication

When a PHP Backend Starts Fighting the Team

These are the symptoms that usually put a migration on the agenda. Most of them have more than one possible cause, which is exactly why the first step is measurement rather than a language decision.

Queue workers eating the cluster

Worker processes consume far more CPU and memory than the work justifies, and adding capacity means scaling the whole PHP runtime along with it.

Jobs that run for hours

Imports, exports and report generation run for minutes or hours inside a runtime built around short request lifecycles.

Concurrency that costs memory

Work that spends most of its time waiting on other systems still needs one process per in-flight operation, so concurrency is paid for in RAM.

Background processing nobody trusts

Jobs die quietly, retries duplicate side effects, and someone checks the queue by hand after every release.

Latency-sensitive endpoints

A handful of routes carry a tight p99 budget that framework boot and per-request setup keep eating into.

Scaling that only goes sideways

The answer to every load problem is more application servers, because no single component can scale on its own.

A runtime past its end of life

A PHP version or framework major that nobody has upgraded, which turns every dependency bump into its own project.

Deployments people schedule around

Releases are slow, partly manual, and risky enough that they happen outside business hours.

A monolith without seams

Everything reaches into everything, so a change to billing can break search and nobody can predict the blast radius.

Architecture debt setting the roadmap

Feature estimates are dominated by working around the current structure rather than by building the feature.

The First Decision Is Not "How Do We Rewrite It?"

The first question is whether a rewrite is necessary at all. Some PHP systems need architectural cleanup, a runtime upgrade, database work or better caching, and once that is done the pressure to migrate disappears along with the latency. That work is cheaper and easier to reverse, which is why it is worth knowing what to measure and fix in PHP before a rewrite is on the table.

Other systems contain a small number of workloads where Go's concurrency model, runtime characteristics or deployment model give a clear advantage. The point of the assessment is to identify those specific workloads before anyone commits to a migration, instead of treating the language itself as the problem. If you want the underlying comparison first, I have written about where PHP and Go genuinely differ as backend runtimes.

Keep PHP

Modernize what already works

  • PHP and runtime upgrade
  • Framework modernization
  • Profiling the real hot paths
  • SQL and index optimization
  • Redis and cache improvements
  • Queue and retry improvements
  • Architecture cleanup inside the monolith

Consider Go

Extract what PHP is a poor fit for

  • High-concurrency workers
  • Fan-out integrations
  • Streaming and data pipelines
  • Resource-heavy jobs
  • Latency-sensitive services
  • Independent backend components
  • Workloads that scale separately from the monolith

Migration Without the Big Bang

The migration runs as a loop rather than a project with a cutover date. Each pass through it is small enough to stop after.

  1. 01Measure
  2. 02Find the bottleneck
  3. 03Choose PHP or Go
  4. 04Extract one workload
  5. 05Deploy gradually
  6. 06Observe
  7. 07Decide what moves next

This is the strangler pattern applied to a PHP monolith: the old system keeps running while new implementations take over one responsibility at a time. The longer technical write-up is in the PHP to Go migration guide. What follows is how an engagement is actually sequenced.

  1. Profile the running system

    Measure where CPU, memory, database time and wall-clock time actually go, in production, under real traffic rather than on a laptop.

  2. Map the dependencies

    Which code paths touch which tables, queues, caches and third-party APIs. This is usually the step that changes people's minds about scope.

  3. Establish baseline metrics

    Throughput, p50/p95/p99 latency, error rate, queue depth and cost per unit of work, recorded before anything changes.

  4. Identify a candidate workload

    One workload with a clear boundary, measurable pain and a small blast radius if it has to be reversed.

  5. Define the boundary

    The API or event contract between PHP and the new component, including which side owns the data it reads and writes.

  6. Build it

    Implement the workload in Go against the agreed contract, with tests driven by real payload and message shapes taken from production.

  7. Roll out gradually

    Shadow traffic where the output can be compared, then a canary share, then the remainder. Queue consumers can simply run side by side.

  8. Monitor against the baseline

    Compare the new numbers with the ones recorded up front. If the improvement is not visible in the metrics, it is not an improvement.

  9. Verify rollback

    Exercise the rollback path instead of trusting that the flag works. A step you cannot reverse is not finished.

  10. Decide what moves next

    Review whether a second workload is justified. Stopping after one is a legitimate outcome, not a failed migration.

Start With a Migration Readiness Audit

Fixed price

€1,200Migration Readiness Audit · one-off

Discovery with a defined scope and a written deliverable. No commitment to any implementation work afterwards.

What you receive

  • Architecture review of the current system
  • Bottleneck analysis based on measurement, not opinion
  • A PHP versus Go decision matrix per workload
  • Concrete migration candidates, ranked
  • Technical risk map with mitigations
  • Dependency map across code, data and integrations
  • An initial migration sequence
  • Implementation estimate for the first workload
  • Written recommendations you can circulate internally

The recommendation may well be that no migration is justified. That is a useful result: it closes an open architectural question with evidence instead of opinion.

If Go Makes Sense, Migrate One Production Workload First

The pilot is a single workload on a hot path, real traffic, real data, real consequences, chosen because it is measurable and reversible. One workload in production tells you more than any proof of concept: you find out how the boundary behaves under load, how the deployment fits your infrastructure, and whether the improvement shows up in the metrics you agreed to watch.

Queue worker

The consumer whose throughput or resource use is the current constraint. Both implementations can read the same queue while traffic shifts.

Scheduled worker

A cron or scheduler job whose runtime has grown past the window it is supposed to finish in.

API endpoint

A single high-traffic or latency-sensitive route, placed behind the router or gateway you already run.

Integration service

The component that calls several third-party systems and spends most of its life waiting on them.

Data processor

Batch or streaming transformation work with a well-defined input and output.

Background service

A long-running component that does not fit the request and response model at all.

Queue consumers are the most common starting point, because both implementations can read the same queue and the traffic split is only a question of how many messages each side takes. The mechanics are covered in moving PHP queue workers to Go. If the discussion inside your team is really about splitting the system up rather than changing language, then whether a PHP monolith should become microservices is the more useful question to settle first.

Framework-specific notes for the two most common cases: Symfony to Go migration and Laravel to Go migration.

Implementation from €550/day after discovery. Migration implementation is scoped once the audit has established what moves and in what order. There is no fixed price for rewriting a whole monolith, because nobody can estimate that honestly before the dependency map exists.

You Work With the Engineer Making the Architecture Decisions

Senior enough to make the architecture decision. Small enough to work directly. Structured enough for European B2B procurement.

No handoff to a junior team

The person who assesses the architecture is the person who writes the migration code. Nothing is sold by one party and delivered by another.

PHP and Go in the same engagement

Deciding what stays and what moves needs both sides of the comparison. A Go specialist who cannot read your Symfony code will always recommend Go.

Architecture and implementation stay connected

Migration plans written by someone who will not implement them tend to skip the parts that turn out to be hard.

Direct technical communication

You talk to the engineer, in your channels, about the actual system. Nobody relays requirements second-hand.

Scope grows one step at a time

An audit first, then one workload. Each step is small enough to stop after without leaving stranded work behind.

An engagement procurement can process

One B2B agreement, EUR invoicing, and the standard documents European companies need before an external engineer starts.

Working stack

  • PHP
  • Symfony
  • Laravel
  • Go
  • PostgreSQL
  • MySQL
  • Redis
  • REST / gRPC APIs
  • RabbitMQ
  • Docker
  • Kubernetes
  • CI/CD

Remote from Serbia, working hours overlapping CET/CEST. Engagements run on one B2B agreement with a monthly invoice in EUR.

Frequently Asked Questions

Should we rewrite our entire PHP monolith in Go?

Almost never, and certainly not as a first move. A full rewrite means running two systems in parallel, freezing product work, and rediscovering years of undocumented business rules that exist only in the current code. The parts of a PHP monolith that actually hurt are usually a small subset: a few workers, a couple of endpoints, one integration that fans out. Moving those to Go while the monolith keeps serving everything else gives you most of the benefit for a fraction of the risk. If the rest of the system is stable and cheap to run, leaving it in PHP is the correct engineering decision rather than a compromise.

When does Go actually make sense?

When the workload profile is the problem, not the code quality. Go earns its place where you need many concurrent in-flight operations inside one process, long-lived connections, predictable memory under sustained load, or a single static binary that starts in milliseconds. The request-per-process model that PHP is built around makes those workloads expensive, because you scale by adding processes and every process carries a full interpreter and framework boot. Good candidates are queue consumers, fan-out API aggregation, streaming and data pipelines, and latency-sensitive services with a tight p99 budget. If the bottleneck is a missing index or an N+1 query, Go will not fix it: it will run the same bad query faster.

Can PHP and Go run together?

Yes, and that is usually the end state rather than a transition phase. The two sides communicate over the same boundaries you would use for any service: HTTP or gRPC calls, a shared queue such as Redis, RabbitMQ or SQS, or events on a topic. In practice the monolith stays the system of record and the Go component takes over one job behind a clear contract. Sharing a PostgreSQL or MySQL schema directly is workable early on but should be treated as temporary, because two writers on one schema removes the boundary you were trying to create. Deployment stays separate, so the Go component can be rolled back without touching PHP.

Can you modernize the PHP system instead?

Often that is the whole engagement. Upgrading the runtime to a current PHP 8.x release, replacing an abandoned framework layer, configuring OPcache and preloading properly, fixing the queries that dominate database time, and putting real caching in front of expensive reads will frequently produce a larger improvement than any rewrite, for a fraction of the cost. It also leaves you with one system to operate instead of two. I would rather tell you that your PHP is fine and your database needs attention than sell you a migration you do not need.

Do we need microservices?

No. Extracting a workload is not the same as adopting microservices. What usually makes sense is a monolith plus a small number of services that exist for a concrete operational reason: independent scaling, isolation of a risky workload, or a genuinely different runtime profile. A fleet of small services brings distributed transactions, new network failure modes and per-service operational overhead, and teams that go there without platform capacity generally regret it. If you cannot name the reason a component must deploy and scale on its own, it belongs in the monolith.

How do you migrate without downtime?

By never making the cutover a single irreversible event. The Go implementation ships alongside the PHP one and traffic moves behind a flag: shadow first, where the new path runs and its output is compared but not used, then a small share of real traffic, then the rest. The baseline metrics collected before any code changes are what tell you whether the new path is genuinely better rather than just newer. Every step keeps the PHP path deployable, so a rollback is a flag change instead of a redeploy. For queue consumers this is easier than it sounds: both consumers run against the same queue and you shift the share of messages each one takes.

Can you work with Symfony?

Yes, and Symfony is usually the easier case. The framework pushes teams toward explicit boundaries such as dependency injection configuration, Messenger transports and console commands, and those map cleanly onto workloads that can be lifted out. Messenger handlers are frequently the first thing worth moving, because the message contract already exists and only the handler changes. Doctrine mappings are also useful as documentation of the schema when the domain model is not written down anywhere else.

Can you work with Laravel?

Yes. Laravel systems tend to have more behaviour wired through framework conveniences such as facades, Eloquent models carrying domain logic, and queued jobs that reach into the container, so the first step is mapping what a job actually touches before deciding whether it can leave. Horizon queues and scheduled commands are the common extraction candidates. Where the boundary turns out to be unclear, it is often cheaper to restructure inside Laravel first and revisit the migration question afterwards.

How much does a migration cost?

Discovery has a fixed price: the migration readiness audit is €1,200 and produces an architecture review, a PHP versus Go decision matrix, a risk map and a proposed sequence. Implementation is €550 per day and is scoped after that audit, because before discovery any number would be invented. I do not quote a fixed price for rewriting a monolith, because fixed-price rewrites are how both sides end up arguing about scope instead of doing engineering. Most engagements start with a single workload, which is a small enough commitment that you can stop there if the results do not justify continuing.

Can you work with our existing development team?

That is the intended arrangement. Your engineers hold the expensive knowledge, which is the business rules and the failure history of the system, while what I add is migration experience and the capacity to do the extraction without stalling your roadmap. In practice that means working in your repository, your review process and your tracker, pairing with whoever will own the Go component afterwards, and writing the decisions down so the pattern can be repeated without me. An engagement that leaves your team unable to maintain the result has failed, even if the code works.

Next step

Send me the shape of your PHP system

The PHP and framework versions, roughly what the traffic looks like, and the part of the system causing the most pain right now. What you get back is an assessment of whether this is a modernization problem or a migration problem, not a pitch for a rewrite.

Serbia-based · CET/CEST · B2B contract · EUR invoicing · English communication