· 7 min read

CodeIgniter to Laravel: a staged migration playbook

How to move a live CodeIgniter or plain-PHP application to Laravel in reversible stages, keeping every URL and the business running.

By

A staged CodeIgniter to Laravel migration moves a live PHP application to Laravel one area at a time, with the old system serving everything that hasn't moved yet, so that the business never stops and every step can be reversed. It is the safer alternative to a "big bang" rewrite that goes live on one night and has no way back.

I've done this for a motoring magazine. TopGear India's CMS moved from CodeIgniter on shared hosting to Laravel on Google Cloud while the newsroom kept publishing every day, and the load time of its home and category pages went from 1–3 min → 3 s. Below is the method I use, with the parts that matter most and the parts people skip. Where I describe what happened on TopGear specifically, I say so; the rest is the general playbook, not a record of that project.

Why not rewrite and switch over in one go?

Because the old system knows things nobody wrote down. Years of small fixes, odd URLs that external sites link to, reports someone in finance runs once a quarter. A one-night switch finds all of them at once, in production. A staged move finds them one area at a time, when each is cheap to fix.

The pattern has a name. Martin Fowler calls it the Strangler Fig Application: the new system grows around the old one and takes over gradually, so that investment and returns arrive "gradually and visibly". It doesn't mean patching the old code forever. On TopGear the code was rewritten rather than patched, because patching the old stack had stopped paying off. Staging is about how you go live, not about how much you rewrite.

Step 1: what does the assessment cover?

Before anything moves, I read the code and write down what I find. For a CodeIgniter or plain-PHP application, the assessment covers:

  • Routes and URLs. Every public URL, including the ones the router doesn't know about (rewrites in .htaccess, files served directly). The full list comes from the code, the server logs and Search Console, not from memory.
  • Data. The schema, the tables nobody uses, the columns that mean different things in different places, and every script that writes to the database outside the application.
  • Integrations. Payment providers, mail, feeds, partner APIs, cron jobs, anything that calls in or out.
  • How people really use it. Editors, support staff and admins, watched doing their actual work.
  • Hosting. What runs where, how it is deployed, how it would be restored.

The output is a written plan: what to keep, what to rewrite, what to retire, and the order of the stages, starting where the value is highest and the risk is lowest.

Step 2: how do old and new run side by side?

Put a routing layer in front of both systems. It can be the web server, a load balancer or a CDN rule: requests for areas that have moved go to Laravel, everything else goes to CodeIgniter. At the start, everything goes to the old system. Each stage changes a few routing rules.

The two systems share the database at first. That is what makes the stages small: Laravel reads and writes the same tables, through Eloquent models mapped onto the existing schema, so there is no data to sync while both are live. Schema improvements come later, one table at a time, once the code that uses each table has moved.

Sessions and sign-in need a decision early. Either both systems read the same session store, or the new one becomes the source of sign-in and the old one trusts it. Getting this wrong is the most common reason users are logged out mid-migration.

Step 3: what moves first?

Usually a read-heavy, self-contained area: public article pages, a listing, a search page. It proves the hosting, the deploy pipeline, the caching and the monitoring on real traffic, with low risk, because reads are easy to compare between old and new.

StageWhat movesHow it's verifiedWay back
0Nothing: routing layer, new hosting, CI/CD, monitoringTraffic flows through the router to the old system unchangedRemove the router rule
1A read-only public areaResponses compared page by page; speed measuredRoute back to the old system
2Editorial or admin screens for that areaEditors work in the new screens on staging, then liveRoute back; same database
3Writes: forms, comments, ordersEvery write checked in the database and downstream systemsRoute back; writes went to the same tables
4Remaining areas, one per stageAs aboveAs above
5Schema clean-up, old system retiredNothing routes to the old system for an agreed periodKept as a backup until sign-off

How do you keep URLs and search traffic?

Every public URL either keeps its exact address or gets a permanent redirect to its new one, in one hop. Google's site move guidance is explicit that 301 and other permanent redirects don't cause a loss in PageRank, and its redirects documentation lists 301 and 308 as the permanent server-side options. The risk isn't the redirect; it's the URL nobody listed.

So the redirect map is built from the full URL list in the assessment, kept in version control, and tested automatically: every old URL is requested and must answer with a permanent redirect to the right page, or with the page itself. That test runs before each stage and again after cut-over. On TopGear, keeping every URL stable limited some structural changes, and it protected years of search traffic.

How do you know the data is right?

For stages that write data, I compare before relying on anything: row counts per table, totals for anything financial, and a random sample of records read through both systems and compared field by field. When the schema changes, migration scripts are written to be re-run safely, rehearsed on a copy of production, and timed, so the real run holds no surprises.

Where does the speed come from?

Not from the framework alone. On TopGear, the slow pages came from slow SQL: the queries were rewritten and the tables indexed for how the site actually reads them. Laravel makes the common fix easy: its documentation describes eager loading as the way to avoid the "N + 1" query problem, where related records are fetched once per row instead of once per page. Add Redis caching and an edge CDN so most page views never reach the database, serve every image at the size it's shown, and move to infrastructure that can be scaled and monitored. The rebuild also took bounce rate from 70% → 30%.

Which Laravel version should you land on?

The current one, with a plan to stay current. Laravel's support policy gives each major release bug fixes for 18 months and security fixes for two years, with a new major version each year. Landing on an old version to "reduce risk" just schedules the next migration. Write the upgrade into the maintenance plan from the start.

What do teams usually forget?

  • Cron jobs and scripts that write to the database outside the application.
  • Files on disk: uploads stored next to the code on shared hosting, which have to move to object storage.
  • Email: templates, sending domains and bounce handling.
  • Editors' habits. A CMS migration only succeeds once publishing is easier as well as faster. On TopGear, publishing moved from hours to minutes for the editorial team, and that, more than speed, is what made the new system stick.
  • Switching the old system off. Leave it running "just in case" and it stays forever. Agree a date when nothing routes to it, keep a backup, and retire it.

How I do this

The legacy modernization offer starts with a fixed-price assessment that ends in the written stage plan above, then quotes each stage separately, so you can stop after any of them with a working system. If your application is already on Laravel and several versions behind, Laravel development and upgrades covers the version-by-version path. The TopGear India case study has the architecture and the results.

Sources