How to Migrate Large WooCommerce Sites
Migrating a large WooCommerce site isn't like moving a simple blog. When you're dealing with 10,000+ products, 100,000+ images, and complex product variations, the standard migration plugins often fail or create a mess that takes weeks to untangle.
After migrating enterprise WooCommerce stores, I've developed a battle-tested process that maintains data integrity, preserves SEO rankings, and keeps your business running with minimal downtime.
Why Standard Migration Methods Fail at Scale
Most WooCommerce site owners try one of these approaches:
All-in-One Migration Plugins - Great for small sites, but choke on huge databases and timeout on massive image libraries.
Manual Database Import - Works until you realize product images are pointing to the old server, thumbnails are broken, and variations are disconnected from parent products.
WooCommerce Product Import/Export - Handles products but completely ignores the complex web of categories, tags, attributes, and image relationships.
The problem? These tools weren't designed for stores operating at scale.
The Professional Migration Process
Here's the proven methodology I use for enterprise WooCommerce migrations:
Phase 1: Database Export and Initial Setup
Start by exporting your entire database - not just WooCommerce tables. You need everything: users, orders, settings, and crucially, the metadata that connects products to images.
# Export complete database
wp db export backup.sql --add-drop-table
Install this on your new hosting environment (I recommend Kinsta for large WooCommerce stores) with the same WordPress and WooCommerce versions as your original site. Version mismatches cause subtle data corruption that won't show up until customers start complaining about checkout issues.
Phase 2: Create Migration Control Tables
This is where my approach differs from standard migrations. Create custom database tables to track what's been migrated:
Products Table:
- Product ID
- SKU
- Product type (simple/variable)
- JSON of complete product details
- Import timestamp
Categories Table:
- Category ID
- Slug
- JSON of category data
- Import timestamp
Why? Because large migrations take days or weeks. These control tables let you:
- Resume from failures without duplicating data
- Track exactly what's been processed
- Identify and fix issues without re-importing everything
Phase 3: Clean Up Existing Data
Before importing anything new, scan your staging environment and remove products and categories that aren't in your control tables. This prevents duplicate SKUs, orphaned images, and the nightmare of trying to figure out which product is the "real" one.
-- Delete products not in migration table
-- Delete galleries and images
-- Clean orphaned categories
Phase 4: Image Migration (The Hard Part)
This is where most migrations fall apart. With 100,000+ images across years of folder structures, you need a surgical approach.
The Image Migration Script
I've developed a WP-CLI script that:
- Preserves folder structure - Your images stay at /2023/12/01/door.jpeg, maintaining all existing URLs
- Skips thumbnails - Ignores WordPress-generated thumbnails (we'll regenerate these)
- Prevents duplicates - Checks the database before registering each image
- Handles all formats - JPG, PNG, WebP, GIF
#!/bin/bash
echo "=== Step 1: Registering files in database ==="
SITE_URL=$(wp option get siteurl 2>/dev/null)
UPLOAD_DIR="/www/apispeed_866/public/wp-content/uploads"
# Function to get mime type from extension
get_mime_type() {
local file="$1"
case "${file##*.}" in
jpg|jpeg|JPG|JPEG) echo "image/jpeg" ;;
png|PNG) echo "image/png" ;;
webp|WEBP) echo "image/webp" ;;
gif|GIF) echo "image/gif" ;;
*) echo "application/octet-stream" ;;
esac
}
count=0
while IFS= read -r file; do
filename=$(basename "$file")
# Skip thumbnails and scaled images
if [[ "$filename" =~ -[0-9]+x[0-9]+\. ]] || [[ "$filename" =~ -scaled\. ]]; then
continue
fi
relative_path=${file#$UPLOAD_DIR/}
file_url="${SITE_URL}/wp-content/uploads/${relative_path}"
# Check if already registered
exists=$(wp db query "SELECT COUNT(*) FROM wp_posts WHERE guid='${file_url}'" --skip-column-names 2>&1 | grep -o '[0-9]*' | head -1)
if [ -z "$exists" ]; then
exists=0
fi
if [ "$exists" -eq "0" ]; then
mime_type=$(get_mime_type "$file")
post_id=$(wp post create \
--post_type=attachment \
--post_mime_type="$mime_type" \
--post_status=inherit \
--post_title="$filename" \
--porcelain 2>/dev/null)
if [ -n "$post_id" ]; then
wp db query "UPDATE wp_posts SET guid='${file_url}' WHERE ID=$post_id" >/dev/null 2>&1
wp post meta add "$post_id" "_wp_attached_file" "$relative_path" >/dev/null 2>&1
((count++))
if [ $((count % 100)) -eq 0 ]; then
echo "Registered $count files..."
fi
fi
fi
done < <(find "$UPLOAD_DIR" -type f \( -iname "*.jpg" -o -iname "*.jpeg" -o -iname "*.png" -o -iname "*.webp" \))
echo "=== Registered $count new files ==="
# Delete old thumbnails
echo "=== Deleting old thumbnails ==="
find "$UPLOAD_DIR" -type f -regex '.*-[0-9]+x[0-9]+\.\(jpg\|jpeg\|png\|webp\)' -delete
# Regenerate fresh thumbnails
echo "=== Regenerating thumbnails ==="
wp media regenerate --yes
echo "=== Complete! ==="
Why this approach works:
- Runs in the background - Use screen to keep it running even if you disconnect
- Resumable - If it crashes after 50,000 images, restart and it skips what's done
- No file movement - Images stay exactly where they are, preserving all URLs
- Fresh thumbnails - Deletes old thumbnails and generates new ones matching your theme's requirements
Timeline for 100,000 images:
- Registration: 14-28 hours
- Thumbnail deletion: 5-10 minutes
- Thumbnail regeneration: 2-6 days
Run it with:
screen -S migration
./image-migration.sh
# Press Ctrl+A then D to detach
# Check progress: screen -r migration
Phase 5: Category Import
Import categories in hierarchical order (parents before children) and associate images:
- Create category structure
- Associate category images from media library
- Update slugs and SEO metadata
- Verify hierarchy is intact
Phase 6: Product Import
This is where your control tables prove their worth. Import in this exact order:
Simple Products:
- Create product with SKU
- Associate main image
- Associate gallery images
- Set pricing (including private/wholesale pricing)
- Configure "login to view price" if needed
- Mark as imported in control table
Variable Products:
- Create parent product
- Create all variations
- Associate parent product images
- Associate variation-specific images
- Set variation pricing
- Link variations to parent
- Mark as imported in control table
Phase 7: Private Pricing Configuration
For B2B stores, private pricing is critical:
// Set private pricing
update_post_meta($product_id, '_price', $private_price);
update_post_meta($product_id, '_regular_price', $private_price);
// Enable login requirement
update_post_meta($product_id, '_login_to_view_price', 'yes');
Phase 8: Validation and Testing
Before going live:
- Verify all product images display correctly
- Test variable product dropdown menus
- Confirm pricing displays properly
- Check category pages load
- Test checkout with various product types
- Verify inventory quantities transferred
- Confirm SEO metadata is intact
Common Pitfalls to Avoid
Memory Exhaustion - PHP memory limits kill migrations. Set to 512M minimum, 1GB for large stores.
Timeout Issues - Don't run migrations through web interfaces. SSH + WP-CLI is the only reliable method at scale.
Broken Image Links - One wrong path and 100,000 images break. Always use relative paths in the database.
Missing Variations - Variable products have parent-child relationships. Import parents first, always.
Inventory Sync Issues - Stock quantities are stored in multiple places. Update all of them.
Why Hire a Professional?
A failed migration means:
- Lost sales during extended downtime
- Broken product pages hurting SEO
- Frustrated customers unable to checkout
- Hours of manual fixes for database corruption
For stores doing significant revenue, the cost of a professional migration is recovered in days through avoided downtime.
Need Expert Help?
I specialize in enterprise WooCommerce migrations. If you're facing:
- 10,000+ products
- 100,000+ images
- Complex product variations
- Custom pricing rules
- B2B private pricing
- Tight migration deadlines
Let's talk. I've successfully migrated stores generating 8-figure annual revenue with zero downtime and complete data integrity.