One Linux VPS can serve many websites. Nginx decides which site to show by reading the domain name the browser asks for and matching it to a server block, which is Nginx's name for a virtual host. This guide sets up two sites, example.com and example.net, each with its own folder, config file and logs.
Before you start
- A Linux VPS running Ubuntu 22.04 or 24.04, Debian 12, or AlmaLinux/Rocky Linux 8 or 9.
- Nginx installed and running. If it is not, follow Install Nginx on Linux first.
- A sudo user and an SSH session to the server.
- DNS A records for each domain (and its
wwwname) pointing at your server's IP, for example203.0.113.10. See Point a domain to your VPS.
How Nginx picks a site
When a request arrives, Nginx looks at the port it came in on and the Host header the browser sent. It then compares that host with the server_name of every server block listening on that port. The block that matches serves the request.
If nothing matches, Nginx uses the default server for that port. That is why a fresh install shows the "Welcome to nginx!" page for any domain you point at it.
Where server blocks live depends on the distribution:
- Ubuntu and Debian: files in
/etc/nginx/sites-available/, switched on with a symlink in/etc/nginx/sites-enabled/. - AlmaLinux and Rocky Linux: any file ending in
.confinside/etc/nginx/conf.d/is loaded automatically.
Create a folder for each site
Give every site its own web root. Keeping them under /var/www makes backups and permissions simple.
sudo mkdir -p /var/www/example.com/public /var/www/example.net/publicAdd a test page to each so you can tell them apart:
echo '<h1>example.com works</h1>' | sudo tee /var/www/example.com/public/index.html
echo '<h1>example.net works</h1>' | sudo tee /var/www/example.net/public/index.htmlSet ownership so your deploy user can upload files and Nginx can read them:
sudo chown -R deploy:deploy /var/www/example.com /var/www/example.net
sudo chmod -R 755 /var/www/example.com /var/www/example.netReplace deploy with your own username.
Write the server block
On Ubuntu or Debian, create /etc/nginx/sites-available/example.com. On AlmaLinux or Rocky Linux, create /etc/nginx/conf.d/example.com.conf instead.
sudo nano /etc/nginx/sites-available/example.comPaste this:
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
root /var/www/example.com/public;
index index.html index.htm index.php;
access_log /var/log/nginx/example.com.access.log;
error_log /var/log/nginx/example.com.error.log;
location / {
try_files $uri $uri/ =404;
}
}What each part does:
listen 80andlisten [::]:80accept plain HTTP on IPv4 and IPv6.server_namelists every hostname this site answers to. Separate names with spaces.rootis the folder Nginx serves files from.try_filesreturns the file if it exists, then a folder, then a 404.- Separate log files make it easy to debug one site without wading through the others.
Repeat the file for the second site, changing every example.com to example.net.
Note: Two server blocks must not list the same name in server_name. Nginx warns about a "conflicting server name" and ignores the second one.
Enable the sites
On Ubuntu and Debian, link each file into sites-enabled:
sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/
sudo ln -s /etc/nginx/sites-available/example.net /etc/nginx/sites-enabled/To switch a site off later, delete only the link in sites-enabled. The original stays in sites-available.
On AlmaLinux and Rocky Linux there is no extra step. Files in conf.d ending in .conf are loaded automatically. On those systems, also make sure SELinux allows Nginx to read your web root:
sudo restorecon -Rv /var/wwwTest and reload
Always check the syntax before applying a change:
sudo nginx -tYou want both lines to say ok and successful:
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successfulThen reload. A reload keeps current connections open and loads the new config:
sudo systemctl reload nginxIf nginx -t reports an error, it names the file and line. Fix it and run the test again before reloading. More on this in Test and reload Nginx config without downtime.
Check it works
Ask Nginx for each site by name, from the server itself. This works even before DNS has updated:
curl -H "Host: example.com" http://127.0.0.1/
curl -H "Host: example.net" http://127.0.0.1/Each should print its own test heading. Once DNS points at the server, open both domains in a browser. To see which config files Nginx actually loaded, list every server_name it knows:
sudo nginx -T | grep -E "server_name|# configuration file"Add HTTPS
Each site needs its own certificate, or one certificate that covers all its names. With Certbot you can request one per site:
sudo certbot --nginx -d example.com -d www.example.comCertbot edits the matching server block and adds the SSL settings. The full walkthrough is in Free SSL for Nginx with Let's Encrypt.
Troubleshooting
- The welcome page appears instead of your site: the
server_namedoes not match the domain you typed, or the site is not enabled. Check the symlink and see Fix Nginx showing the default page. - Both domains show the same site: one domain is missing from every
server_name, so it falls through to the default server. Add it to the right block and reload. - 403 Forbidden: there is no
index.htmlin the root, or Nginx cannot read the folder. Check permissions withnamei -l /var/www/example.com/public/index.html. nginx -tsays "conflicting server name": the same hostname appears in two files. Remove it from one.- Site works by IP but not by name: DNS is not pointing at the server yet. Check with
dig +short example.com.
Next steps
- Send every visitor to one hostname: Redirect www to non-www in Nginx.
- Run a PHP or Node.js app behind one of the sites: Set up Nginx as a reverse proxy.
