Running one website per server is expensive. A $6 VPS can comfortably host a dozen small websites — if you know how to tell Nginx which site a visitor is asking for. That is exactly what server blocks do: they let one Nginx instance serve many different domains from the same machine.

In this guide you will set up two completely separate websites on a single Ubuntu 24.04 server, each with its own domain name, document root, and configuration file. The technique is identical to Apache's virtual hosts — Nginx just calls them server blocks. Every step below uses real, current commands tested on Ubuntu 24.04 and 22.04.

Prerequisites

  • A server running Ubuntu 24.04 or 22.04 with a non-root user that has sudo privileges.
  • Two domain names with A records pointing at your server's public IP (e.g. site1.example.com and site2.example.com). If your domains are not pointed yet, you can still test locally using your computer's hosts file — Step 6 shows how.
  • A terminal connection to the server (SSH).

Step 1 — Install Nginx and open the firewall

First update the package index, then install Nginx:

sudo apt update
sudo apt install nginx -y

apt update refreshes the local list of available packages so you get the current Nginx version from Ubuntu's repositories. The -y flag answers "yes" to the install confirmation automatically.

Check that the service is running:

sudo systemctl status nginx

You should see active (running). If UFW is enabled, allow HTTP and HTTPS traffic:

sudo ufw allow 'Nginx Full'

The 'Nginx Full' profile opens ports 80 and 443, which is what you need for websites now and for HTTPS later.

Step 2 — Create a document root and sample page for each site

Each website gets its own folder under /var/www. Replace the example domains with your own:

sudo mkdir -p /var/www/site1.example.com/html
sudo mkdir -p /var/www/site2.example.com/html

mkdir -p creates the nested directories in one shot. The html subfolder keeps the public web files separate from any future private files for the site.

Give your user ownership of these folders so you can edit files without sudo every time:

sudo chown -R $USER:$USER /var/www/site1.example.com/html
sudo chown -R $USER:$USER /var/www/site2.example.com/html
sudo chmod -R 755 /var/www

755 lets Nginx read the files while only the owner can write to them — the safe, standard permission for web roots.

Now drop a simple test page in each site so you can tell them apart:

cat > /var/www/site1.example.com/html/index.html <<'EOF'
<html>
  <head><title>Site 1</title></head>
  <body>
    <h1>Welcome to Site 1</h1>
    <p>Served by the first Nginx server block.</p>
  </body>
</html>
EOF
cat > /var/www/site2.example.com/html/index.html <<'EOF'
<html>
  <head><title>Site 2</title></head>
  <body>
    <h1>Welcome to Site 2</h1>
    <p>Served by the second Nginx server block.</p>
  </body>
</html>
EOF

The heredoc (<<'EOF') writes everything between the markers into the file — a clean way to create multi-line files from the terminal.

Step 3 — Create a server block file for each site

Nginx on Ubuntu reads site configuration from /etc/nginx/sites-available/, and only loads files that are linked into /etc/nginx/sites-enabled/. Create the config for the first site:

sudo nano /etc/nginx/sites-available/site1.example.com

Paste in the following block:

