How Much Does Legacy PHP Modernization Cost?
There is no honest fixed price for modernising a PHP system you have not looked at. There are cost drivers you can name before anyone invents a rewrite budget.
Ask five vendors what it costs to modernise a legacy PHP application and you will get five confident numbers. None of them can be true yet. Cost is a function of the system: runtime age, how entangled the domain is with the framework, whether anyone can change a column without a weekend, and whether “modernise” means upgrade PHP or rewrite the company in another language.
A €1,200 Migration Readiness Audit exists because guessing the total is how programmes blow up. The audit is not the implementation budget. It is the cheapest way to stop treating a slogan as a quote.
This article is about what moves the money. It is not a price list for your monolith, and it is not a claim that Go is cheaper than PHP. Language choice is a later lever. Most of the budget is decided before anyone opens a new repository.
There is no honest fixed modernisation price
Two hypothetical systems, clearly labelled as such:
- Hypothetical A. PHP 8.2, Symfony LTS, a CI pipeline, PHPUnit on the money paths, a schema that already has owners, one embarrassing but isolated god service. Modernisation is a sequence of upgrades and a few boundaries. Weeks to a small number of months of senior time, not a programme with a war room.
- Hypothetical B. PHP 7.1, an unmaintained framework fork, Composer packages that no longer install, business rules in triggers and in copy-pasted controllers, no tests that the team trusts, a database that is also the integration bus. Modernisation is years if you pretend it is a rewrite, and still many months if you are disciplined about slices.
Same word, “modernise.” Different orders of magnitude. Anyone who quotes Hypothetical B like Hypothetical A is selling you a start date, not an outcome.
A third hypothetical sits in between, and it is the most common: PHP 8.0, Laravel or Symfony a major version behind, tests that cover happy paths, a few abandoned packages, one table everyone is afraid of, deploys that still work if the right person is in the office. That system is not a rewrite candidate. It is also not a two-week upgrade. The budget is the cost of making change boring again.
If someone gives you a single number before they have seen the Composer lockfile, the deploy path and the slowest queries, they are not estimating. They are anchoring.
Cost drivers
The useful question is not “what does PHP modernization cost.” It is which of these drivers are actually present.
PHP and runtime version. Each major jump has a finite, knowable surface: deprecated APIs, extensions, ini defaults. The cost explodes when the jump is coupled to a framework that itself cannot run on the target runtime. PHP 7.4 to 8.3 with a supported framework is a project. PHP 5.6 with a custom bootstrap that reimplements autoloading is archaeology. The runtime itself is rarely the expensive line; the things pinned to it are.
Framework version. Symfony or Laravel major upgrades are projects with a known shape: changelog, deprecations, a test gap, a few packages that lag. A custom framework that only one former employee understood is not a major-version upgrade. It is a rewrite of the application container, whether you admit it or not.
Abandoned dependencies. A package with no PHP 8.4 release is either a rewrite of that integration, a fork you now own, or a reason to stay on an old runtime. All three have prices. The hidden cost is the package you think is maintained because it still installs, while the last commit is four years old and the maintainer is gone.
Automated tests. Tests do not make upgrades free. They make upgrades possible without a human regression festival. Characterisation tests around the money paths are often the first modernisation spend, and they are the spend that later makes every other phase cheaper. No tests means you pay with time, downtime risk, or both. Buying a rewrite without tests is buying two products: the new one, and a discovery process for the old one.
Domain coupling. When “order” is a 4,000-line class that also sends email and writes to three databases, every change is expensive. Boundaries are the work. If you skip them and “just upgrade PHP,” you will still be able to ship, and every subsequent feature estimate will still be a lie.
Database coupling. Shared tables, implicit foreign keys, reports that select from production OLTP: schema change becomes an organisation-wide negotiation. See also what to fix before rewriting PHP in Go — a lot of “we must rewrite” is still database work. Dual-writing during a rewrite does not remove this cost; it doubles it until you pick an owner.
Deployment. If a release is a ritual, you will spend modernisation budget on the pipeline before you spend it on the domain. That is the correct order. A system you cannot deploy twice a week is a system you cannot modernise incrementally, which forces you toward bigger, riskier batches, which is how the budget grows.
Queues and background jobs. Workers are often the cheapest place to change runtime later. They are also easy to forget in a “website upgrade” quote. The practical PHP to Go migration guide starts here for a reason: a consumer with a clear contract is a slice; a checkout controller is not. If the quote does not mention workers, cron, and the job that only runs on the first Monday of the month, it is incomplete.
Observability. No traces, no slow-query log, no error budget: you will modernise blind and discover the cost in production. Adding decent metrics is cheap relative to a rewrite. Skipping them is how a “six-week upgrade” becomes an incident programme.
External integrations. Partner APIs with no sandbox, SOAP stacks, file drops to a mainframe: the PHP is not the expensive part. The calendar of the other organisation is. Budget the wait.
Downtime requirements. A retail peak that forbids a two-hour window changes the design of every migration. Zero-downtime is a feature you pay for in dual writes, expand/contract schema, and rehearsal. A Sunday-night window is cheaper. Pretending you have the window when you do not is the expensive option.
Rewrite versus incremental work. A full rewrite prices the entire product again, plus the period when two systems must agree. Incremental work prices slices. Upgrade, refactor or rewrite a legacy PHP system is the strategy choice that dominates the budget more than a contractor’s day rate. Teams that skip the strategy conversation pay for a rewrite they did not need, or they spend a year “preparing to rewrite” and ship neither the rewrite nor the upgrades.
What cheap projects have in common
Cheap here means surprisingly cheap given the age of the code, not cheap as in a junior ticket.
- The runtime is already on a supported PHP 8.x.
- Composer installs in CI without folklore.
- The money paths have tests, even if the admin UI does not.
- One person can deploy without a war story.
- The database has owners, or at least a list of tables nobody else should write.
- The pain is named: “this worker OOMs,” “this upgrade is blocked on package X,” “p95 on checkout is the query.”
Those systems still cost senior time. They do not cost a programme office.
What expensive projects have in common
- Business rules live in three places: PHP, SQL, and a spreadsheet the finance team emails weekly.
- The framework is a fork, or the “framework” is a folder called
core. - Staging does not resemble production, so every production release is a discovery process.
- The team that built it is gone, and the remaining team has learned not to touch certain directories.
- Success is defined as “rewrite it in a modern stack,” not as a metric.
The last one is a budget killer on its own. If the acceptance criterion is a new language rather than a measurable operational improvement, the work cannot finish. There is always more code.
Phases, because a blob estimate is how you get a blob failure
- Assessment. What hurts, what can stay, what is blocked on data. This is the PHP modernization audit shape: written, finite, allowed to conclude “do not rewrite.”
- Stabilisation. CI, backups, error tracking, the one deploy that currently requires a hero. Cheap relative to a rewrite; skipped constantly. If you skip it, every later phase inherits production risk as a tax.
- Runtime and framework upgrades. Move the floor so the rest of the work is not happening on abandoned software. This is the phase procurement understands, because it has a version number. It is also the phase that gets quoted in isolation while the test gap is ignored.
- Architecture changes. Boundaries, module extraction, stopping the god class from growing. This is usually where the real product velocity returns. It is also where people get bored and propose microservices instead.
- Optional workload extraction. Only where the runtime is the actual lever — typically workers, fan-out, CPU-heavy pipelines. Optional means you can stop after phase 4 and still have bought something.
- Production rollout. Rehearsal, rollback, metrics you agreed in phase 1. If phase 1 never named metrics, phase 6 is a feeling.
You can stop after any phase. A quote that only makes sense if you commit to phase 5 on day one is not a modernisation quote. It is a rewrite sales motion.
A hypothetical sequencing, labelled as such: a team spends two weeks on assessment, three weeks on CI and deploy, six weeks on PHP and framework, then a month bounding billing. They never extract anything to Go. The programme still paid for itself if deploys stopped being events and the next feature estimate shrank. That is modernisation. It will not appear in a case study titled “we moved to Go.”
How to think about people cost without inventing a total
Implementation on this site is priced at €402/day for PHP modernisation and PHP → Go work after discovery, and embedded backend capacity at €300/day. Those figures are rates, not project prices. A buyer still has to multiply by days, and days still depend on the drivers above.
A crude planning habit that does not pretend to be a quote:
- Count the phases you actually need.
- For each phase, name the unknown that would double the time (no tests, no staging, a package with no replacement, a table with two writers).
- Budget the assessment as a fixed cost so those unknowns are named before anyone staffs a quarter.
- Do not annualise a rewrite as if the product will freeze.
If finance needs a number they can put in a spreadsheet this week, the honest number is the assessment plus a range for the first production slice, not a five-year TCO for a system nobody has profiled.
What you should not pay for yet
- A Go rewrite of screens that are I/O bound and already fine
- A microservices estate to avoid a conversation about the data model
- A multi-year programme with no first production slice
- A day rate times 200 with no sequence
- A “platform team” whose first output is a new repo and a slide, not a change in production
Those purchases feel like progress. They are how modernisation budgets evaporate before the runtime is even current.
How a buyer should budget without lying to themselves
Budget the assessment as a fixed cost. Budget stabilisation as a short senior engagement. Budget upgrades as a project with a test gap as the risk register. Keep extraction optional until something measured says the runtime is the constraint.
If you need that sequence against your architecture rather than a generic checklist, that is the Migration Readiness Audit: €1,200, written recommendation, including the recommendation to keep the system in PHP. Implementation after that is scoped, at €402/day, only where the assessment says it is justified. Extraction, when the document names a slice, is the PHP to Go migration engagement.