Type at least 2 characters to search.
Completed

Migrating a Live WooCommerce Store from Linux to Windows Server Without Losing an Order

NIMU Technologies moved a live, ad-driven WooCommerce store from a Linux VPS to an on-premises Windows Server in four days, without losing an order, without adding a monthly cost, and without opening a single inbound port to the internet.

ClientA Delhi-Based Computer Hardware Retailer
IndustryE-Commerce
ServicesServer Migration, WooCommerce Development, Performance Optimisation, Cloud Security
StatusCompleted
PublishedOctober 2026

The Challenge

A computer hardware and gaming PC retailer wanted its WooCommerce store, with about 2,000 products and 9,000+ historical orders, on its own office server. The old Linux VPS was being limited by its provider, so there was no going back once cutover began. The new server also runs the business's Tally accounting for five or six remote staff. The brief had four hard constraints: sales could not stop during daily Google Ads campaigns, no paid order could be lost, no new hosting, VPN or licence costs were allowed, and Tally had to keep working.

Our Approach

Every fix came from evidence: packet captures, IIS logs, MySQL error logs and the Windows event log, not guesswork. We avoided problems we could not control, such as the ISP and the router, by publishing the store through an outbound Cloudflare Tunnel, then fixed each Linux-to-Windows difference where it actually occurred.

Go Live via Tunnel

Cloudflare Tunnel replaced inbound ports the ISP was blocking

Stabilise on IIS

PHP logging, cache drop-ins and a silent plugin crash fixed

Reconcile Orders

The one missing paid order moved by SQL under its original number

Tune Speed & Feed

OPcache, WebP, bot rules, CA bundle and a scheduler for WP-Cron

Close the Server

Zero Trust staff access and every router port forward disabled

Migration Journey

  • Cutover through Cloudflare Tunnel
  • WordPress stabilised on IIS
  • Missing order reconciled
  • Recovered from power cut
  • Speed and Google feed restored
  • Server closed to the internet
  • Ongoing hardening

Key Technical Areas

Cloudflare Tunnel

Store published outbound, so no inbound web port is needed

PHP on IIS

Writable error log so warnings never break pages or the REST API

Order Reconciliation

Missing order moved by SQL in one transaction, with no duplicate emails

Performance

OPcache enabled; uncached home page down to about 1.8 seconds

Bot Mitigation

Cloudflare rule cut cart-link bot traffic from about 70 to 3 requests a minute

Google Shopping Feed

CA bundle and a Windows scheduled task brought 1,098 products back

Zero Trust Access

Staff reach RDP and Tally through Cloudflare with an email PIN

Self-Healing Services

MySQL and the tunnel restart on failure after crashes and power cuts

Results & Current Status

The store now runs on the client's own hardware, faster than it did during cutover. The uncached home page responds in about 1.8 seconds, down from 20+ seconds under bot load. 1,098 products are active in Google Shopping with none disapproved, and no orders were lost. No ports are open to the internet, and the work added no monthly cost.

20s+ to ~1.8s Response
0 Orders Lost
0 Inbound Ports Open
1,098 Products in Shopping

Technology & Approach Summary

LayerApproach
BeforeLinux VPS: Nginx-style stack, MySQL, Redis, cron
ServerWindows Server 2022, 8-core Ryzen, 48 GB RAM
Web & RuntimeIIS 10, PHP 8.4 with OPcache
DatabaseMySQL 8.4 with restart-on-failure
PlatformWordPress + WooCommerce
Public AccessCloudflare Tunnel (cloudflared as a Windows service)
Staff AccessCloudflare Zero Trust, One Client, split tunnelling
SchedulingWindows Task Scheduler running WP-Cron every minute
CostRs 0 a month: free Cloudflare plans and built-in Windows tools

Key Takeaways

  1. Test from a genuinely outside network; hairpin NAT tests from inside the office hide problems.
  2. When the ISP or router is outside your control, use an outbound tunnel instead of port forwarding.
  3. On IIS, give PHP a writable error_log, or a single warning can turn a page or API call into an error.
  4. Replace Linux cron with a Windows scheduled task, or feeds and background jobs silently stop.
  5. Measure the order gap from the exact snapshot time and move missing orders by SQL in one transaction.

