Your First Virtual Host
Introduction
A virtual host is one server { } block: a hostname, a document root, and rules for URL paths. This chapter walks through creating a site from scratch—HTML on disk, config in sites-available, symlink in sites-enabled, and verification with curl. By the end, you will have a repeatable pattern for every new project.
Prerequisites
- Nginx Configuration Syntax
- Installing Nginx and Directory Layout
- Working Nginx install with
sudoaccess
What You Will Build
| Item | Value |
|---|---|
| Hostname (local test) | demo.local |
| Web root | /var/www/demo |
| Config file | /etc/nginx/sites-available/demo |
| Content | Single index.html page |
Step 1: Create the Web Root and Page
sudo mkdir -p /var/www/demoCreate /var/www/demo/index.html:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Demo Virtual Host</title>
</head>
<body>
<h1>Hello from demo.local</h1>
<p>This page is served by your first Nginx virtual host.</p>
</body>
</html>Write with a text editor or:
sudo tee /var/www/demo/index.html > /dev/null <<'EOF'
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Demo Virtual Host</title>
</head>
<body>
<h1>Hello from demo.local</h1>
</body>
</html>
EOFSet permissions so Nginx can read files:
sudo chown -R www-data:www-data /var/www/demo
sudo find /var/www/demo -type d -exec chmod 755 {} \;
sudo find /var/www/demo -type f -exec chmod 644 {} \;Code explanation:
- Nginx workers run as
www-dataon Debian/Ubuntu - Directories need execute bit (
755) for traversal; files need read (644)
Step 2: Write the server Block
Create /etc/nginx/sites-available/demo:
Code explanation:
server_name demo.local— matches when theHostheader isdemo.localtry_files— serve file, then directory index, else 404- Separate logs make debugging this site easier on a shared server
Save with sudo nano /etc/nginx/sites-available/demo or your preferred editor.
Step 3: Enable the Site
sudo ln -sf /etc/nginx/sites-available/demo /etc/nginx/sites-enabled/demo
sudo nginx -t
sudo systemctl reload nginxCode explanation:
- Symlink activates the config via
include /etc/nginx/sites-enabled/* - Never skip
nginx -tbefore reload
Tip
Disable the Default Site Optional
Ubuntu’s default site may still answer for localhost. For a clean test of demo.local, disable it:
sudo rm /etc/nginx/sites-enabled/default
sudo nginx -t && sudo systemctl reload nginxStep 4: Point demo.local to Your Machine
On Linux / WSL
grep demo.local /etc/hosts || echo '127.0.0.1 demo.local' | sudo tee -a /etc/hostsOn macOS
Edit /etc/hosts as root and add:
127.0.0.1 demo.localCode explanation:
/etc/hostsmaps a name to an IP before public DNS- Production uses real DNS A/AAAA records instead
Step 5: Verify
curl -I -H "Host: demo.local" http://127.0.0.1/
curl http://demo.local/Expected:
HTTP/1.1 200 OK
...Browser: open http://demo.local/ and see your heading.
Check logs if something fails:
sudo tail -n 20 /var/log/nginx/demo.error.log
sudo tail -n 20 /var/log/nginx/demo.access.logAlternative: conf.d Style
Same server block in /etc/nginx/conf.d/demo.conf works on many distros—no symlink step:
sudo cp /etc/nginx/sites-available/demo /etc/nginx/conf.d/demo.conf
sudo nginx -t && sudo systemctl reload nginxPick one layout (sites-available or conf.d) per server to avoid loading the same server twice.
Troubleshooting
| Symptom | Check |
|---|---|
| Default Nginx page instead of yours | Wrong server_name; request Host header; disable default site |
| 403 Forbidden | Permissions on /var/www/demo; SELinux on RHEL |
| 404 Not Found | root path wrong; index.html missing |
nginx -t fails | Typo in config; duplicate listen conflicts |
| Browser cannot resolve demo.local | /etc/hosts entry missing |
Test which server block Nginx selects:
curl -v -H "Host: demo.local" http://127.0.0.1/ 2>&1 | grep -E '< HTTP|< Server'Project Layout Reference
/var/www/demo/
└── index.html
/etc/nginx/sites-available/demo
/etc/nginx/sites-enabled/demo → symlink
/var/log/nginx/demo.access.log
/var/log/nginx/demo.error.logWhat Comes Next in This Track
Later chapters add static SPA routing, gzip, reverse proxy to Java/Node, and HTTPS—each extends this same server block pattern.
FAQ
Can I use an IP address in server_name?
You can use the IP as server_name, but hostname-based virtual hosts are standard. For IP-only access, default_server on listen is common.
How many sites on one server?
Many—each gets its own server { } with distinct server_name (or listen options).
Do I delete sites-available when disabling?
Remove the sites-enabled symlink only; keep the file in sites-available for easy re-enable.
Why try_files instead of only index?
try_files also serves other static files (about.html, assets) and returns 404 when missing.
Can I test without editing /etc/hosts?
Yes: curl -H "Host: demo.local" http://127.0.0.1/ sends the correct Host header.
Same config for production domain?
Replace demo.local with example.com, point DNS to your VPS IP, and add HTTPS in a later chapter.