Hosting Multiple Sites on One VPS: Isolation That Stops One Site Sinking the Rest

Running several sites on one server saves real money, but without isolation a single heavy site can drag all the others down. The fix is three layers: a virtual host per site, a separate Linux user, and a memory-capped PHP-FPM pool per site.
Running several sites on a single VPS is the financially correct call, as long as each site is isolated from its neighbor across three layers: a virtual host (server block) that separates domains at the web server level, a separate Linux user per site that separates files and permissions, and an independent, memory-capped PHP-FPM pool that stops one site from eating all the RAM. Without these layers the server is a house with no interior walls, so a traffic spike on one site or one heavy database query freezes every site at once. With proper isolation, the failing site goes down alone and the rest keep serving.
The practical capacity rule: a Business plan with 8GB RAM and 4 vCPU comfortably holds 8 to 12 moderate-traffic sites (brochure sites, blogs, small stores), or 3 to 4 heavy applications (busy Laravel or WordPress). The deciding factor is not the count of sites but peak concurrent requests and the memory each PHP process uses. This guide builds the isolation step by step, then sizes capacity with real numbers, and defines exactly when to move from this pattern to separate servers.
Layer one: a virtual host per domain
The virtual host (a server block in Nginx) is what lets the server know that a request for site-a.sa is served from one directory and a request for site-b.sa from another, on the same IP and port. Each site gets its own configuration file, its own document root, and its own SSL certificate. This separation is necessary but shallow, because it splits routing only, not resources and not permissions.
In Nginx each site is a server block under /etc/nginx/sites-available/ pointing to its own pool through fastcgi_pass. The common mistake is two sites sharing a document root or an upload folder, which turns a breach of one site into a doorway into its neighbor. Keep each document root under its own user's directory, never under a shared folder.
Layer two: a separate Linux user per site
This layer is the real security wall. Create a system user for each site and place its files under that user's home directory with full ownership. The result is that the PHP process for site one cannot read the wp-config.php or .env of site two, because file permissions forbid it. If a site is compromised through an infected plugin, the damage stays locked inside its user and never spreads.
The correct permission setup: directories at 750 and files at 640, owned by the site user with the web server group (www-data) as the group. Never use 777 on any upload folder, since it opens the door to executing uploaded scripts. Bind each user to its own pool running under its identity, so isolation at the file layer matches isolation at the execution layer.
Layer three: a memory-capped PHP-FPM pool per site
This layer settles the question of stopping one site from sinking the rest. Instead of one shared pool for all PHP on the server, create an independent pool per site, each running under the site's user with its own limits on worker count and per-process memory. Each pool reserves its slice of RAM with a clear ceiling, so if a site sees a request spike, its processes hit their defined cap and queue, rather than consuming the whole server's memory and choking the neighbors.
The values that matter most in each pool:
- pm.max_children: the maximum concurrent PHP processes for the site. This is the real ceiling on memory use.
- pm = ondemand for light sites: processes spawn on request and close after a short idle, so they reserve no memory while asleep.
- pm = dynamic for active sites: keeps a minimum number of processes ready to cut response time.
- php_admin_value[memory_limit]: the memory ceiling for a single process, usually 128MB to 256MB for a mid-size application.
Calculating the ceiling with numbers
The simple rule: a site's maximum memory = pm.max_children × average process memory. If each Laravel process averages 80MB and you set max_children to 10, the site will not exceed roughly 800MB no matter the pressure. Sum the ceilings of all sites and make sure the total stays under available RAM minus the system and database share.
Sizing capacity on an 8GB Business plan
Let us distribute 8GB realistically on a Business plan (4 vCPU / 8GB):
| Item | Reserved | Note |
|---|---|---|
| OS + Nginx | around 1GB | core services |
| MySQL / MariaDB | 1.5 to 2GB | depends on index size and cache |
| Left for sites (PHP-FPM) | 5 to 5.5GB | split across pools |
If we allocate on average 500MB per moderate site (roughly 6 processes × 80MB each), then 5GB comfortably serves 8 to 10 moderate sites with a safety margin. If the sites are very light (brochure sites with low traffic) in ondemand mode, the count rises to 12 and beyond because they reserve no memory while idle. Heavy applications (a busy store or a SaaS app) fit 3 to 4 on the same plan before upgrading becomes smarter than crowding.
Do not forget CPU and disk
Memory usually runs out first, but watch the 4 vCPU too: every PHP process under real load takes its share of the processor, and a concurrent spike across several sites pushes the load average above the core count and slows everyone. Disk is shared as well, so a site writing huge logs or backups competes with the rest for input and output. Monitor all three, not memory alone.
When to move to separate servers
The shared pattern is excellent until you hit one of these thresholds:
- Sustained use above 70% of RAM or CPU for days in a row, not transient peaks. Here upgrading or splitting is a necessity.
- A site becomes business-critical: a store or app whose revenue depends on its stability should not share its fate with test sites.
- Compliance or data-isolation requirements: a government entity or sensitive data may mandate a dedicated server rather than a shared one.
- Conflicting load patterns: a CPU-bound site (image processing) next to a disk-bound site (a heavy database) competes for different resources, so splitting relieves both.
The advantage on Cloud is that upgrading is not a migration. You add +2GB RAM for 600 SAR per year or +1 vCPU for 500 SAR on the same server without moving files, so you postpone the split decision until it is genuinely necessary rather than precautionary. See the Cloud Server page and pricing and plans for add-on details.
An isolated database per site
Isolation is not complete without separating databases. Create a separate database and a dedicated MySQL user for each site, with permissions confined to its own database alone. The result is that a leak of one site's connection credentials does not open the rest of the databases, and a heavy query in one site does not lock another site's tables. Also set the cache-memory limits in MySQL deliberately, because one greedy database can eat the server's memory at everyone's expense. The practical rule: a DB user per site with minimal permissions, a separate database per application, and monitoring slow queries through the slow query log to spot the site crowding the rest before it takes them down. If a site becomes query-heavy, move its database to a separate database server, since this is usually the first thing split off before separating the web server itself.
The practical takeaway
To run several sites on one VPS safely: (1) an independent server block and SSL certificate per domain, (2) a separate Linux user with 750/640 permissions per site, (3) a PHP-FPM pool per site with calculated max_children and memory_limit. Compute the memory ceiling per pool and sum them so the total stays under available RAM, and monitor memory, CPU, and disk together. When usage settles above 70% or a site turns business-critical, upgrade resources first on Cloud without migration, then split servers if growth continues. Start with a Business 8GB plan and place 8 to 10 moderate sites on it with confidence.