server {
    listen 80;
    listen [::]:80;

    server_name site1.example.com www.site1.example.com;
    root /var/www/site1.example.com/html;
    index index.html;

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

What each directive does: listen binds this block to port 80 (IPv4 and IPv6); server_name is the magic line — Nginx compares the domain in the visitor's request against this and routes the request to the matching block; root points at the site's document root from Step 2; try_files serves the requested file, then the directory index, then a 404 if neither exists.

Create the second block the same way, changing the names and root:

sudo nano /etc/nginx/sites-available/site2.example.com
server {
    listen 80;
    listen [::]:80;

    server_name site2.example.com www.site2.example.com;
    root /var/www/site2.example.com/html;
    index index.html;

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

Step 4 — Enable the sites and deal with the default block

"Enabling" a site means creating a symlink in sites-enabled. Nginx ignores files in sites-available that are not linked:

sudo ln -s /etc/nginx/sites-available/site1.example.com /etc/nginx/sites-enabled/
sudo ln -s /etc/nginx/sites-available/site2.example.com /etc/nginx/sites-enabled/

Ubuntu ships with a default block that catches any request whose domain does not match a server_name. If you want unmatched visitors to see one of your sites instead of the Nginx welcome page, remove it:

sudo rm /etc/nginx/sites-enabled/default

If you would rather keep the welcome page as a fallback, leave the symlink in place. Either choice is valid; just be deliberate about it.

Step 5 — Test the configuration and reload Nginx

Never reload Nginx without testing first — a syntax error would take down all your sites:

sudo nginx -t

You want to see syntax is ok and test is successful. Then apply the changes:

sudo systemctl reload nginx

reload (unlike restart) applies the new configuration without dropping active connections — your existing visitors notice nothing.

Step 6 — Point your domains (or fake it for testing)

In production, each domain needs an A record pointing to your server's IP at your DNS provider. DNS can take a few minutes to a few hours to propagate.

To test immediately without touching DNS, add lines to the hosts file on your own computer (not the server). On Linux/macOS edit /etc/hosts; on Windows edit the hosts file at C:\Windows\System32\drivers\etc\hosts, adding:

203.0.113.10 site1.example.com
203.0.113.10 site2.example.com

Replace 203.0.113.10 with your server's real IP. Now your browser resolves those domains to your server, and Nginx's server_name matching kicks in. Remove these lines once real DNS is live.

Verify it worked

Run through this checklist:

  1. Open http://site1.example.com and http://site2.example.com in a browser — each should show its own welcome page.
  2. From the server, force the Host header to double-check routing without DNS:
    curl -H "Host: site1.example.com" http://localhost/
    curl -H "Host: site2.example.com" http://localhost/
    Each response should contain the matching site's title.
  3. Confirm both blocks are loaded:
    sudo nginx -T | grep server_name
    You should see your domains listed.
  4. Check the logs if anything looks off:
    sudo tail -n 20 /var/log/nginx/access.log
    sudo tail -n 20 /var/log/nginx/error.log

Common issues & fixes

1. 403 Forbidden

Almost always a permission problem. Nginx's worker user (www-data) must be able to read every directory down to the files. Fix ownership and permissions, then reload:

sudo chown -R $USER:www-data /var/www/site1.example.com
sudo chmod -R 755 /var/www/site1.example.com
sudo systemctl reload nginx

2. 404 Not Found on every page

The root path in the server block does not contain the requested file — usually a typo in the domain folder name. Compare the root line with the actual path using ls, fix it, run sudo nginx -t and reload.

3. nginx -t reports a syntax error

A missing semicolon or a stray brace is the classic culprit. The error message names the file and line number — open that exact line, fix it, and re-test. Never reload until the test passes.

4. The Nginx welcome page shows instead of my site

The request is hitting the default block, which means server_name did not match. Causes: the browser is using the wrong domain, DNS still points elsewhere, or you kept sites-enabled/default and its default_server wins. Check with curl -H "Host: ..." to isolate DNS from Nginx config.

5. Changes do not appear after editing

You edited the file in sites-available but either forgot to create the symlink in sites-enabled, or forgot to reload. Verify with ls -l /etc/nginx/sites-enabled/ and run sudo systemctl reload nginx. Also hard-refresh the browser (Ctrl+Shift+R) to rule out cache.

Conclusion

That is all it takes: one block per site, one symlink per block, and Nginx routes each domain to its own document root. The same pattern scales to dozens of sites on a single VPS, and each block can later grow its own HTTPS certificate, PHP handling, or reverse-proxy rules without touching the others.

If you would rather not wrestle with server configuration yourself, XpertBees sets up and hardens Nginx servers for clients every week — reach out through the contact page and we will handle it.