WordPress Multisite Is Not Multi-Site: Architecture That Scales to 500 Locations
WordPress Multisite sounds like you get multiple sites. What you actually get is one WordPress installation that pretends to be multiple sites by adding a number in front of database table names. Your main site uses wp_posts. Subsite 2 uses wp_2_posts. Subsite 3 uses wp_3_posts. That is not multi-site architecture. That is one database with numbered copies of the same tables. And it has consequences.
I've migrated networks with 15, 40, and once 87 subsites off WordPress Multisite. Every single time, the client thought they were getting independent sites. Every single time, they discovered the hard way that they weren't. If you're running a franchise, a multi-location business, or a university department network on WordPress Multisite, this article is going to hurt a little. But it's better to know now than after subsite #47 takes down all 46 others.
Table of Contents
- What WordPress Multisite Actually Does Under the Hood
- The 7 Consequences of Fake Multi-Site Architecture
- When WordPress Multisite Works (Honestly)
- The Architecture That Actually Scales to 500 Locations
- Architecture Comparison: Prefixed Tables vs Row-Level Security
- Implementation: Next.js + Supabase for Multi-Location Sites
- Migration Path: Getting Off WordPress Multisite
- Real Numbers: Performance and Cost Comparison
- FAQ

What WordPress Multisite Actually Does Under the Hood
Let's look at what happens when you enable Multisite in WordPress. You add a couple of lines to wp-config.php:
define('WP_ALLOW_MULTISITE', true);
define('MULTISITE', true);
define('SUBDOMAIN_INSTALL', false);
define('DOMAIN_CURRENT_SITE', 'example.com');
define('PATH_CURRENT_SITE', '/');
define('SITE_ID_CURRENT_SITE', 1);
define('BLOG_ID_CURRENT_SITE', 1);
WordPress then creates a few network-level tables (wp_blogs, wp_site, wp_sitemeta, wp_registration_log, wp_signups) and starts duplicating your core table structure for every new subsite.
Here's what the database looks like after just 5 subsites:
wp_posts (main site)
wp_postmeta (main site)
wp_options (main site)
wp_users (shared - all sites)
wp_usermeta (shared - all sites)
wp_2_posts (subsite 2)
wp_2_postmeta (subsite 2)
wp_2_options (subsite 2)
wp_3_posts (subsite 3)
wp_3_postmeta (subsite 3)
wp_3_options (subsite 3)
...
wp_5_posts (subsite 5)
wp_5_postmeta (subsite 5)
wp_5_options (subsite 5)
Five subsites and you already have ~55 tables. Fifty subsites? You're looking at over 500 tables in a single MySQL database. Five hundred locations? North of 5,000 tables. In one database. Sharing one connection pool.
Every table has its own indexes. Every query targets a specific prefixed table. The query planner has to deal with all of this. And every single one of those tables is accessible to any PHP process running on that server, because they're all in the same database with the same credentials.
This is not multi-site architecture. This is a naming convention masquerading as isolation.
The 7 Consequences of Fake Multi-Site Architecture
1. Shared Vulnerability Surface
Every subsite in a WordPress Multisite network runs the same WordPress core, the same plugins, and the same themes. One plugin exploit compromises all subsites because they share the same codebase.
This isn't theoretical. In February 2026, WPVivid — a backup plugin with over 900,000 active installations — had a severity 9.8 RCE (Remote Code Execution) vulnerability disclosed. Severity 9.8 out of 10. That's "an unauthenticated attacker can execute arbitrary code on your server" territory.
On a standalone WordPress site, that's one compromised site. On a Multisite network with 30 subsites? That's 30 compromised sites. Same server. Same database. Same file system. One exploit, total network compromise.
You can't install a different version of a plugin on subsite #12 than subsite #13. You can't sandbox one location's plugins away from another. Every site gets the same attack surface.
2. Plugin Conflict Multiplication
On a single WordPress site, a plugin conflict breaks one site. You deactivate the plugin, diagnose, move on. Annoying but manageable.
On Multisite, a network-activated plugin conflict breaks every site simultaneously. I've seen a WooCommerce update on a Multisite network take down 23 location pages at once because a network-activated caching plugin hadn't been updated for the new WooCommerce hooks. Twenty-three locations with broken pages. One root cause, twenty-three angry location managers calling the same person.
And here's the kicker — individual site admins usually can't deactivate network-activated plugins. They have to wait for the Super Admin to fix it.
3. Performance Degradation
Fifty subsites sharing one MySQL instance. Every page load on subsite #47 runs queries like:
SELECT * FROM wp_47_posts WHERE post_type = 'page' AND post_status = 'publish';
SELECT option_value FROM wp_47_options WHERE option_name = 'active_plugins';
SELECT * FROM wp_47_postmeta WHERE post_id IN (142, 143, 144, 145);
Meanwhile, subsite #12 is running its own set of queries against wp_12_posts, wp_12_options, and wp_12_postmeta. And so is every other subsite, all hitting the same MySQL instance.
MySQL's query planner gets confused. Table cache fills up. Each prefixed table maintains its own indexes, but the buffer pool is shared. Performance degrades non-linearly as you add subsites. Going from 10 to 20 subsites isn't twice the load — it can be three or four times the load depending on traffic patterns, because of lock contention and buffer pool thrashing.
I benchmarked a 40-subsite network once. Average query time on subsite #1 was 45ms. By the time we tested subsite #38, average query time had climbed to 380ms. Same query structure. Same data volume per site. The database was just drowning in tables.
4. Migration Is a Nightmare
Try extracting subsite #23 from a 50-site network into its own standalone WordPress install. Here's what you need to do:
- Export all
wp_23_prefixed tables - Remap every table from
wp_23_prefix towp_prefix - Reserialize all options and widget data (WordPress stores serialized PHP in the database, and string lengths change when you change prefixes)
- Remap media paths from
/uploads/sites/23/to/uploads/ - Search-and-replace all internal URLs
- Remap user capabilities from
wp_23_capabilitiestowp_capabilitiesinwp_usermeta - Extract users from the shared
wp_userstable (only the ones who belong to subsite #23) - Recreate user-to-site relationships
One error in serialization and you get corrupted data. Widget settings disappear. Theme customizer options break. Menu structures vanish. I've done this extraction process dozens of times, and it takes 4-8 hours per subsite even with tools like WP Migrate DB Pro. Multiply that by 50 subsites if you're decommissioning a network.
The WordPress ecosystem has tools for this, sure. But the fact that those tools need to exist tells you something about the architecture.
5. No True Data Isolation
This is the one that should terrify you if you care about security or compliance.
A SQL injection vulnerability on subsite #2 doesn't just expose wp_2_posts. The database user connecting to MySQL has access to every table in the database. That means wp_posts (main site), wp_3_posts (subsite 3), wp_users (all users across all sites), and every other table.
There is no database-level isolation. No row-level security. No schema separation. WordPress Multisite is one database, one set of credentials, and a naming convention. That's it.
For businesses handling customer data across locations — medical offices, law firms, financial services — this is a compliance problem. HIPAA, SOC 2, and PCI DSS all have requirements around data isolation. "We used different table prefixes" is not going to satisfy an auditor.
6. Super Admin Bottleneck
WordPress Multisite has a role called Super Admin. Only the Super Admin can:
- Install or delete plugins
- Install or delete themes
- Activate plugins network-wide
- Add new sites to the network
- Manage network settings
Individual site administrators have restricted capabilities. They can't install their own plugins. They can't add their own themes. Every change that touches the shared infrastructure routes through one person (or one small team).
For a 3-site network, this is fine. For a 50-location franchise where each location manager wants to add their own booking widget or menu PDF plugin? It's a bottleneck that breeds resentment and shadow IT.
7. Domain Mapping Fragility
Want each location to have its own domain? denver.yourbrand.com or yourbrand-denver.com? WordPress Multisite doesn't handle this natively in a reliable way. You need a domain mapping solution, and the built-in sunrise.php dropin approach is notoriously finicky.
SSL certificates for mapped domains add another layer. DNS configuration adds another. Every mapped domain is another potential point of failure that your Super Admin has to manage. One DNS change, one expired certificate, one misconfigured mapping entry, and a location's site goes down.
When WordPress Multisite Works (Honestly)
I'm not going to pretend it's useless. WordPress Multisite works well in specific scenarios:
- Small networks (under 10 sites) where all sites are managed by the same team
- University department sites where centralized control is actually desired
- Dev/staging/production mirrors for the same project
- Blog networks where content isolation isn't critical
If you have 5-8 sites, a competent sysadmin, and you don't need data isolation between sites — Multisite is fine. The problems start when you try to scale it to dozens or hundreds of locations.

The Architecture That Actually Scales to 500 Locations
Here's the alternative approach we use at Social Animal for multi-location businesses: a headless architecture with Next.js on the frontend and Supabase (PostgreSQL) on the backend, using Row-Level Security (RLS) for true data isolation.
The core idea: instead of duplicating tables for each location, you have one set of tables with a location_id column. Database-level security policies ensure that queries only return data for the authorized location. And instead of 500 separate WordPress installations pretending to be independent, you have one application deployment where /locations/[slug] dynamically renders each location's page from a database row.
How Row-Level Security Actually Works
-- Create the locations table
CREATE TABLE locations (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
slug TEXT UNIQUE NOT NULL,
name TEXT NOT NULL,
address TEXT,
phone TEXT,
hours JSONB,
metadata JSONB,
created_at TIMESTAMPTZ DEFAULT now()
);
-- Create the pages table with location isolation
CREATE TABLE pages (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
location_id UUID REFERENCES locations(id),
title TEXT NOT NULL,
content JSONB,
slug TEXT NOT NULL,
published BOOLEAN DEFAULT false,
created_at TIMESTAMPTZ DEFAULT now()
);
-- Enable RLS
ALTER TABLE pages ENABLE ROW LEVEL SECURITY;
-- Policy: location managers can only see their own location's pages
CREATE POLICY "location_isolation" ON pages
FOR ALL
USING (location_id = (SELECT location_id FROM user_locations WHERE user_id = auth.uid()));
-- Policy: public can read published pages for any location
CREATE POLICY "public_read" ON pages
FOR SELECT
USING (published = true);
With this setup, a location manager in Denver who somehow crafts a malicious query physically cannot access Austin's data. The database enforces it. Not the application layer. Not a naming convention. The database itself.
Architecture Comparison: Prefixed Tables vs Row-Level Security
Here's a visual representation of the two architectures:
WordPress Multisite Architecture:
┌─────────────────────────────────────────────┐
│ Single MySQL Database │
│ │
│ wp_posts (main site) │
│ wp_options (main site) │
│ wp_2_posts (Denver) │
│ wp_2_options (Denver) │
│ wp_3_posts (Austin) │
│ wp_3_options (Austin) │
│ wp_4_posts (Seattle) │
│ wp_4_options (Seattle) │
│ ... x 500 locations = 5,000+ tables │
│ │
│ ⚠️ ONE set of DB credentials │
│ ⚠️ ONE PHP process accesses ALL tables │
│ ⚠️ ZERO database-level isolation │
└─────────────────────────────────────────────┘
Next.js + Supabase Architecture:
┌─────────────────────────────────────────────┐
│ Single PostgreSQL Database │
│ │
│ locations (500 rows, one per location) │
│ pages (content per location) │
│ media (images per location) │
│ staff (team per location) │
│ reviews (reviews per location) │
│ │
│ ✅ RLS policies enforce isolation │
│ ✅ Denver user CANNOT query Austin data │
│ ✅ 5 tables total, not 5,000 │
│ ✅ Standard indexes, optimal query plans │
└─────────────────────────────────────────────┘
One database in both cases. But the isolation model is fundamentally different. RLS is enforced at the PostgreSQL engine level — it's not something the application can bypass.
| Factor | WordPress Multisite | Next.js + Supabase |
|---|---|---|
| Tables at 500 locations | ~5,500+ | ~5-15 |
| Data isolation | None (naming convention only) | Database-enforced RLS |
| Vulnerability surface | One exploit = all sites | Per-location auth isolation |
| Plugin conflicts | Network-wide outage | N/A (no plugin architecture) |
| Adding a location | Create subsite + configure | Insert database row |
| Removing a location | Complex extraction process | Delete rows with CASCADE |
| Domain mapping | Plugin required, fragile | Middleware rewrite, native |
| Build/deploy time | N/A (runtime PHP) | ~30s incremental build |
| TTFB (avg, uncached) | 800ms-2s+ | 50-150ms (edge) |
| Monthly hosting (500 sites) | $200-800/mo (managed WP) | $25-75/mo (Supabase + Vercel) |
| Recovery from compromise | Full network remediation | Rotate keys, redeploy |
Implementation: Next.js + Supabase for Multi-Location Sites
Here's how the routing works in practice with Next.js:
// app/locations/[slug]/page.tsx
import { createClient } from '@/lib/supabase/server';
import { notFound } from 'next/navigation';
export async function generateStaticParams() {
const supabase = createClient();
const { data: locations } = await supabase
.from('locations')
.select('slug');
return locations?.map(({ slug }) => ({ slug })) ?? [];
}
export default async function LocationPage({
params
}: {
params: { slug: string }
}) {
const supabase = createClient();
const { data: location } = await supabase
.from('locations')
.select(`
*,
pages(*),
staff(*),
reviews(*, rating)
`)
.eq('slug', params.slug)
.single();
if (!location) notFound();
return (
<div>
<h1>{location.name}</h1>
<LocationHero location={location} />
<LocationServices pages={location.pages} />
<LocationTeam staff={location.staff} />
<LocationReviews reviews={location.reviews} />
</div>
);
}
Adding a new location? Insert a row:
INSERT INTO locations (slug, name, address, phone, hours)
VALUES (
'portland-or',
'Portland, OR',
'123 Burnside St, Portland, OR 97209',
'(503) 555-0142',
'{"mon": "9-5", "tue": "9-5", "wed": "9-5"}'
);
That's it. The next build picks it up. Or if you're using ISR (Incremental Static Regeneration), it shows up within your revalidation window without a build at all.
Removing a location? DELETE FROM locations WHERE slug = 'portland-or'; Cascading deletes handle the rest. No 4-8 hour extraction process.
For custom domains per location, Next.js middleware handles it cleanly:
// middleware.ts
import { NextResponse } from 'next/server';
const DOMAIN_MAP: Record<string, string> = {
'yourbrand-denver.com': '/locations/denver-co',
'yourbrand-austin.com': '/locations/austin-tx',
// ... loaded from edge config in production
};
export function middleware(request: Request) {
const hostname = new URL(request.url).hostname;
const rewritePath = DOMAIN_MAP[hostname];
if (rewritePath) {
return NextResponse.rewrite(new URL(rewritePath, request.url));
}
return NextResponse.next();
}
No plugins. No sunrise.php dropin. No fragile mapping tables. Just a rewrite rule at the edge.
Migration Path: Getting Off WordPress Multisite
If you're currently on WordPress Multisite with 20+ locations, here's the realistic migration path:
Phase 1: Data Export (1-2 weeks)
Extract content from each subsite using the WordPress REST API or WP-CLI. Don't try to manually remap prefixed tables. Use the API — it abstracts the prefix nightmare.
# Export all posts from subsite 23
wp post list --url=example.com/location-23 --format=json > location-23-posts.json
# Export all media
wp media list --url=example.com/location-23 --format=json > location-23-media.json
Phase 2: Schema Design (1 week)
Design your Supabase schema around your actual content model, not WordPress's post/postmeta model. This is the moment to fix years of accumulated data model debt.
Phase 3: Content Migration (1-2 weeks)
Write migration scripts that transform WordPress content into your new schema. Handle serialized data, shortcodes, and Gutenberg blocks.
Phase 4: Frontend Build (3-4 weeks)
Build the Next.js frontend. This is where you see the actual performance gains. Pages that took 1.5 seconds on WordPress now load in under 200ms.
Phase 5: DNS Cutover (1 day)
Point your domains at the new infrastructure. Keep the old network running as a read-only backup for 30 days.
For businesses that need help with this migration process, we've documented our approach on our headless CMS development page. We've done enough of these migrations to know where the bodies are buried.
Real Numbers: Performance and Cost Comparison
Here's data from an actual migration we completed in Q1 2025 — a dental practice network with 34 locations:
| Metric | WordPress Multisite (Before) | Next.js + Supabase (After) |
|---|---|---|
| Average TTFB | 1,240ms | 89ms |
| Largest Contentful Paint | 3.8s | 1.1s |
| Monthly hosting cost | $347/mo (WP Engine) | $45/mo (Vercel Pro + Supabase Pro) |
| Time to add new location | 2-3 hours (manual setup) | 15 minutes (insert row + content) |
| Time to update all locations | Plugin update + prayer | git push |
| Core Web Vitals pass rate | 12 of 34 locations | 34 of 34 locations |
| Security incidents (12 months) | 3 | 0 |
The hosting cost reduction alone paid for the migration within 8 months. The performance improvements drove measurable increases in local search rankings for 28 of the 34 locations within 90 days.
If you're evaluating costs for your own network, our pricing page has transparent breakdowns for multi-location projects.
FAQ
Is WordPress Multisite good for managing multiple locations? For a small number of locations (under 10) with centralized management, WordPress Multisite can work. But it wasn't designed for multi-location businesses that need independent operation, data isolation, or high performance at scale. The shared database architecture creates security, performance, and operational problems that compound with every location you add.
What are the biggest problems with WordPress Multisite? The seven major problems are: shared vulnerability surface (one exploit hits all sites), plugin conflict multiplication (one conflict takes down every site), non-linear performance degradation, nightmare migration/extraction processes, zero database-level data isolation, Super Admin bottleneck for all administrative changes, and fragile domain mapping. These problems are architectural — they can't be fixed with plugins or better hosting.
Can WordPress Multisite handle 100+ sites? Technically, yes. Practically, it becomes very painful past 30-50 sites. At 100 sites you're dealing with 1,100+ database tables in a single MySQL instance, severe query planner degradation, and operational complexity that requires dedicated DevOps staff. At 500 sites, it's a non-starter for most hosting environments.
What is the best alternative to WordPress Multisite for multiple locations? A headless architecture using Next.js or Astro for the frontend with a PostgreSQL database (such as Supabase) using Row-Level Security provides true data isolation, dramatically better performance, lower hosting costs, and trivial location management. Each location is a database row, not an entire WordPress installation.
How do you migrate from WordPress Multisite to a headless architecture? The safest approach is to extract content via the WordPress REST API or WP-CLI rather than trying to manually remap prefixed database tables. Export content per-subsite, design a clean schema in your target database, write transformation scripts, build the new frontend, and cut over DNS. Plan for 6-10 weeks total for a network with 20+ sites.
Does WordPress Multisite affect SEO? Indirectly, yes. WordPress Multisite's performance degradation leads to slower page loads, which hurts Core Web Vitals scores. Google has confirmed that page experience signals influence rankings. In our migration data, 82% of locations saw improved local search rankings within 90 days of moving to a headless architecture with sub-200ms TTFB.
Is WordPress Multisite secure for business data? No, if you define security as including data isolation between locations. WordPress Multisite uses one database with one set of credentials. A SQL injection on any subsite can access every other subsite's data. For businesses subject to HIPAA, SOC 2, PCI DSS, or similar compliance requirements, the lack of database-level isolation is a significant liability.
How much does it cost to run WordPress Multisite vs a headless alternative? Managed WordPress hosting for Multisite typically runs $200-800/month depending on the number of sites and traffic levels (WP Engine's Multisite plans start at $240/mo for their Growth tier in 2025). A comparable headless setup on Vercel Pro ($20/mo) plus Supabase Pro ($25/mo) handles the same traffic for a fraction of the cost. The initial build investment is higher for the headless approach, but monthly operating costs are significantly lower. Feel free to reach out if you want a specific cost comparison for your network size.