How to move ecommerce infrastructure from a single VPS to HA without rewriting the application

Binadit Tech Team 26 August 2026 7 min leggi
How to move ecommerce infrastructure from a single VPS to HA without rewriting the application

What you will achieve and why it matters

Most stores start on a single VPS: web server, database, and file storage all on one box. It works, until that box reboots for a kernel update, runs out of memory during a campaign, or the disk fills up at 2am. This guide walks through moving that same application to a highly available architecture, load balancer, multiple app nodes, a managed or replicated database, shared storage, without rewriting the codebase.

This matters because the business risk of a single VPS is not exotic, it is a maintenance window, a traffic spike, or a hardware fault. The fix is architectural, not a code migration. If your application runs on PHP, Node, or Python with a relational database, the pattern below applies whether you run WooCommerce, Magento, or a custom stack.

Prerequisites and assumptions

Before starting, confirm the following:

  • Your application is stateless enough to run on more than one server, or can be made so (sessions, uploaded files, and cache are the usual blockers)
  • You have root access to provision new servers, not just an FTP account
  • You can schedule at least one maintenance window for DNS and database cutover, even if the goal is near-zero downtime
  • You have a recent, tested backup before touching anything
  • You know your current traffic pattern: peak requests per second, database connections, and average payload size

This guide assumes a typical LAMP or LEMP-style stack (Nginx or Apache, PHP-FPM or Node, MySQL or PostgreSQL, Redis for cache/sessions). The same principles apply to other stacks with different tooling.

Step-by-step implementation

Step 1: Separate state from compute

The single biggest blocker to horizontal scaling is state stored on local disk. Before adding servers, move these off the application server:

  • Sessions: move from file-based sessions to Redis
  • Uploaded media: move to object storage (S3-compatible) or a shared NFS volume
  • Cache: move from local disk/OPcache-only setups to Redis or Memcached

Example PHP session config change:

; php.ininsession.save_handler = redisnsession.save_path = "tcp://10.0.0.5:6379"

For uploaded files in a PHP application, most frameworks support a storage abstraction. For WooCommerce specifically, plugins like WP Offload Media handle this without custom code. For custom applications, replace direct filesystem writes with an S3-compatible SDK call:

$s3->putObject([n    'Bucket' => 'store-uploads',n    'Key'    => $filename,n    'Body'   => fopen($tmpPath, 'r'),n]);

Step 2: Introduce a load balancer

Provision a small VM or use a managed load balancer in front of your current VPS. At this stage you are running one backend, but the load balancer becomes the fixed point that DNS points to going forward, so future scaling does not require another DNS change.

Example Nginx upstream config as a starting load balancer:

