Modernising a legacy system without a big-bang rewrite
By Lal Chand, founder of Codic Systems
Published Last updated 5 min read
Every business of a certain age has one: the system that does something important, was written by someone who left, and that nobody wants to touch. It runs. It earns money. Changes take weeks because every edit might break something unrelated.
I've worked on systems like that, including government and healthcare software where being wrong has real costs. The temptation, when you see the code, is to say "let's rewrite it." I'd usually advise against doing that first.
Why the big rewrite goes wrong
A rewrite sounds clean. In practice:
- Nothing ships until everything ships. For months or years the business pays for a system that earns nothing, while the old one still needs fixes.
- The old system knows things nobody remembers. The odd rule in the invoice code exists because of a customer, a law or a bug somebody paid for. A rewrite doesn't know that rule exists, and it comes back as a new bug.
- The target moves. The business keeps changing while you rebuild, so the new system is out of date on launch day.
Martin Fowler's name for the better approach is the strangler fig. The idea, in his description, is to replace legacy software gradually: build new functionality alongside the old system, find seams in the old code, and move behaviour across one piece at a time. Each step delivers something and teaches you more about the system.
Step one: find out what you've got
Before changing a line, I want answers to a few plain questions.
- What does the system do, in the business's own words?
- Who uses it, and what would they notice first if it stopped?
- Where does it run, and who has the keys?
- When did anyone last restore a backup, and did it work?
- What does it connect to?
That last question matters most. Hidden integrations, such as a scheduled job that emails a report or a file another system reads every night, are the usual source of surprises.
I'd call this an audit, and it's cheap compared with a surprise in week ten. It's also why I like a short, fixed-scope first stage before committing to a larger build.
Step two: protect what works
Old systems often have no tests. The code itself is the only specification.
So I write tests that describe what the system does today, even where I'm not sure it's right. These are sometimes called characterisation tests. They don't say the behaviour is correct. They say it's the behaviour we're about to preserve, so that a change which alters it is noticed.
Where tests are hard to add, I record real inputs and outputs and replay them. It isn't elegant, but it gives you a safety net, and a safety net is what you need before anything else.
Step three: upgrade the ground it stands on
Many legacy systems run on a language version that no longer gets fixes. PHP is a good example. The php.net page explains that each branch gets two years of active support, then two more years of security fixes only, then reaches end of life. As of 9 October 2026, the page lists 8.5 and 8.4 as actively supported, 8.3 and 8.2 as security-fixes-only, and 8.1 and older as end of life.
Running an unsupported runtime isn't automatically a disaster, but it means known flaws stay unpatched. The OWASP Top 10:2025 lists security misconfiguration and software supply chain failures among the main risks, and an old runtime with old dependencies fits both.
Upgrading the runtime and dependencies is often the best first change, because it's mostly mechanical, and the tests from step two tell you what it broke.
Step four: find a seam and cut there
Now the strangler approach starts. I look for a part of the system with a clear boundary: a report, a search page, an import job, a customer portal. Something with defined inputs and outputs that can be built separately.
The mechanics are usually simple. Put a thin layer in front of the old system, such as a reverse proxy or a router. Requests for the old part go where they always did. Requests for the new part go to the new code. Over time, more routes point to the new code and fewer to the old.
Each slice should:
- Be small enough to finish in weeks, not quarters.
- Be checked against the old behaviour using the tests.
- Be easy to switch back if it goes wrong.
That last point matters. If you can roll a slice back in a minute, you can take bigger risks.
Step five: retire things on purpose
When nothing uses an old part any more, turn it off, wait, and then delete it. Deleting is the point. A modernisation that adds a new system and never removes the old one leaves you maintaining both.
Fowler notes there is some transitional code needed while old and new coexist. That's the price, and it's worth paying. Plan to remove it when you're done.
What I won't promise
I won't give a delivery date before I've looked at the code. Legacy work has too many unknowns for that, and anyone who quotes a number from a one-hour call is guessing. What I can do is make the first stage small and fixed, so you learn a lot before you commit to a lot.
I also won't tell you to modernise when you don't need to. If the system works, is secure enough, and the business isn't being held back by it, leaving it alone is a respectable choice.
The principle underneath
Government systems taught me one thing about this: they outlive their builders. So I build for the person who will inherit the work. Boring technology, written notes, tests that describe behaviour and changes small enough to reverse.
The best modernisation is one the business barely notices, except that things start getting easier to change.
Questions this article answers
Should we rewrite our old system from scratch?
Usually not as a first move. A full rewrite delays value, and the old system contains years of decisions nobody wrote down. Replacing it piece by piece is generally safer.
What is the strangler fig pattern?
A way of replacing legacy software gradually. You build new functionality alongside the old system, route more behaviour to it over time, and retire the old parts when nothing depends on them. Martin Fowler named the pattern.
Is running an old PHP version a security risk?
It can be. PHP branches get security fixes for a limited time and then reach end of life. As of 9 October 2026, php.net lists 8.5, 8.4, 8.3 and 8.2 as supported, and 8.1 and older as end of life.
What should be done first on a legacy system?
Take a backup you have actually tested restoring, write down what the system does and who depends on it, and add tests around behaviour that matters before changing anything.
How long does modernisation take?
It depends entirely on the system. Anyone who quotes a duration before looking at the code is guessing.