Nginx Server Blocks: Host Multiple Sites on One Server

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:
- A server running Nginx. If you don't have one yet, our guide to installing a LEMP stack on Ubuntu 24.04 gets you from bare server to working stack.
- A domain name with an A record (and ideally an AAAA record) pointing at your server's IP. For testing without a domain, you can fake it with your local
hostsfile — covered below. - A non-root user with
sudoprivileges.
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:
- This layout is a Debian/Ubuntu convention, not an Nginx feature. Nginx builds from other sources (or on RHEL-family distros) typically use
/etc/nginx/conf.d/*.confinstead. Both work; use whichever your distribution set up. - A fresh Ubuntu install ships a
defaultsite insites-enabledthat serves the "Welcome to nginx" page. We'll deal with it shortly.
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:
listen 80;andlisten [::]:80;— accept HTTP connections on port 80, over IPv4 and IPv6 respectively.server_name example.com www.example.com;— the domains this block answers to. Requests whoseHostheader matches either name land here.root— the directory Nginx serves files from for this site.index— which file to serve when a request hits a directory.try_files $uri $uri/ =404;— try the exact file, then a directory of that name, then return 404. This is the safe default for static content.
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:
sudo mkdir -p /var/www/seconddomain.com/htmland set ownership.- Create
/etc/nginx/sites-available/seconddomain.comwith its ownserver_nameandroot. - Symlink it into
sites-enabled, runsudo 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:
- Exact
server_namematch. - Longest wildcard name starting with
*(e.g.*.example.com). - Longest wildcard name ending with
*(e.g.mail.*). - First matching regular-expression name, in config-file order.
- 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
- "conflicting server name ... ignored" — two enabled blocks claim the same
server_nameon the same port. Nginx uses the first and warns about the rest. Find the duplicate withgrep -R "server_name" /etc/nginx/sites-enabled/. - "a duplicate default server" — two blocks declare
default_serveron one port. Remove the flag from one of them (the stockdefaultsite is the usual culprit). - "could not build server_names_hash" — you have many (or unusually long) domain names. Raise the bucket size in the
httpblock of/etc/nginx/nginx.conf:server_names_hash_bucket_size 64;. - Wrong site loads — usually DNS hasn't propagated, or your
Hostheader doesn't match anyserver_name, so the request fell to the default block. Test precisely withcurl -H "Host: example.com" http://your-server-ip/. - 403 Forbidden — Nginx can't read the web root. Check that every directory in the path is executable by
www-dataand files are readable (the755/644pattern from Step 1 handles this).
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.