Planning a similar project?

Let's discuss how we can help you plan, build, or integrate your next e-commerce project.

Get in Touch
ClientA Delhi-Based Computer Hardware Retailer
MoveLinux VPS to Windows Server 2022 + IIS 10
Timeline4 days, including cutover and hardening
Outcome0 orders lost, 0 inbound ports, Rs 0 added cost

Project Overview

A Delhi-Based Computer Hardware Retailer sells computer hardware and gaming PCs through a WooCommerce store with about 2,000 products and more than 9,000 historical orders. Most of its traffic comes from daily Google Ads and Google Shopping campaigns, so every hour the checkout is broken costs real money.

The client wanted to move the store from a Linux VPS to its own office server: an 8-core Ryzen machine with 48 GB of RAM running Windows Server 2022. The same machine also runs the business's Tally accounting, which five or six staff use remotely every day. NIMU Technologies planned and carried out the server migration over four days, then stabilised, sped up and secured the new setup.

ItemDetails
Store sizeAbout 2,000 products, 9,000+ historical orders, traffic mainly from Google Ads
FromLinux VPS (Nginx-style stack, MySQL, Redis)
ToWindows Server 2022, IIS 10, PHP 8.4, MySQL 8.4
ConstraintSame server runs Tally; no paid licences or extra hosting
TimelineFour days, from cutover through stabilisation and security hardening

The Challenge

The old VPS was being limited by its provider, so once cutover began there was no going back. The brief came with four hard constraints:

  • Sales could not stop. The store runs paid Google campaigns every day, so a broken checkout burns ad spend every hour.
  • No lost orders. Customers kept buying on the old server until the switch, and every paid order had to reach the new one.
  • No new costs. Free services only: no extra hosting, VPN subscriptions or paid licences.
  • Tally must keep working. Accounting staff connect to the same machine every day.

There was also a security problem to solve. Before the project, the office router forwarded every port to the server, including remote desktop and file sharing. The store's back office was effectively on the open internet.

Phase 1: Going Live Without Inbound Ports

The first cutover failed with Cloudflare error 525, even though the server answered HTTPS perfectly from inside the office. The evidence pointed outside the server:

  1. IIS, PHP, MySQL and the Cloudflare Origin Certificate all worked locally, returning HTTP 200.
  2. Tests from the server to its own public IP passed, but these were hairpin NAT tests that never left the building.
  3. A real outside test from a mobile network failed the TLS handshake, and a packet capture showed the traffic never reached the server's network card.
  4. A raw TCP listener on a second port, unrelated to IIS, also received nothing, while remote desktop traffic from the same tester did arrive.

Something between the internet and the server, either the ISP or the fibre router, was intercepting web traffic, and neither was under our control. So instead of opening ports, we stopped needing them. We installed Cloudflare Tunnel (cloudflared) as a Windows service. The server makes an outbound connection to Cloudflare, and visitors reach the store through it.

Shopper Cloudflare Cloudflare Tunnel (outbound) IIS on Windows Server WooCommerce
  • Published routes for the root domain and www, pointing to the local IIS site over HTTPS with the correct origin hostname.
  • A Cloudflare redirect rule sends www to the root domain with a 301.
  • The service restarts on failure, so it recovers from crashes and reboots without anyone logging in.

The store was live within an hour of choosing this route.

Phase 2: Stabilising WordPress on IIS

Most of the hardest bugs came from one Windows-specific behaviour: when PHP writes to stderr, IIS replaces the whole page with that text and returns an error.

SymptomRoot causeFix
Admin pages replaced by PHP noticesDebug mode left on, and the log file was not writable, so notices went to stderrTurned debug off; gave PHP a dedicated, writable error log outside the web root
"W3 Total Cache Error" on every admin pageCache drop-in file copied from Linux, plugin folder missingSet the orphaned drop-in aside (renamed, not deleted)
Blank admin after thatRedis object-cache drop-in, but no Redis on WindowsSet the drop-in aside; WordPress fell back to its built-in cache
Dashboard and orders list blank, no error loggedA marketing plugin's admin-menu badge counter ended the request silentlyTraced it with a temporary must-use plugin, then disabled only that counter
Checkout payment step failing, REST API returning 500A payment plugin logged a debug line on every REST call; with no log file, IIS turned it into a 500Set error_log in php.ini, which fixed REST, payments and pixel tracking at once

