A server block is Nginx’s equivalent of an Apache virtual host: a server { } section that tells Nginx how to answer requests for a particular domain. With server blocks, one VPS and one IP address can serve as many websites as its resources allow.

This guide assumes Nginx is installed on Ubuntu or Debian — see installing Nginx on Ubuntu.

How Nginx picks a server block

For each request, Nginx:

  1. Takes the IP and port the request arrived on, and considers only server blocks that listen there.
  2. Compares the request’s Host header against each block’s server_name.
  3. If nothing matches, uses the default server for that IP/port — the one marked default_server, or else the first one loaded.

That third step explains a classic surprise: a typo in server_name doesn’t cause an error, it silently shows another site.

Directory layout per site

sudo mkdir -p /var/www/site-one.com/html /var/www/site-two.com/html
echo '<h1>Site one</h1>' | sudo tee /var/www/site-one.com/html/index.html
echo '<h1>Site two</h1>' | sudo tee /var/www/site-two.com/html/index.html
sudo chown -R www-data:www-data /var/www/site-one.com /var/www/site-two.com

If a deploy user uploads files, make it the owner and give www-data read access through the group instead — Nginx only needs to read static files.

One file per site

/etc/nginx/sites-available/site-one.com:

server {
    listen 80;
    listen [::]:80;
    server_name site-one.com www.site-one.com;

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

    location / {
        try_files $uri $uri/ =404;
    }

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

Create site-two.com the same way with its own server_name and root. Then enable both:

sudo ln -s /etc/nginx/sites-available/site-one.com /etc/nginx/sites-enabled/
sudo ln -s /etc/nginx/sites-available/site-two.com /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx

On distributions without sites-available (AlmaLinux, Rocky, nginx.org packages), put each file in /etc/nginx/conf.d/<domain>.conf — every .conf there is loaded automatically.

server_name patterns

server_name example.com www.example.com;   # exact names
server_name *.example.com;                  # any subdomain
server_name .example.com;                   # example.com and all subdomains
server_name ~^(?<sub>.+)\.example\.com$;    # regex, captures the subdomain

Exact names win over wildcards, wildcards over regexes.

Catch-all default server

Requests for unknown hostnames — or for your bare IP address, which scanners hit constantly — should not land on a real site. Add a catch-all:

# /etc/nginx/sites-available/000-default
server {
    listen 80 default_server;
    listen [::]:80 default_server;
    server_name _;
    return 444;   # close the connection without a response
}

Remove Ubuntu’s stock default site from sites-enabled when you add this. Only one block per IP:port may be default_server.

Redirect www to the bare domain

Pick one canonical hostname for SEO and cookies:

server {
    listen 80;
    server_name www.site-one.com;
    return 301 http://site-one.com$request_uri;
}

After HTTPS is enabled, the redirect target becomes https://.

HTTPS for every site

Certbot handles multiple sites without fuss — one certificate per site, each with its domains:

sudo certbot --nginx -d site-one.com -d www.site-one.com
sudo certbot --nginx -d site-two.com -d www.site-two.com

Certbot adds listen 443 ssl blocks and HTTP→HTTPS redirects into each file. Nginx uses SNI to present the right certificate for each domain on the same IP. See Let’s Encrypt with Certbot on Nginx.

Proxying a site to an app

A server block doesn’t have to serve files. To send a domain to a Node.js, Python or Docker app on a local port:

server {
    listen 80;
    server_name app.example.com;

    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 in the Nginx reverse proxy guide.

Debugging

sudo nginx -t                        # syntax check
sudo nginx -T | less                 # print the full merged configuration
curl -I -H 'Host: site-two.com' http://127.0.0.1   # test a block before DNS is live

The nginx -T output shows exactly which server blocks are loaded and in what order — the fastest way to find a duplicate server_name (Nginx warns: conflicting server name … ignored).

How many sites can one VPS hold?

Static sites cost almost nothing — hundreds fit on a small server. Dynamic sites (WordPress, apps) are limited by RAM and PHP workers: plan roughly 100–300 MB per moderately busy WordPress site, plus the database. For an overview of running many sites, see hosting multiple websites on one VPS.

Our KVM VPS plans give you root access and a dedicated IPv4 address, so you can run as many server blocks as your plan’s resources support.

  • Nginx
  • Web Server
  • Linux