Nginx Virtual Hosts: Hosting Multiple Websites on One Server
Unlock the power of Nginx virtual hosts (server blocks) to efficiently host multiple websites or subdomains on a single server. This guide provides a comprehensive, step-by-step tutorial, covering directory setup, configuration file creation, enabling server blocks, and Nginx testing. Learn best practices for subdomains, default server blocks, HTTPS integration, and dedicated logging. Practical examples and essential troubleshooting tips will help you master multi-site Nginx hosting, optimizing resource usage and streamlining web server management.
Nginx Virtual Hosts: Hosting Multiple Websites on One Server
Running several small sites from one server is a normal Nginx job. You might have a company site, a docs site, a staging app, and a customer portal all landing on the same machine. Nginx separates them with server blocks. Apache people often call the same idea virtual hosts, so you will see both terms used in tutorials and hosting panels.
The important part is simple: Nginx looks at the port, IP address, and Host header, then chooses the matching server block. If the wrong site loads, the problem is usually not mysterious. It is often a missing DNS record, a typo in server_name, a default server catching the request, or two files claiming the same name.
Understanding Nginx Server Blocks (Virtual Hosts)
At its core, an Nginx server block is a configuration directive defined within the Nginx configuration file (nginx.conf or included files). Each server block defines the configuration for a specific virtual host, dictating how Nginx should respond to requests for a particular domain or set of domains. Nginx uses the listen directive to specify the IP address and port it should listen on, and the server_name directive to identify which domain names or hostnames this server block should respond to.
When a request comes in, Nginx examines the Host header of the HTTP request and compares it against the server_name directives of its configured server blocks. It then serves the content defined in the matching server block. If no server_name matches, Nginx typically falls back to the default server block (the first server block or one explicitly marked as default_server).
Prerequisites
Before you begin, ensure you have the following:
- Nginx Installed: Nginx should be installed and running on your server. If not, you can usually install it via your system's package manager (e.g.,
sudo apt update && sudo apt install nginxon Ubuntu/Debian,sudo yum install nginxon CentOS/RHEL). - Domain Names: You need at least two domain names (e.g.,
example1.comandexample2.com) or subdomains (e.g.,blog.example.comandapp.example.com) that you want to host. These domains' DNS A/AAAA records must point to your server's public IP address. - Basic Directory Structure: A plan for where your website files will reside. A common practice is
/var/www/yourdomain.com/html. - Sudo Privileges: You'll need
sudoaccess to modify Nginx configuration files.
Step-by-Step Setup Guide
Let's set up two virtual hosts: example1.com and example2.com.
Step 1: Create Directory Structure for Websites
First, create root directories for each of your websites. This is where their HTML, CSS, JavaScript, and other static files will be stored. A common location is /var/www/.
sudo mkdir -p /var/www/example1.com/html
sudo mkdir -p /var/www/example2.com/html
# Set ownership to your user (replace $USER with your username) to allow editing
sudo chown -R $USER:$USER /var/www/example1.com/html
sudo chown -R $USER:$USER /var/www/example2.com/html
# Set read permissions for the web server
sudo chmod -R 755 /var/www
Next, create a simple index.html file in each directory to test the setup:
For /var/www/example1.com/html/index.html:
<!-- /var/www/example1.com/html/index.html -->
<!DOCTYPE html>
<html>
<head>
<title>Welcome to Example1.com!</title>
</head>
<body>
<h1>Success! This is Example1.com.</h1>
<p>This virtual host is working correctly.</p>
</body>
</html>
For /var/www/example2.com/html/index.html:
<!-- /var/www/example2.com/html/index.html -->
<!DOCTYPE html>
<html>
<head>
<title>Welcome to Example2.com!</title>
</head>
<body>
<h1>Success! This is Example2.com.</h1>
<p>This virtual host is also working!</p>
</body>
</html>
Step 2: Create Nginx Server Block Configuration Files
Nginx typically loads server block configurations from files in the /etc/nginx/sites-enabled/ directory. These files are usually symlinks to configurations stored in /etc/nginx/sites-available/. This separation allows you to store configurations that are not yet active or to easily enable/disable sites.
Create a new configuration file for example1.com:
sudo nano /etc/nginx/sites-available/example1.com.conf
Add the following content:
# /etc/nginx/sites-available/example1.com.conf
server {
listen 80;
listen [::]:80;
root /var/www/example1.com/html;
index index.html index.htm index.nginx-debian.html;
server_name example1.com www.example1.com;
location / {
try_files $uri $uri/ =404;
}
access_log /var/log/nginx/example1.com_access.log;
error_log /var/log/nginx/example1.com_error.log;
}
Explanation of Directives:
listen 80;: Nginx listens on port 80 (standard HTTP).listen [::]:80;is for IPv6.root /var/www/example1.com/html;: Specifies the document root for this server block. Nginx will look for files within this directory.index index.html ...;: Defines the default file Nginx should serve when a directory is requested (e.g., when someone visitsexample1.com/).server_name example1.com www.example1.com;: This is crucial. It tells Nginx to respond to requests forexample1.comorwww.example1.comusing this server block's configuration.location / { ... }: A block defining how to handle requests for specific URIs.try_filesattempts to serve a file directly ($uri), then a directory ($uri/), and finally returns a404 Not Founderror.access_loganderror_log: Specifies separate log files for this specific site, which is a good practice for easier debugging and analytics.
Now, create a similar configuration file for example2.com:
sudo nano /etc/nginx/sites-available/example2.com.conf
Add the following content:
# /etc/nginx/sites-available/example2.com.conf
server {
listen 80;
listen [::]:80;
root /var/www/example2.com/html;
index index.html index.htm index.nginx-debian.html;
server_name example2.com www.example2.com;
location / {
try_files $uri $uri/ =404;
}
access_log /var/log/nginx/example2.com_access.log;
error_log /var/log/nginx/example2.com_error.log;
}
Step 3: Enable Server Blocks
To enable these configurations, create symbolic links from the sites-available directory to the sites-enabled directory. This tells Nginx to include these files when it starts up.
sudo ln -s /etc/nginx/sites-available/example1.com.conf /etc/nginx/sites-enabled/
sudo ln -s /etc/nginx/sites-available/example2.com.conf /etc/nginx/sites-enabled/
Step 4: Test Nginx Configuration
It's crucial to test your Nginx configuration for syntax errors before reloading. This prevents Nginx from failing to restart due to a typo.
sudo nginx -t
You should see output similar to this, indicating success:
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
If you see any errors, fix them in the respective configuration files and re-run sudo nginx -t until it passes.
Step 5: Restart Nginx
Apply the new configuration by restarting or reloading Nginx. reload is generally preferred as it allows Nginx to load new configurations without dropping active connections.
sudo systemctl reload nginx
# Or, if reload doesn't work or for fresh installations:
sudo systemctl restart nginx
Step 6: Update DNS Records
Ensure that the DNS A records for example1.com, www.example1.com, example2.com, and www.example2.com all point to the IP address of your Nginx server. Without correct DNS entries, your browser won't know where to find your websites.
Once DNS propagation completes (which can take a few minutes to several hours), you should be able to visit http://example1.com and http://example2.com in your web browser and see the respective index.html pages.
Advanced Scenarios and Best Practices
Hosting Subdomains
Hosting subdomains (e.g., blog.example.com, shop.example.com) works exactly like hosting separate domains. You just define a new server block with the subdomain as the server_name.
Example for blog.example.com:
# /etc/nginx/sites-available/blog.example.com.conf
server {
listen 80;
listen [::]:80;
root /var/www/blog.example.com/html;
index index.html;
server_name blog.example.com;
location / {
try_files $uri $uri/ =404;
}
}
Remember to create the directory (/var/www/blog.example.com/html), create an index.html, create the symlink, and reload Nginx.
The Default Server Block
It's good practice to have a default server block that catches requests for domain names that don't match any other server_name directive on your server. This prevents unknown requests from being served by the "first" virtual host Nginx finds, or allows you to serve a generic "site not found" page.
Typically, the first server block in your nginx.conf or sites-enabled is implicitly the default. You can explicitly set one using default_server:
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
# The underscore `_` is a non-existent domain name that will never match a real request.
# You can also use localhost.
root /var/www/default_site/html;
index index.html;
location / {
return 444; # Return a Nginx-specific 444 error (no response) for unknown hosts
# Or, serve a generic landing page:
# try_files $uri $uri/ =404;
}
}
Warning: If you define a default_server block, make sure only one server block on a given listen port has the default_server flag, otherwise Nginx will log a warning.
Securing Virtual Hosts with HTTPS (SSL/TLS)
For production websites, enabling HTTPS is essential. This involves obtaining an SSL/TLS certificate (e.g., via Let's Encrypt using Certbot) and configuring Nginx to listen on port 443 with the certificate.
A typical HTTPS server block looks like this (after obtaining certificates):
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name example1.com www.example1.com;
root /var/www/example1.com/html;
index index.html;
ssl_certificate /etc/letsencrypt/live/example1.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example1.com/privkey.pem;
# Include other SSL configurations (ciphers, protocols, etc.)
include /etc/nginx/snippets/ssl-params.conf;
location / {
try_files $uri $uri/ =404;
}
}
# Optional: HTTP to HTTPS redirect for this domain
server {
listen 80;
listen [::]:80;
server_name example1.com www.example1.com;
return 301 https://$host$request_uri;
}
It's common to have a separate HTTP server block whose sole purpose is to redirect all traffic to its HTTPS counterpart.
If you use Certbot, it may create or edit these blocks for you. That is convenient, but you should still read the resulting file. Automated certificate tools sometimes add redirect logic in a place you would not have chosen yourself, and duplicated redirects can make troubleshooting harder.
Logging for Each Site
As shown in the examples, dedicating separate access_log and error_log files for each virtual host is a best practice. This makes it significantly easier to debug issues and analyze traffic for individual websites without sifting through combined logs.
Configuration File Structure
For larger deployments, consider organizing your Nginx configuration files like this:
nginx.conf: Main configuration, includesconf.d/*.confandsites-enabled/*.conf.d/: General server-wide settings (e.g., Gzip, caching).snippets/: Reusable Nginx configuration snippets (e.g., SSL parameters, commonlocationblocks).sites-available/: Individualserverblocks for each website.sites-enabled/: Symbolic links to active configurations insites-available/.
Troubleshooting Common Issues
- 403 Forbidden Error: This usually means Nginx doesn't have read access to your website's files or directories. Double-check file and directory permissions (e.g.,
sudo chmod -R 755 /var/www/yourdomain.com/htmland ensure the Nginx user, typicallywww-dataornginx, can read them). - 404 Not Found Error: Verify that the
rootdirective in your server block points to the correct directory and that yourindex.htmlfile exists in that location. Also, ensuretry_filesis correctly configured. - Wrong Site is Loading: This often indicates an issue with the
server_namedirective. Ensure theserver_nameexactly matches the domain name you're trying to access (includingwww.or subdomains). Also, check your DNS records. - Nginx Fails to Start/Reload: Always use
sudo nginx -tto test your configuration before attempting to reload or restart Nginx. Error messages will pinpoint the line and file where the syntax error occurred. - DNS Issues: If you can access your site by IP address but not by domain name, it's almost certainly a DNS problem. Use
digornslookupto verify your domain's A records point to the correct server IP.
Testing Before DNS Is Ready
You do not have to wait for public DNS to test the Nginx side. You can send a request with a custom Host header:
curl -H "Host: example1.com" http://203.0.113.10/
curl -H "Host: example2.com" http://203.0.113.10/
Replace 203.0.113.10 with your server IP. If each command returns the right test page, the server block matching is working. If both commands return the same page, check whether both files are enabled, whether server_name is correct, and whether a default block is catching the request.
For HTTPS, the test is a little different because TLS uses SNI before the HTTP Host header is processed:
curl --resolve example1.com:443:203.0.113.10 https://example1.com/
That command tells curl to connect to your server IP while still using example1.com for TLS and HTTP. It is one of the quickest ways to test a new HTTPS virtual host before changing DNS.
A Maintainable Multi-Site Pattern
For a handful of static sites, the examples above are enough. Once you host several applications, repeat less and centralize only the parts that are genuinely shared. For example, put common security headers, compression, and SSL parameters in snippets, but keep each site's root, server_name, upstream, and logs visible in its own file.
Avoid copying a large production block from one domain to another without reading every line. That is how server_name mistakes, wrong certificate paths, and shared log files creep in. A practical review checklist is short:
- Does
server_nameinclude every hostname users will type? - Does the
rootorproxy_passpoint to this site, not the previous one? - Are access and error logs separated enough to debug this site alone?
- Does
nginx -tpass before reload? - Does
curl -H "Host: ..."orcurl --resolvereturn the expected site?
Final Notes
Nginx virtual hosts are dependable when each site has a clear server block, a correct server_name, and a predictable default fallback. Keep the files boring. Test every change before reload. Use dedicated logs when the sites matter. Most multi-site Nginx problems become easy to solve once you can prove whether DNS, TLS/SNI, or server block matching is the part that failed.