How we found a silent crash. With no error in any log, we added a small temporary must-use plugin that ran only when a secret query string was present. At shutdown, it recorded which WordPress hook was running. It pointed straight at one plugin's admin-menu counter, so we disabled that single filter instead of the whole plugin, keeping the client's Google Ads integration live.

Phase 3: No Order Left Behind

The database snapshot was taken a day before cutover, so any order placed in between existed only on the old server. We found exactly one paid order and moved it across under its original order number, without sending the customer a duplicate email.

  1. Measured the gap first. On the old server, we listed every order and customer account created after the snapshot time. Result: one paid order, no new accounts.
  2. Checked for number collisions. The new database's next ID was lower than the missing order's number, so a future order could have reused it. We confirmed the order number and its line-item IDs were still free.
  3. Exported the order as SQL, not by retyping it. The order row, its 61 details, 4 line items, 33 line-item details and 4 order notes were exported with quoted values, so addresses and payment references stayed exact.
  4. Imported it in one transaction. If any statement had failed, nothing would have been saved.
  5. Verified in the admin. Customer, items, shipping and total matched the original to the rupee.
  6. Cleaned up. The SQL file and notes, which held customer data, were deleted from both servers.

Because the import went straight into the database, WooCommerce sent no emails, and the customer saw nothing unusual.

Phase 4: Recovering from a Power Cut

On the first morning, the home page loaded but "Add to cart" failed with a database error. MySQL had stopped and refused to start. The cause was two separate events:

  • Two unexpected shutdowns overnight. Windows logs showed the server lost power twice in five hours, with no crash code and no power-button press.
  • An earlier permissions change. Folder permissions on the data drive had been reset, removing the MySQL service account's write access to its own data files. MySQL kept running until the first power cut, then could not restart.

The MySQL error log named the exact file it could not write. We restored write access for the MySQL service account on the database folder only, started the service, and let InnoDB's crash recovery finish. No data was lost, and the reconciled order from Phase 3 was intact. Both MySQL and the tunnel service now restart automatically on failure.

The same permission reset explained why plugin updates kept leaving the site in maintenance mode: WordPress could not write its own files. We gave the web server write access to wp-content only, kept wp-config.php and WordPress core read-only, and turned off automatic plugin updates so changes happen deliberately.

Phase 5: Speed, Images and the Google Shopping Feed

Once the store was stable, a slowdown showed that a Linux-to-Windows move breaks things that never appear as errors. Each fix below came from a log, not a guess.

ProblemWhat the logs showedFix
Product and banner images missing803 image requests failing with IIS 404.3: IIS had no MIME type for .webpAdded the WebP MIME type at server level, after backing up the IIS config
Site design lost after a restartCached pages referenced optimised CSS files that did not exist, because the plugin could not write themDisabled the CSS optimiser and cleared the stale page cache
Whole site slow, 10 to 23 seconds per pageA bot opened 2,148 add-to-cart and remove links in 30 minutes, with no referrer, filling all 30 PHP workersA Cloudflare custom rule challenges cart links without a store referrer; bot traffic fell from about 70 to 3 requests a minute
Pages still 3.5 to 4 secondsPHP OPcache was off, as it is by default on WindowsEnabled OPcache with a file cache fallback; home page to about 1.8 seconds
Google for WooCommerce could not reach GooglePHP had no certificate bundle, so every outbound HTTPS call failed with cURL error 60Installed Mozilla's CA bundle (checksum verified) and pointed PHP at it
Product feed stuck since cutoverThe Linux server ran WordPress's scheduler from cron; nothing replaced it on WindowsA Windows scheduled task runs the scheduler every minute; 1,098 products synced the same afternoon

The product feed matters most to this client: their Google Shopping campaigns depend on Google seeing current prices and stock.

Phase 6: Closing the Server to the Internet

The Windows security log recorded more than 25,000 failed remote desktop logins in a single day, from addresses around the world. The audit found that every successful login came from the client's own staff, network-level authentication was on, and account lockout had stopped the guessing. We moved fast anyway, because one leaked password would expose the store, the database and the accounts system.

