← All case studies

Cloud Migration & Multi-Site Platform for a Global Manufacturer

Project Director · migration governance & maintenance SLA · 2025 – 2026

  • WordPress Multisite
  • WooCommerce
  • Kinsta
  • AWS
  • Custom FTP → ERP sync

Ninety markets on three tired servers

The client makes cash-management hardware and point-of-sale peripherals, the kind of kit retail and hospitality operators run behind the counter in more than ninety countries. Their web presence did not match that reach. It ran on self-managed cloud servers, three EC2 instances with attached volumes, carrying several regional storefronts between them. Every patch, every scaling decision, and every plugin update was the team’s own problem, and a couple of unstable third-party commerce plugins had already caused incidents in production. The setup was expensive to keep running and fragile to touch.

Moving the network without a store going dark

The job was to get all of that onto managed multi-site WordPress on Kinsta, with real staging and production environments in place of undifferentiated server babysitting. The constraint that shaped the cutover was simple to state and unforgiving in practice: no regional store could go dark, and nothing could be lost.

So the sequence was conservative. Assess and back up the existing servers and volumes first, then transfer databases, files, and configuration into the managed network. Validate the multisite settings and every domain mapping against the old environment before sending real traffic. Redirect mapping was drawn up ahead of the switch so that each regional address resolved to its store the moment DNS moved, with no gap where a country hit a dead host. The old EC2 infrastructure stayed up until a post-migration validation report was signed off, and only then was it decommissioned. Data loss across the network came out at zero.

One catalogue, but land on the right store

One catalogue serves ninety-plus countries, and a shopper in Germany should never open the US store by accident. On the multisite each regional storefront is a subsite, which turns the routing question into a lookup: given where a request came from, which subsite should answer.

Kinsta fronts the network with Cloudflare, and Cloudflare stamps every request with the visitor’s country before it reaches WordPress. A small resolver reads that header, maps the ISO country code to a regional store, and redirects on the first hit. Countries with no store of their own fall back to the global catalogue rather than a wrong guess, and a shopper who has already picked a store keeps it on later visits.

Fig. 1 · Geo-based store routing across the multisite
01 · Request reaches the edge Cloudflare in front of Kinsta 02 · Detect the visitor country read from the CF-IPCountry header 03 · Map country to a regional store ISO code resolves to a subsite slug 04 · Serve that regional storefront the US or EU subsite 05 · Fall back to the global catalogue unmapped or unknown country

The two edge cases are what keep it honest. Cloudflare returns XX when it cannot place a client (a reserved IP range, an anonymising proxy), and an unmapped country is just as much a miss, so both resolve to global instead of routing somewhere plausible but wrong.

store-router.php PHP
// Kinsta fronts the network with Cloudflare, so each request already
// carries the visitor's country as an edge header. Map it to a regional
// store, and fall back to the global catalogue when there is no match.
const STORE_BY_COUNTRY = [
'US' => 'us', 'CA' => 'us',
'GB' => 'eu', 'DE' => 'eu', 'FR' => 'eu', 'NL' => 'eu',
];

function resolve_store_slug(): string {
$country = strtoupper($_SERVER['HTTP_CF_IPCOUNTRY'] ?? '');
// 'XX' is Cloudflare's code for an unknown or reserved-range client;
// treat it, and anything unmapped, as global rather than guessing.
if ($country === '' || $country === 'XX') {
  return 'global';
}
return STORE_BY_COUNTRY[$country] ?? 'global';
}

function maybe_route_to_store(): void {
// A shopper who already chose a store keeps it; only first hits route.
if (isset($_COOKIE['store_pref'])) return;
$target = resolve_store_slug();
if ($target !== 'global' && trim(get_blog_details()->path, '/') !== $target) {
  wp_safe_redirect(network_site_url($target . '/'), 302);
  exit;
}
}

The European store could not oversell

The European store’s inventory was exactly where the old fragile plugins had bitten before, so it did not get a plugin. It got a custom FTP-to-ERP sync instead. Stock and order files land on an FTP drop from the ERP and are polled on a short interval, roughly every five minutes, which keeps the storefront close to the warehouse without hammering either side. An overselling guard sits on the write path: any order that would exceed available stock is refused and the sales team is notified, so a race between two checkouts never sells the same unit twice. The catalogue shows plain in-stock and out-of-stock flags rather than exact counts, because a number that drifts between syncs is worse than no number at all. Around that same store we rebuilt shipment tracking with automated notifications, added a product configurator, wired up translations, and tuned the whole thing for mobile.

Directing it, then keeping it

I ran planning, design, web, engineering, and QA against a milestone plan: the maintenance workflow first, then the server-to-managed-host migration, the European store automation, the site enhancements, and a customer download portal last. Risk was front-loaded on purpose. Full backups and a staging validation pass came before any production change, redirect mapping went in before DNS moved, and the client signed off a validation report before the old servers were retired. When the migration closed it did not end the relationship, it converted into a recurring monthly maintenance plan that still covers the platform past go-live.

Outcomes

Engineering Outcomes

0
data loss moving the US and EU stores off self-run servers
~5 min
stock and order sync from FTP into the ERP
Staging+Prod
managed environments in place of three EC2 boxes

Directorial Outcomes

100%
regional domains live, signed off by the client
3 → 0
self-managed server instances retired after cutover
Monthly
maintenance retainer running past go-live