LEMP Field Notes
Nginx & PHP

Nginx Server Blocks: Host Multiple Sites on One Server

Nginx Server Blocks: Host Multiple Sites on One Server
In briefNginx server blocks are configuration sections — each a server { } block — that let one Nginx instance host multiple websites on a single server. Each block declares the port it listens on, the domain names it answers to via server_name, and the document root for that site. Nginx matches each incoming request's Host header against every block's server_name to route it. On Ubuntu, blocks live in /etc/nginx/sites-available/ and are activated by symlinking them into /etc/nginx/sites-enabled/.

What Are Nginx Server Blocks?

Nginx server blocks are configuration sections that let a single Nginx instance serve multiple websites from one server. Each server { } block defines one site: which port it listens on, which domain names it answers to (server_name), and where its files live (root). When a request arrives, Nginx reads the Host header, matches it against the server_name of every block listening on that port, and hands the request to the winner. If you're coming from Apache, server blocks are the direct equivalent of virtual hosts — same idea, different syntax.

This guide walks through creating a server block from scratch on Ubuntu, enabling it the standard way with sites-available and sites-enabled, and verifying it works — then repeating the pattern for as many sites as your server can handle.

Prerequisites

Before you start, you'll need:

We'll use example.com throughout. Substitute your own domain everywhere it appears.

How sites-available and sites-enabled Work

On Debian and Ubuntu, the Nginx package ships two directories for site configs:

Directory Purpose
/etc/nginx/sites-available/ One file per site. Every config you've written lives here, active or not.
/etc/nginx/sites-enabled/ Symlinks to files in sites-available. Only linked configs are loaded.

