+1 (276) 265-7197

Moving a LAMP Application to AWS Without Rewriting It

Most LAMP applications that move to AWS do not need to be rewritten, containerized, or made "cloud native" first. They need to run on hardware someone else patches, with a database someone else backs up, and with a rollback path if the cutover goes badly. That is a lift-and-shift, and on a typical single-server PHP/MySQL app it is a few weeks of careful work — not a replatforming project.

The reason this comes up is usually unglamorous: the box is out of warranty, the colo contract is ending, the hypervisor is on an OS with no security updates, or the one person who knew the server left. Those are good reasons. "Cloud" by itself is not, and the move will not make a slow query fast.

Step 1: inventory the server you actually have, not the one in the docs

Every legacy server has undocumented state. Find it before you build anything, because whatever you miss becomes a production incident at 2 a.m. on cutover night.

# what is listening, and what is running
ss -lntup
systemctl list-units --type=service --state=running

# PHP: version, modules, ini files that are actually loaded
php -v; php -m; php --ini

# Apache: enabled modules and every vhost
apachectl -M; apachectl -S

# scheduled work, including the crontabs nobody mentions
crontab -l; ls -l /etc/cron.d/ /etc/cron.*/; for u in $(cut -d: -f1 /etc/passwd); do crontab -l -u $u 2>/dev/null | sed "s/^/$u: /"; done

# anything writing outside the document root
find / -xdev -newermt '-30 days' -type f -not -path '/proc/*' -not -path '/var/log/*' 2>/dev/null | head -50

Write down: PHP version and extension list (including the ones installed from source or PECL), MySQL version and non-default my.cnf values, mail delivery (a local sendmail/postfix will not exist on the new host), TLS certificate sources and renewal, upload directories, generated PDFs and reports, log destinations, IP allowlists pointed at the current address, and every cron job with its schedule and owner. Half of the surprises in a migration are in that last item.

Step 2: pick the boring target architecture

For a single-server app serving up to a few million requests a month, the honest target is:

  • EC2 for Apache and PHP — same major OS family, same PHP minor version as production. Match the version first; upgrade after you are stable. One change at a time.
  • RDS for MySQL at the same version you run today (5.7 is no longer an option on RDS as a new engine, which is worth knowing early — if you are still on 5.7, the version upgrade and the move become one project, and it needs the rehearsal discipline of any upgrade).
  • An Application Load Balancer in front, largely so TLS termination and certificate renewal become a managed problem rather than a cron job on a box.
  • S3 for user uploads and generated files, if the application can be pointed at it cheaply. If it cannot, EFS mounted at the same path is the compatibility option — slower per operation, but it requires no code change and it lets you add a second instance later.

What we do not recommend on a lift-and-shift: containerizing during the move, splitting the app into services, swapping Apache for something else, or adopting a framework. Each is a defensible change on its own schedule; stacking them onto a migration means that when something breaks you have five suspects instead of one.

Step 3: size from measurement

Instance sizing is where cloud bills go wrong in both directions. Take a week of real numbers off the current server before you choose anything:

# CPU, memory, and I/O over time (sysstat)
sar -u -r -b -f /var/log/sa/sa$(date +%d)

# actual database size, table by table
SELECT table_schema, ROUND(SUM(data_length+index_length)/1024/1024) AS mb
FROM information_schema.tables GROUP BY table_schema;

# peak concurrency and buffer pool pressure
SHOW GLOBAL STATUS LIKE 'Max_used_connections';
SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_reads';

A database whose working set fits in RAM behaves completely differently from one that does not, so pick an RDS class whose memory comfortably exceeds your current innodb_buffer_pool_size, and set gp3 IOPS from measured I/O rather than the default. On the web tier, the pm.max_children arithmetic is unchanged by the move — it is still average worker RSS against available memory, and an undersized instance will swap in AWS exactly as it did in the rack.

One AWS-specific trap: storage-level throughput on smaller instance types is credit-based. A nightly mysqldump or a batch import can exhaust burst credit and turn a fast machine into a slow one for hours. Watch the burst-balance metric during your first month.

Step 4: rehearse the whole thing on restored data

Build the target from a restored backup, not from production access. Restore last night's dump into RDS, deploy the code, point a test hostname at the load balancer, and run the application: log in, place an order, generate the report that hits the ugliest query, send an email, run each cron job by hand. Compare page timings against production — if a page is three times slower, find out why now, because "it got slow after the migration" is much harder to debug once you are live.

The common findings at this stage are dull and easy to fix: a PHP extension that was compiled by hand and is missing, a hardcoded /var/www/uploads path, mail silently discarded because there is no local MTA (use SES or an authenticated relay, and expect sending-domain checks), and an outbound IP change that breaks a payment gateway or supplier allowlist. Collect the new egress addresses — a NAT gateway or Elastic IP — and get them allowlisted weeks ahead. That request always takes longer than the engineering.

Step 5: the cutover

The database is the only part that cannot be copied twice, so plan the move around it. For most apps the replica approach keeps the read-only window to minutes:

  1. Load a dump into RDS, then set the old server up as the replication source and let RDS catch up. Let it run for a day; confirm Seconds_Behind_Source sits at zero.
  2. Lower the DNS TTL for the application hostname to 60 seconds, at least 48 hours ahead.
  3. At the cutover window, put the app into maintenance mode on the old server — read-only or a static page. Note the time.
  4. Wait for replication lag to reach zero, then promote RDS: stop replication, make it writable, point the new application at it.
  5. Move DNS to the load balancer. Keep the old server running but writable-by-nobody, so late-resolving clients cannot write to a database you are about to abandon. That one detail prevents the worst outcome of a cutover: orders written to the old database for six hours after the move.
  6. Watch error logs, slow query log, and the queue of any background workers for the first hour, then again after the first nightly cron cycle.

If the app tolerates a longer window and the database is under about 20 GB, a straight maintenance-mode dump and restore is simpler and less to get wrong. Measure the restore time during rehearsal so the announced window is a number, not a hope.

Step 6: keep rollback warm for a week

Do not decommission anything on cutover day. Leave the old server intact, patched, and shut out of write traffic for a week or two — long enough to cover a monthly billing job or a quarter-end report. Rollback is DNS back plus a decision about the writes that landed in RDS, which is why keeping the window short matters: a few minutes of orders can be replayed by hand, a day of them cannot.

After a couple of stable weeks, the work that was deferred becomes worth doing in order: version upgrades on PHP and MySQL, backup-restore drills against RDS snapshots (a snapshot you have never restored is the same hope a dump you have never restored is), CloudWatch alarms on the numbers you measured in step 3, and only then any question of containers or refactoring.

A lift-and-shift does not modernize the application. It changes who is responsible for the hardware, makes backups and TLS someone else's routine, and buys you the ability to resize in an afternoon. That is a fair trade for a few weeks of careful work — and it is enough. If someone tells you the move requires a rewrite first, ask them which of your business requirements changed.