Nginx Server Blocks (Virtual Hosts)
A single Ubuntu server can host many websites at the same time, each with its own domain name and its own files. In Nginx, the configuration that defines one of those sites is called a server block (often called a virtual host, the same idea Apache uses under a different name). A server block tells Nginx: “when a request comes in for this domain, serve files from this folder.” This page walks you through building a server block from scratch and hosting example.com on a fresh Ubuntu 22.04/24.04 LTS server.
What a server block actually does
When a browser asks for http://example.com, it opens a connection to your server’s IP address and sends along the host name it wants (example.com). Nginx looks at every server block you have defined and picks the one whose server_name matches. That matching block decides which directory the files come from, which port to listen on, and a hundred other things.
This matters because it lets one cheap server run example.com, blog.example.com, and shop.example.com all at once, each pointing at different files — no extra machines needed.
When to use server blocks: any time you host more than one site or domain on a server, or even when you host just one site but want clean, per-site config files. When NOT to bother: for a quick throwaway test on
localhost, the default config is fine — you only need named server blocks once real domains are involved.
The sites-available and sites-enabled pattern
On Ubuntu, Nginx config is split into two directories on purpose:
| Directory | What it holds | When Nginx reads it |
|---|---|---|
/etc/nginx/sites-available | Every site config you have written, enabled or not | Never directly |
/etc/nginx/sites-enabled | Symbolic links to the configs that are live | On every reload |
The main config file /etc/nginx/nginx.conf contains a line include /etc/nginx/sites-enabled/*;, so only files (links) inside sites-enabled are actually loaded. A symbolic link (or symlink) is just a pointer to another file. This split lets you keep a site’s config on disk but turn it off by removing only the link — you never lose the real file.
Step 1 — create the web root and a test page
The web root is the folder whose files get served to visitors. Create one for the site and drop in a simple HTML file:
sudo mkdir -p /var/www/example.com/html
sudo chown -R $USER:$USER /var/www/example.com/html
echo '<h1>Hello from example.com</h1>' | sudo tee /var/www/example.com/html/index.html
The mkdir -p makes the full path in one go, chown gives your user ownership so you can edit files without sudo, and tee writes the test page.
Step 2 — write the server block
Create a new config file in sites-available, named after the domain so it is easy to find later:
sudo nano /etc/nginx/sites-available/example.com
Paste this in:
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;
}
}
Here is what each directive means:
| Directive | What it does |
|---|---|
listen 80; | Accept plain HTTP on port 80 (IPv4). The [::]:80 line does the same for IPv6. |
server_name | The domain(s) this block answers to. List both the bare and www versions. |
root | The folder on disk to serve files from. |
index | Which file to serve when someone requests a directory (here, index.html). |
location / | Rules for matching request paths; try_files looks for the file, then the directory, then returns a 404 if neither exists. |
Step 3 — enable the site with a symlink
Link the file from sites-available into sites-enabled. The -s flag makes it a symbolic link rather than a full copy:
sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
Gotcha: never copy the file into
sites-enabledinstead of linking it. If you copy it, you end up with two separate files, and editing one won’t update the other — a classic source of “but I changed the config!” confusion. Always useln -s.
Step 4 — test the config and reload
Always check your config for typos before reloading. The nginx -t command parses everything and reports problems without touching the running server:
sudo nginx -t
Output:
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
If the test passes, reload Nginx so it picks up the new block. A reload applies new config without dropping existing connections, unlike a full restart:
sudo systemctl reload nginx
Step 5 — open the firewall and verify
If you use ufw (Uncomplicated Firewall, Ubuntu’s simple firewall front-end), make sure HTTP traffic is allowed:
sudo ufw allow 'Nginx HTTP'
sudo ufw status
Output:
Status: active
To Action From
-- ------ ----
Nginx HTTP ALLOW Anywhere
Nginx HTTP (v6) ALLOW Anywhere (v6)
Now request the site. If your DNS already points example.com at the server, visit it in a browser. To test before DNS is set up, force the host header locally with curl:
curl -H "Host: example.com" http://localhost
Output:
<h1>Hello from example.com</h1>
That output confirms the right server block answered for example.com.
Disabling a site
To take a site offline without deleting its config, remove only the symlink and reload:
sudo rm /etc/nginx/sites-enabled/example.com
sudo systemctl reload nginx
The real file in sites-available stays put, so you can re-enable it later by linking it again.
Best Practices
- Name each config file after its primary domain (
example.com) so the purpose is obvious at a glance. - Keep web roots organized under
/var/www/<domain>/html— one predictable folder per site. - Always run
sudo nginx -tbefore every reload; never reload a config you haven’t tested. - Enable sites with symlinks (
ln -s), and disable them by removing the link, not by editing the real file. - List both
example.comandwww.example.cominserver_nameso visitors reach you either way. - Once a site works on HTTP, add HTTPS with a free Let’s Encrypt certificate (via
certbot) before going to production. - Remove the bundled
defaultsymlink fromsites-enabledonce your own block is the intended catch-all, to avoid confusing fall-through behavior.