The main config file, /etc/nginx/nginx.conf, contains the line include /etc/nginx/sites-enabled/*; — so Nginx only ever reads what's linked into sites-enabled. The payoff is operational: to take a site offline, you delete its symlink and reload Nginx. The config itself stays safely in sites-available, ready to re-enable with one command. No editing, no commenting-out, no risk of losing work.

Two things worth knowing:

Step 1 — Create the Web Root

Each site gets its own document root. The conventional location is a per-domain directory under /var/www:

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

Give your own user ownership so you can deploy files without sudo:

sudo chown -R $USER:$USER /var/www/example.com/html

Then make sure permissions allow Nginx (which runs as the www-data user on Ubuntu) to read the files:

sudo chmod -R 755 /var/www/example.com

Create a placeholder page so you can confirm the block works:

nano /var/www/example.com/html/index.html

Paste something identifiable:

<!DOCTYPE html>
<html>
<head><title>example.com works</title></head>
<body><h1>Server block for example.com is live.</h1></body>
</html>

Step 2 — Write the Server Block

Create a new config file named after the domain:

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

Here's a complete, working server block for a static site:

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

    server_name example.com www.example.com;

    root /var/www/example.com/html;
    index index.html index.htm;

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

Line by line:

If the site runs PHP, add a PHP location block that hands .php requests to PHP-FPM:

    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php8.3-fpm.sock;
    }

The socket path must match your installed PHP version — on Ubuntu 24.04 that's PHP 8.3 by default; run ls /run/php/ to confirm what's actually there.

Step 3 — Enable the Site

Link the config into sites-enabled:

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

Now — always — test the configuration before touching the running service:

sudo nginx -t

You want to see syntax is ok and test is successful. If you get an error instead, the message includes the file and line number; fix it and re-test. Nothing has broken yet, because the running Nginx is still using the old config.

Once the test passes, reload:

sudo systemctl reload nginx

reload tells Nginx to re-read its config and swap in new worker processes without dropping existing connections — safer than restart on a live server.

Visit http://example.com in a browser. You should see your placeholder page.

No public domain yet? Add a line to the hosts file on your local machine (/etc/hosts on Linux/macOS, C:\Windows\System32\drivers\etc\hosts on Windows):

203.0.113.10  example.com www.example.com

Replace 203.0.113.10 with your server's real IP. Your browser will then resolve the domain to your server, letting you test name-based hosting before DNS exists. Remove the line when you're done.

Adding More Sites

This is where server blocks earn their keep. To host a second site, repeat the pattern with a new domain:

  1. sudo mkdir -p /var/www/seconddomain.com/html and set ownership.
  2. Create /etc/nginx/sites-available/seconddomain.com with its own server_name and root.
  3. Symlink it into sites-enabled, run sudo nginx -t, then reload.

Both sites now share port 80 on the same IP. Nginx tells them apart purely by the Host header each browser sends — this is name-based virtual hosting, and it's how one modest VPS can serve dozens of low-traffic sites.

How Nginx Picks a Block

When several blocks listen on the same port, Nginx selects one in this order:

  1. Exact server_name match.
  2. Longest wildcard name starting with * (e.g. *.example.com).
  3. Longest wildcard name ending with * (e.g. mail.*).
  4. First matching regular-expression name, in config-file order.
  5. If nothing matches: the block marked default_server, or failing that, the first block defined for that port.

Set an Explicit Catch-All

Requests that match no server_name — bots probing your bare IP, typo domains pointed at you — fall through to the default. Rather than let one of your real sites absorb that traffic, define an explicit catch-all:

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

444 is a non-standard Nginx status that closes the connection without a response — a clean way to drop junk traffic. Ubuntu's stock default site already claims default_server, and only one block per port may hold it, so either edit that file or remove its symlink (sudo rm /etc/nginx/sites-enabled/default) before adding your own catch-all. Then nginx -t and reload as always.

Troubleshooting Common Errors

Next Step: Add HTTPS

A server block on port 80 is only half a site in 2026 — browsers flag plain HTTP, and search engines expect encryption. The good news: Certbot reads your server_name directives and configures TLS per site automatically, adding the listen 443 ssl configuration for you. Our Let's Encrypt and Certbot tutorial walks through issuing free certificates and setting up automatic renewal for every server block you've created here.

From there, the pattern is set: one config file per site, nginx -t before every reload, and a server that scales to as many domains as you care to point at it. For more line-by-line Nginx configuration guides, browse our Nginx & PHP collection.

FAQ

What is the difference between sites-available and sites-enabled in Nginx?

sites-available holds one config file per site, whether or not that site is live. sites-enabled holds symlinks pointing to files in sites-available, and Nginx's main config only loads what's in sites-enabled. To activate a site you create a symlink with ln -s; to deactivate it you delete the symlink and reload Nginx. The original config stays untouched in sites-available, so re-enabling later takes one command. This two-directory layout is a Debian/Ubuntu packaging convention, not a core Nginx feature.

Are Nginx server blocks the same as Apache virtual hosts?

Functionally, yes. Both let one web server host multiple sites on a single machine by inspecting the Host header of each request. Apache calls them virtual hosts and configures them with VirtualHost sections; Nginx calls them server blocks and uses server { } sections with a server_name directive. The concept and the DNS setup are identical — only the configuration syntax and file locations differ. If a tutorial mentions an Nginx virtual host, it means a server block.

How many websites can one Nginx server host?

There is no fixed limit in Nginx itself — you can define as many server blocks as you like, and one modest VPS commonly serves dozens of low-traffic sites. The practical ceiling is your server's resources: RAM, CPU, and (for dynamic sites) PHP-FPM capacity and database load. If you host many domains or very long domain names, you may need to raise server_names_hash_bucket_size in nginx.conf so Nginx can build its name-lookup table.

How does Nginx decide which server block handles a request?

Nginx first narrows to the blocks listening on the matching IP and port, then compares the request's Host header against each block's server_name in a fixed order: exact match first, then the longest wildcard starting with an asterisk, then the longest wildcard ending with one, then the first matching regular expression. If nothing matches, the request goes to the block marked default_server on that port — or, absent that flag, to the first block defined for it.

Why is my Nginx server block showing the wrong website?

Almost always, the request is falling through to the default server block because no server_name matched. Common causes: DNS hasn't propagated yet, a typo in the server_name directive, the symlink in sites-enabled was never created, or Nginx wasn't reloaded after the change. Test precisely with curl -H "Host: example.com" http://your-server-ip/ to bypass DNS, and run nginx -t to surface warnings like conflicting server names, which mean two blocks claim the same domain.

Do I need a separate server block for HTTPS?

Each site typically ends up with configuration listening on both port 80 and port 443, and the common pattern is a small port-80 block that redirects to HTTPS plus a main block with listen 443 ssl and the certificate paths. In practice you rarely write this by hand: Certbot's Nginx plugin reads your existing server_name directives, obtains a certificate, and edits the block to add the TLS configuration and redirect for you.