How to host several websites on one VPS with nginx server blocks

One VPS can serve many websites. Each visitor's browser tells the server which domain it wants, and nginx uses that to pick the right site. The mechanism is called a server block, and once you understand it, adding a new site takes a few minutes and a short file.

How nginx chooses

When a request arrives, nginx looks at the Host header the browser sent, such as shop.mydomain.net, and compares it with the server_name values in your server blocks. The matching block decides what to do: serve files from a folder, or pass the request to an application. If nothing matches, nginx uses the default server.

All your sites share the same IP address and the same ports, 80 and 443. The domain name is what separates them. Each domain needs a DNS record pointing to the server. See Point your domain to your server and DNS records explained.

Where the files go

On Ubuntu and Debian, nginx uses two folders:

  • /etc/nginx/sites-available/ holds a config file for each site. Nothing here is active on its own.
  • /etc/nginx/sites-enabled/ holds links to the files in sites-available that you want active.

That arrangement lets you switch a site off by removing the link, without deleting the file.

Add a static site

  1. Make a folder for the site's files, and a test page:
sudo mkdir -p /var/www/site-one
echo "Hello from site one" | sudo tee /var/www/site-one/index.html
  1. Create the config, for example /etc/nginx/sites-available/site-one:
server {
    listen 80;
    listen [::]:80;
    server_name site-one.net www.site-one.net;

    root /var/www/site-one;
    index index.html;

    location / {
        try_files $uri $uri/ =404;
    }
}
  1. Enable it with a link, test the syntax and reload:
sudo ln -s /etc/nginx/sites-available/site-one /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx

nginx -t checks the configuration before you apply it. Never skip it. A typo can take every site down.

Add a second site

Repeat the process with a different folder, file name and server_name. That is all. Both sites now respond on the same server, each to its own domain.

Pass a site to an application

For a Node.js, Python or container app, the block forwards requests instead of serving files:

server {
    listen 80;
    server_name app.mydomain.net;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

More detail, including keeping the app running, is in Run a Node.js app on your VPS with PM2 and nginx.

Add HTTPS

Use Certbot, which edits the right server blocks for you. For each domain:

sudo certbot --nginx -d site-one.net -d www.site-one.net

It obtains a certificate and adds the HTTPS settings and a redirect. See Set up an SSL certificate (HTTPS). If a renewal ever fails, Fixing Let's Encrypt renewal problems helps.

A default server for unknown names

By default, a request for an unknown domain pointed at your IP will land on the first server block, which may expose one of your sites under a wrong name. Add an explicit catch-all that does nothing useful:

server {
    listen 80 default_server;
    listen [::]:80 default_server;
    server_name _;
    return 444;
}

Status 444 is nginx-specific, and means "close the connection without answering".

Logs

Each site can have its own logs, which makes troubleshooting far easier:

access_log /var/log/nginx/site-one.access.log;
error_log  /var/log/nginx/site-one.error.log;

When something is wrong, the error log is the first thing to read. How to read your server logs has tips on finding the real cause.

Common mistakes

MistakeEffect
Missing semicolon at the end of a linenginx -t reports a syntax error
Same server_name in two filesA "conflicting server name" warning. One block is ignored
Forgetting the link in sites-enabledThe site does not exist as far as nginx is concerned
Wrong folder permissions403 Forbidden. The www-data user must be able to read the files
Not reloading after a changeThe old configuration keeps running
DNS not pointing at the serverThe domain never reaches nginx

A conflicting name is easy to cause. If you copy a config to make a new site and forget to change server_name, nginx warns about it and silently uses one of the two.

When a page returns an error

A 502 or 504 means nginx could not get a good answer from the application behind it. See How to fix nginx 502 and 504 errors.


Still stuck? Open a ticket on Discord - run /ticket in any channel, pick a department, and include your Server ID or Invoice ID so we can jump straight in.