nginx Server Blocks (Virtual Hosts) on Ubuntu
Apache people call them virtual hosts; nginx calls them server blocks. Either way the idea is the same: one nginx instance on one IP address serves several sites, and picks the right document root based on the Host header the browser sends. On Ubuntu 16.04 the package layout makes this tidy, with one file per site under /etc/nginx/sites-available/ and a symlink into sites-enabled/ to turn it on.
I use this constantly for internal tools: phpMyAdmin on one hostname, a status page on another, all on the same small VM. Below is two sites plus a default catch-all, so random hostnames don't land on the wrong site.
You need nginx installed (sudo apt install nginx, which is 1.10.x on xenial), DNS A records or hosts-file entries for your names, and port 80 open in ufw (sudo ufw allow 'Nginx HTTP').
Document roots
I keep sites under /var/www/<hostname>/html so the path tells me what it is:
sudo mkdir -p /var/www/example.com/html
sudo mkdir -p /var/www/tools.example.com/html
sudo chown -R $USER:$USER /var/www/example.com/html /var/www/tools.example.com/html
sudo chmod -R 755 /var/www
Drop a trivial page in each so you can tell them apart later:
echo '<h1>example.com</h1>' > /var/www/example.com/html/index.html
echo '<h1>tools.example.com</h1>' > /var/www/tools.example.com/html/index.html
The first server block
I don't edit the shipped default file for real sites. Copy it and strip it down:
sudo cp /etc/nginx/sites-available/default /etc/nginx/sites-available/example.com
sudo nano /etc/nginx/sites-available/example.com
Replace the contents with:
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
root /var/www/example.com/html;
index index.html index.htm;
access_log /var/log/nginx/example.com.access.log;
error_log /var/log/nginx/example.com.error.log;
location / {
try_files $uri $uri/ =404;
}
}
The key line is server_name. nginx compares it to the Host header, exact names first, then wildcard names like *.example.com, then regular expressions. Per-site log files are optional but make debugging much easier than grepping one shared log.
Second site
sudo nano /etc/nginx/sites-available/tools.example.com
server {
listen 80;
listen [::]:80;
server_name tools.example.com;
root /var/www/tools.example.com/html;
index index.html;
access_log /var/log/nginx/tools.example.com.access.log;
error_log /var/log/nginx/tools.example.com.error.log;
location / {
try_files $uri $uri/ =404;
}
}
The default catch-all
If a request arrives with a Host that matches no block, nginx serves the first block it finds for that port, which is whatever sorts first alphabetically in sites-enabled/. I would rather it return nothing. Edit /etc/nginx/sites-available/default to be a bare block with default_server and the special return 444, which closes the connection without a response:
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
return 444;
}
Only one block per listen address/port can carry default_server, otherwise nginx refuses to start.
Enable and reload
sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
sudo ln -s /etc/nginx/sites-available/tools.example.com /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
nginx -t must print syntax is ok and test is successful before you reload. A reload with a broken config leaves the old worker processes running, but it is still better to catch it first.
Testing it
From your workstation, force the Host header with curl so DNS does not have to be in place yet:
curl -H 'Host: example.com' http://203.0.113.10/
curl -H 'Host: tools.example.com' http://203.0.113.10/
curl -H 'Host: nothing.example.net' http://203.0.113.10/
The first two should return the matching <h1>; the third should fail with curl: (52) Empty reply from server, which is the 444 doing its job. On the server, tail -f /var/log/nginx/example.com.access.log while you make requests confirms the right log is being written.
When it goes wrong
nginx: [emerg] could not build server_names_hash, you should increase server_names_hash_bucket_size: 32: a long hostname exceeded the default bucket. Addserver_names_hash_bucket_size 64;inside thehttp {}block of/etc/nginx/nginx.conf.nginx: [emerg] a duplicate default server for 0.0.0.0:80: two blocks claimdefault_server. Remove it from one of them.- The wrong site loads for a hostname: usually a typo in
server_name, or the symlink points at an old file.nginx -Tdumps the whole effective configuration so you can search it. 403 Forbiddenwithdirectory index of "/var/www/..." is forbiddenin the error log: there is noindex.htmland autoindex is off, or thewww-datauser cannot traverse the directory. Checkls -ldon every path component.
Adding a site later is one file, one symlink, one reload. Good enough for me.