Using only free tools, we built:

  1. Cloudflare Zero Trust (free plan, up to 50 users). The existing tunnel now also carries a private route to the server's office IP.
  2. Email one-time PIN sign-in in the Cloudflare One Client. Only named staff email addresses can enrol a device.
  3. Split tunnelling, which sends only the server's address through Cloudflare, so staff internet speed is unaffected.
  4. Two-way testing from outside the office: with the client disconnected, the server was unreachable; connected, remote desktop and Tally both worked.
  5. A closed router. DMZ off, and all seven port forwards disabled rather than deleted, so they can be restored if ever needed.
  6. External verification. Remote desktop, Tally, file sharing, HTTP and HTTPS ports all report closed. The website still works, because it never needed an open port.
Staff Device Cloudflare One Client Cloudflare Zero Trust Tunnel RDP & Tally on Server

Staff received a one-page WhatsApp guide to install the client and switch their remote desktop and Tally address to the server's private IP. Read more about our approach to cloud security.

Results

MeasureBeforeAfter
Home page server response, uncached20+ s (under bot load)About 1.8 s
Product page server response, uncached20+ sAbout 1.85 s
Inbound ports open to the internetAll (router DMZ)None
Failed remote desktop logins25,000+ per dayNo public remote desktop port to attack
Google Shopping products activeFeed stalled since cutover1,098 active, 0 disapproved
Orders lost in migrationRisk of 1 paid order0 (reconciled under its original number)
Added monthly costn/aRs 0 (free Cloudflare plans, built-in Windows tools)

Linux to Windows WooCommerce Migration Checklist

A WordPress site that works on Linux will not simply work on Windows and IIS: the defaults differ in ways that fail silently. These are the checks we now run on every such move.

Before cutover

  • Test from a genuinely outside network, never from the server or office LAN (hairpin NAT hides problems)
  • Prefer an outbound tunnel over inbound port forwarding when the ISP or router is not under your control
  • Set error_log in php.ini to a writable file, so warnings never reach stderr and break pages
  • Enable OPcache and point PHP at a CA certificate bundle
  • Add MIME types for .webp and other modern formats in IIS
  • Replace the Linux cron job with a Windows scheduled task if DISABLE_WP_CRON is set
  • Remove cache drop-ins (advanced-cache.php, object-cache.php) whose backends do not exist on the new server
  • Grant the web server write access to wp-content only, and the database service to its data folder only

At cutover

  • Note the exact snapshot time, then list orders and accounts created after it on the old server
  • Move missing orders by exported SQL in one transaction, checking ID collisions first
  • Disable automatic plugin updates until permissions and backups are confirmed

After cutover

  • Read the IIS log for 404.x and 500 patterns, and for traffic floods on cart and search URLs
  • Check that background jobs complete and that ad and shopping feeds sync
  • Close every inbound port and move staff access to a zero-trust client
  • Put the server on a UPS and set every critical service to restart on failure

What's Next

The store is live, fast and closed to the internet. The remaining work is routine hardening:

  • Staged plugin updates, one at a time, each with a backup and a test checkout
  • Re-enabling CSS optimisation now that the cache folder is writable
  • Further isolating the web server and database service accounts
  • A UPS for the server, to end unplanned power-offs

NIMU Technologies' Role

NIMU Technologies planned and carried out the migration end to end:

  • Diagnosed the ISP-level block and published the store through Cloudflare Tunnel with no inbound ports
  • Stabilised WordPress and WooCommerce on IIS, fixing PHP logging, cache drop-ins and a silent plugin crash
  • Found and reconciled the one missing paid order under its original number
  • Recovered MySQL after a power cut with no data loss and made critical services self-healing
  • Brought page response from 20+ seconds to about 1.8 seconds and restored the Google Shopping feed
  • Replaced open remote desktop and Tally ports with Cloudflare Zero Trust access for staff

Planning a Server Move for Your Store?

Whether you are moving WooCommerce to new hosting, your own hardware or a different operating system, NIMU Technologies can plan the cutover, keep orders flowing and secure the new server. Explore our server migration, WooCommerce development and performance optimisation services.

Discuss Your Migration Explore Server Migration