The architecture diagram for a cloud migration always looks clean. Old server on the left, managed services on the right, an arrow in between. The trouble lives in the arrow. After several of these moves, we see the same problems each time, and almost none of them involve the cloud provider.

Files on local disk

Older apps write to the server's disk: uploaded images, generated PDFs, cache files, sometimes session data. On a single server that works. Once you run two copies of the app behind a load balancer, a user uploads a file to one copy and requests it from the other.

Plan: search the code for every file write before you migrate. Uploads go to object storage (S3 or equivalent), sessions go to Redis or the database, and temporary files stay temporary. We usually find two or three writes nobody knew about, such as a report export or a log file the admin panel reads.

Hidden cron jobs

The old server has a crontab. Nobody documented it. It sends invoices at midnight, cleans a table on Sundays and renews a certificate. After the migration, those jobs stop and nobody notices for a week.

Plan: copy the crontab (and check every user's crontab, not only root's) on day one. Each job becomes a scheduled task in the new platform with an alert if it fails.

Hardcoded addresses

Config files point at localhost for the database, an internal IP for the mail server, a file path for the SSL certificate. Some of these live in the database itself, in a settings table.

Plan: move every address into environment variables and grep the database for IPs and hostnames. We keep a spreadsheet of every external dependency with its old and new address and tick them off during cutover.

Timeouts and connection limits

The app worked with a database on the same machine. Now the database sits across a network with managed connection limits, and a page that ran 200 small queries takes seconds instead of milliseconds. Serverless functions make it worse: each one opens its own connection and the database runs out.

Plan: load-test in the new environment before cutover, with production-sized data. Add connection pooling. Fix the worst N+1 query pages, which the migration usually exposes for the first time.

The cost surprise

Teams move a server that cost a fixed monthly fee to a setup billed by use, then leave everything running at full size. Data transfer between regions or out to the internet shows up as a line item nobody budgeted.

Plan: set a budget alert before migrating a single service. Tag every resource with the project name so you can see where the money goes. Right-size after two weeks of real traffic, not before.

Cutover without drama

We migrate data twice. The first run is a rehearsal, timed, with a checklist. It tells us how long the real cutover takes and what breaks. For the real run we lower DNS TTLs a day in advance, put the old app in read-only mode, sync the final data and switch. The old server stays up, untouched, for a week so we can roll back.

The takeaway

Cloud platforms rarely cause migration failures. Assumptions baked into the old app do: that there's one server, one disk and one clock. List those assumptions before you start and the migration becomes a checklist instead of a firefight.

← More in Cloud & DevOps