upstream app_servers {n    server 10.0.0.10:80 max_fails=3 fail_timeout=30s;n    server 10.0.0.11:80 max_fails=3 fail_timeout=30s backup;n}nnserver {n    listen 443 ssl;n    server_name shop.example.com;n    location / {n        proxy_pass http://app_servers;n        proxy_set_header Host $host;n        proxy_set_header X-Real-IP $remote_addr;n    }n}

Point DNS at the load balancer's IP now, even before the second app server exists. This decouples DNS from server count for every future change.

Step 3: Clone the application server

With sessions and media externalized, the app server itself should now be close to stateless. Build a second node from the same base image or provisioning script (Ansible, a Docker image, or a simple shell script that installs the same package versions).

Keep configuration in version control from this point forward, this is also the moment to start treating servers as replaceable rather than hand-tuned.

# minimal provisioning checknphp -vnnginx -vncomposer --versionncat /etc/php/8.2/fpm/pool.d/www.conf | grep pm.max_children

Add the second server to the load balancer's upstream block, remove the backup flag, and confirm both nodes are serving traffic under normal load before moving to the database.

Step 4: Replicate the database

This is the step with the most risk, and where zero-downtime database migration techniques matter most. The goal is a primary-replica setup, or a managed database cluster, instead of a single MySQL/PostgreSQL instance on the original VPS.

For MySQL, set up binary log replication:

# On primary my.cnfnserver-id = 1nlog_bin = /var/log/mysql/mysql-bin.lognbinlog_do_db = shop_productionnn# On replica my.cnfnserver-id = 2nrelay-log = /var/log/mysql/mysql-relay-bin.log
CHANGE MASTER TOn  MASTER_HOST='10.0.0.5',n  MASTER_USER='replicator',n  MASTER_PASSWORD='***',n  MASTER_LOG_FILE='mysql-bin.000003',n  MASTER_LOG_POS=154;nSTART SLAVE;

Verify replication lag before cutover:

SHOW SLAVE STATUS\Gn-- check: Seconds_Behind_Master = 0

Once the replica is caught up and stable, promote it or switch the application's write connection using a virtual IP or a proxy like ProxySQL, so failover does not require editing config files under pressure.

Step 5: Add health checks and failover

A load balancer with two backends is not yet highly available if it does not detect failure. Add active health checks:

upstream app_servers {n    server 10.0.0.10:80 max_fails=2 fail_timeout=10s;n    server 10.0.0.11:80 max_fails=2 fail_timeout=10s;n}nn# with nginx plus or haproxy, use active checksnoption httpchk GET /healthnhttp-check expect status 200

Build a real /health endpoint that checks database connectivity and Redis, not just that Nginx is running:

<?phpn// health.phpntry {n    $pdo = new PDO($dsn, $user, $pass);n    $redis = new Redis();n    $redis->connect('10.0.0.5', 6379);n    http_response_code(200);n    echo 'ok';n} catch (Exception $e) {n    http_response_code(503);n    echo 'unhealthy';n}

Verification: how to confirm it works

Do not assume HA works because the config is deployed. Test it deliberately:

  • Kill one app node during low traffic and confirm the load balancer routes around it within your configured fail_timeout window (check response times stay flat, not just that requests succeed)
  • Check replication lag under real load: SHOW SLAVE STATUS should show Seconds_Behind_Master near zero even during peak checkout traffic
  • Run a load test against both nodes and confirm even distribution: ab -n 5000 -c 50 https://shop.example.com/ and compare access logs on each server
  • Simulate a database failover in a staging environment first, and measure how long the application takes to reconnect, this number should be seconds, not minutes
  • Monitor TTFB separately from full page load post-migration, since load balancer hops can add latency if health checks are misconfigured, see our guide on choosing the right performance metric for ecommerce infrastructure

A useful baseline: after this migration, a well-configured setup should sustain the loss of one app node with zero customer-visible errors, and a database failover should complete in under 30 seconds with connection retry logic in place.

Common pitfalls to avoid

  • Sticky sessions as a shortcut: using session affinity instead of externalizing sessions works until one node goes down and half your logged-in users are logged out
  • Forgetting cron jobs: scheduled tasks (order processing, cache warming) running on both nodes will duplicate work; move them to a single designated node or a job queue
  • Skipping the health check depth: a health check that only pings port 80 will report "healthy" even when the database connection is dead
  • Assuming replication equals backup: replication protects against hardware failure, not against a bad DELETE statement replicating instantly to the replica
  • Not testing failover before you need it: the first time your database failover logic runs should not be during an actual outage

Next steps and related reading

Once the core HA pattern is in place, the next layers worth investing in are DNS resilience and caching strategy. Our guide on how DNS resolution works under the hood covers how TTLs and resolver behavior affect failover speed, which matters once you have multiple regions or providers in play.

If your store is WooCommerce specifically, pair this HA setup with the practices in how to scale WooCommerce infrastructure without downtime, since plugin behavior and object caching add constraints that generic HA guides do not cover.

Longer term, treat this as the foundation for infrastructure management services thinking: version-controlled provisioning, tested failover, and monitoring that reflects real user experience rather than server uptime alone.

Ready when you are

Moving from a single VPS to high availability infrastructure does not require a rewrite, but it does require getting the sequence right: externalize state, add the load balancer, replicate the database, and test failover before you need it. Done in that order, the application code never has to change.

Need this running in production without building it yourself? See our managed infrastructure services or schedule a call.