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.

  • VPS
  • Nginx
  • WordPress
  • PHP