Host Multiple Websites on One VPS with Nginx Server Blocks
You've got one VPS and three (or ten) client sites to put on it. You don't need a separate server for each one — Nginx server blocks let you run any number of independent domains off a single box, each with its own document root, logs, and SSL certificate. Here's how to set it up correctly, and how to fix it when the wrong site loads or SSL only works for one domain.
What a server block actually does
A server block (Apache calls the same thing a virtual host) is a chunk of Nginx config that says: "if a request comes in for this hostname, serve it from this folder, with these rules." Nginx reads the Host header on every incoming request and matches it against your server blocks to decide which site to serve. One IP address, one Nginx process, many completely separate websites.
Each site gets:
- Its own document root (e.g.
/var/www/clienta.com/public_html) - Its own access and error log
- Its own SSL certificate
- Its own PHP-FPM pool, if you want that level of isolation
Step 1: Point DNS at your VPS for each domain
Before touching Nginx, make sure every domain you're adding has an A record pointing at your VPS's IP:
Type: A
Host: @
Value: your.vps.ip.address
TTL: 3600
Type: A
Host: www
Value: your.vps.ip.address
TTL: 3600
Do this for each domain in whatever DNS panel manages it (Getwebup DNS zone editor, or the registrar if you're not using our nameservers). Nginx can't route a request that never reaches the server in the first place, so confirm each domain actually resolves before you move on:
dig +short clienta.com
Step 2: Create the folder structure
Keep each site in its own directory under /var/www/ rather than dumping everything into one shared folder — it makes backups, permissions, and troubleshooting far simpler later.
sudo mkdir -p /var/www/clienta.com/public_html
sudo mkdir -p /var/www/clientb.com/public_html
sudo chown -R $USER:$USER /var/www/clienta.com/public_html
sudo chown -R $USER:$USER /var/www/clientb.com/public_html
sudo chmod -R 755 /var/www
Drop a placeholder index.html in each so you can confirm routing before you upload the real site:
echo "clienta.com is live" | sudo tee /var/www/clienta.com/public_html/index.html
Step 3: Write a server block per domain
Nginx configs usually live in /etc/nginx/sites-available/ with symlinks in /etc/nginx/sites-enabled/. Create one file per domain — don't cram multiple domains into one file:
sudo nano /etc/nginx/sites-available/clienta.com
server {
listen 80;
listen [::]:80;
server_name clienta.com www.clienta.com;
root /var/www/clienta.com/public_html;
index index.php index.html;
access_log /var/log/nginx/clienta.com.access.log;
error_log /var/log/nginx/clienta.com.error.log;
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
}
location ~ /\.ht {
deny all;
}
}
Enable it and repeat for every other domain:
sudo ln -s /etc/nginx/sites-available/clienta.com /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
Always run nginx -t before reloading. A typo in one server block file can take down every site on the box if you reload broken config — catching it at the test step costs you nothing.
Step 4: Get SSL for each domain separately
If you're using Certbot with the Nginx plugin, run it once per domain (or list several at once) and it'll edit each server block to add the correct listen 443 and certificate paths automatically:
sudo certbot --nginx -d clienta.com -d www.clienta.com
sudo certbot --nginx -d clientb.com -d www.clientb.com
Certbot creates a separate certificate per domain by default, which is what you want — each site's cert only covers its own hostnames, and renewing or revoking one never touches another site. Confirm auto-renewal is active:
sudo certbot renew --dry-run
Fixing the common problems
Every domain shows the same site
This almost always means server_name is wrong or missing in one of the blocks, or Nginx's default config is still enabled and catching requests first. Check what's active:
sudo nginx -T | grep server_name
If /etc/nginx/sites-enabled/default is still symlinked and has listen 80 default_server;, it can intercept requests for hostnames that don't exactly match another block. Either remove that symlink or make sure it's not marked as the default server once your real sites are configured.
New domain shows the Nginx welcome page instead of your site
Usually one of two things: the server block was never symlinked into sites-enabled, or DNS for that domain isn't actually pointing at this VPS yet. Check both:
ls -la /etc/nginx/sites-enabled/
dig +short yourdomain.com
403 Forbidden on a new site
Nginx runs as www-data (on Debian/Ubuntu) and needs read access down the whole path to your files, plus an actual index file to serve. Check permissions and that index.html or index.php exists in the document root:
sudo chown -R www-data:www-data /var/www/clienta.com/public_html
ls /var/www/clienta.com/public_html
One site's PHP errors are crashing another site's pages
That's a sign you're sharing a single PHP-FPM pool across sites with no isolation. For anything beyond a couple of low-traffic sites, give each domain its own pool in /etc/php/8.2/fpm/pool.d/clienta.conf with a distinct listen socket, and point that domain's server block at it. It costs a bit more RAM per site but keeps one site's runaway process from starving the others.
Keeping it manageable as you add more sites
- Name config files after the domain, not "site1", "site2" — future-you will thank you during an incident.
- Set up log rotation for each site's access/error logs (
logrotatehandles this by default on most distros, just confirm your custom log paths are included). - Watch total resource usage as sites grow — RAM and PHP-FPM worker limits are shared across every site on the box, so a traffic spike on one domain can slow the others. Monitor with
htopor a proper monitoring tool once you're past 4-5 sites. - Keep a per-site backup routine (or use Getwebup's VPS backup add-on) rather than one big backup job — it makes single-site restores much faster.
| Symptom | Likely Cause | Fix |
|---|---|---|
| All domains show the same content | Missing/duplicate server_name, leftover default block | Check nginx -T, remove/adjust default_server |
| Nginx welcome page on new domain | Block not symlinked, or DNS not pointed yet | Check sites-enabled and run dig |
| 403 Forbidden | Wrong file ownership or missing index file | chown www-data:www-data, confirm index exists |
| SSL works on one domain only | Certbot run without -d for the new domain | Re-run certbot with all relevant -d flags |
| One site's crash affects others | Shared PHP-FPM pool | Give high-traffic sites their own FPM pool |
Frequently asked questions
How many websites can I realistically host on one VPS?
It depends on your VPS specs and each site's traffic, not a fixed number. A 2GB-4GB RAM VPS comfortably handles 5-10 low-to-moderate traffic WordPress or static sites. Once any single site starts pulling serious traffic, consider giving it its own PHP-FPM pool or moving it to its own VPS.
Do I need a separate SSL certificate for every domain, or can one cover all of them?
You need a certificate that covers each domain's hostnames. Certbot issues one cert per domain by default, which is the safer setup - if you'd rather manage fewer certs, a SAN certificate can cover multiple unrelated domains, but renewing or reissuing it affects every domain listed on it at once.
Can I mix a static HTML site and a WordPress site on the same VPS?
Yes. Each server block is independent - one can point at a plain HTML folder with no PHP handling at all, while another routes .php requests to PHP-FPM. They don't interfere with each other as long as each has its own server block and document root.
Why does restarting Nginx sometimes take down every site on the VPS?
A syntax error in any one server block file can prevent Nginx from starting or reloading at all, since it loads the full config as one unit. Always run sudo nginx -t before sudo systemctl reload nginx - it validates every file and tells you exactly which one is broken before anything goes down.
Is server blocks the same thing as Apache's virtual hosts?
Functionally yes - both let one server answer for multiple domains based on the Host header. The config syntax is different (server { } blocks in Nginx vs VirtualHost tags in Apache), but the concept and the DNS setup you need beforehand are the same.