A single VPS can comfortably host many websites. The key is to keep them separated — each site with its own directory, system user, PHP pool and database — so one site’s problem doesn’t spread to the others.
Layout
/var/www/
├── site-a.com/
│ ├── public/ # document root
│ └── logs/
└── site-b.com/
├── public/
└── logs/
1. Create a user per site
for s in site-a.com site-b.com; do
u="web_${s%%.*}"; u="${u//-/_}"
adduser --system --group --home /var/www/$s "$u"
mkdir -p /var/www/$s/{public,logs}
chown -R "$u":"$u" /var/www/$s
done
2. One PHP-FPM pool per site
/etc/php/8.3/fpm/pool.d/site-a.conf:
[site-a]
user = web_site_a
group = web_site_a
listen = /run/php/site-a.sock
listen.owner = www-data
listen.group = www-data
pm = ondemand
pm.max_children = 8
pm.process_idle_timeout = 30s
php_admin_value[open_basedir] = /var/www/site-a.com:/tmp
php_admin_value[error_log] = /var/www/site-a.com/logs/php-error.log
Repeat for each site, then systemctl reload php8.3-fpm. ondemand starts workers only when needed, which saves memory on low-traffic sites.
3. An Nginx server block per site
/etc/nginx/sites-available/site-a.com:
server {
listen 80;
server_name site-a.com www.site-a.com;
root /var/www/site-a.com/public;
index index.php index.html;
access_log /var/www/site-a.com/logs/access.log;
error_log /var/www/site-a.com/logs/error.log;
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/site-a.sock;
}
}
Static sites skip the PHP block; Node/Python apps use proxy_pass instead (see reverse proxy on Linux).
ln -s /etc/nginx/sites-available/site-a.com /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx
Add a default server that rejects unknown hostnames, so requests to the bare IP don’t show one of your sites:
server {
listen 80 default_server;
server_name _;
return 444;
}
4. A database and user per site
CREATE DATABASE site_a CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
CREATE USER 'site_a'@'localhost' IDENTIFIED BY '…';
GRANT ALL ON site_a.* TO 'site_a'@'localhost';
5. HTTPS for every domain
certbot --nginx -d site-a.com -d www.site-a.com
certbot --nginx -d site-b.com -d www.site-b.com
Certbot renews all certificates automatically.
6. Plan resources
Rough guidance with page caching in place:
| VPS size | Typical capacity |
|---|---|
| 2 GB RAM | A few small sites or one moderate WordPress site |
| 4 GB RAM | 5–10 low/moderate-traffic WordPress sites |
| 8 GB RAM | 10–25 sites, or a few busy ones |
Caching makes the biggest difference — see WordPress VPS performance tuning. Watch memory with free -h and per-pool usage with ps.
Should you use a control panel?
Panels like HestiaCP, CyberPanel or cPanel automate exactly these steps (users, pools, vhosts, databases, certificates, email) through a web UI. They’re worth it if you manage many sites for clients. For a handful of your own sites, the manual setup above is lighter and more transparent. See installing cPanel/WHM on a VPS.
Separate IPs per site?
Not required — Nginx and TLS (SNI) serve many HTTPS sites from one IP. Some SEO and multi-tenant setups prefer a separate IP per site; our Multi-IP VPS plans include many IPv4 addresses for that.
When to split onto more servers
- One site’s traffic dominates and slows others.
- A site has different security requirements (e.g. a shop handling payments).
- Backups and maintenance windows conflict.
Related: secure web hosting on Linux and LAMP stack installation.