Nginx server blocks let one Nginx service host multiple domains, subdomains, or applications from separate configuration blocks. Apache users often call this virtual hosting; Nginx documentation usually calls each matching block a virtual server.
The important part is selection order. Nginx first matches the request against the listening address and port, then checks the request Host header against server_name. If no name matches, Nginx uses the default server for that address and port.
Work from one static HTTP server block first, then add multiple domains, a default catch-all, HTTPS, and application proxying after each layer passes nginx -t and a local host-header probe. That sequence keeps wrong-site responses, Nginx welcome pages, and 404 errors tied to the layer that caused them.
Understand Nginx Server Blocks and Virtual Hosts
A server block is a server { ... } context inside Nginx’s http configuration. Each server block normally defines a listener, one or more hostnames, and the action Nginx should take for requests that land there.
These directives appear in most Nginx virtual host files.
| Directive | Context | Purpose |
|---|---|---|
server | http | Creates a virtual server that handles requests for a matching address, port, and hostname. |
listen | server | Sets the IP address, port, socket, and optional default_server status for the server block. |
server_name | server | Defines the hostnames that should select this server block. |
root | http, server, location | Sets the filesystem directory used to build paths for requested files. |
index | http, server, location | Defines default index files such as index.html or index.php. |
location | server, nested location | Matches URI paths and applies routing, file-serving, proxy, or FastCGI behavior. |
try_files | server, location | Checks possible file paths in order, then falls back to a URI or status code. |
How Nginx Chooses a Server Block
Nginx request processing is not based on file order alone. The selection flow works like this:
- Nginx first checks the local address and port from the
listendirectives. - Among server blocks that match that address and port, Nginx checks the request
Hostheader againstserver_name. - If no
server_namematches, Nginx uses the default server for that listening address and port. - If you do not mark a server with
default_server, Nginx uses the first server block for that listening address and port as the default.
The default_server parameter belongs to the listen directive. It is not a property of the domain name. This matters when you have separate IPv4, IPv6, HTTP, HTTPS, or custom-port server blocks.
How Nginx Matches Server Names
When several names can match the same request, Nginx uses a documented precedence order. Exact names win first, followed by the longest wildcard starting with an asterisk, the longest wildcard ending with an asterisk, and then the first matching regular expression in configuration order.
| Server Name Type | Example | When To Use It |
|---|---|---|
| Exact name | example.com www.example.com | Normal websites and most production virtual hosts. |
| Leading wildcard | *.example.com | Subdomains such as app.example.com and cdn.example.com. |
| Trailing wildcard | mail.* | Less common patterns that match several suffixes. |
| Regular expression | ~^(?<user>.+)\.example\.com$ | Advanced dynamic matching where exact or wildcard names are not enough. |
Use exact names unless you genuinely need wildcard or regex matching. They are clearer, faster to audit, and less likely to catch a hostname that should belong to another server block.
Check the Nginx Include Layout
Nginx package layouts differ by distribution and package source. Debian and Ubuntu packages commonly use /etc/nginx/sites-available/ and /etc/nginx/sites-enabled/. Fedora, RHEL, Rocky Linux, and nginx.org packages commonly include /etc/nginx/conf.d/*.conf.
Check which include pattern your Nginx configuration loads before creating a site file:
sudo nginx -T 2>/dev/null | grep -E 'include .*(sites-enabled|conf\.d)'
Relevant output may show one or both include layouts:
include /etc/nginx/conf.d/*.conf; include /etc/nginx/sites-enabled/*;
Do not copy a Debian-style
sites-availableworkflow onto a server that only loadsconf.d. The file can exist on disk and still do nothing if Nginx never includes it.
Create an Nginx Server Block for a Static Site
The cleanest first server block is a simple static site. Once the hostname, root path, and matching behavior work, you can add PHP, proxying, redirects, or HTTPS without debugging every layer at once.
Create the Nginx Site Directory
Create a document root for the first site. Replace example.com with your real domain throughout the commands and configuration.
sudo mkdir -p /var/www/example.com/public_html
printf '%s\n' 'Example Domain via Nginx' | sudo tee /var/www/example.com/public_html/index.html > /dev/null
Make the directories searchable and the test file readable by Nginx:
sudo find /var/www/example.com -type d -exec chmod 755 {} \;
sudo find /var/www/example.com -type f -exec chmod 644 {} \;
Check the test file before editing Nginx:
cat /var/www/example.com/public_html/index.html
Expected output:
Example Domain via Nginx
Create the Nginx Server Block File
On Debian and Ubuntu package layouts, create the site file under sites-available:
sudo nano /etc/nginx/sites-available/example.com
On Fedora, RHEL, Rocky Linux, nginx.org packages, and other conf.d layouts, create the file under conf.d instead:
sudo nano /etc/nginx/conf.d/example.com.conf
Add this basic HTTP server block:
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
root /var/www/example.com/public_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 root directive points to the site directory. The try_files line tells Nginx to try the exact URI path, then a directory index path, then return 404 if neither exists. That behavior is correct for a normal static website.
If you created the file in sites-available, enable it with a symlink:
sudo ln -s /etc/nginx/sites-available/example.com /etc/nginx/sites-enabled/example.com
If you created the file in conf.d, no symlink is required because Nginx normally loads matching .conf files from that directory directly.
Test and Reload the Nginx Server Block
Test the complete Nginx configuration before applying the new virtual host:
sudo nginx -t
A successful syntax test prints:
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok nginx: configuration file /etc/nginx/nginx.conf test is successful
Reload Nginx after the syntax test passes:
sudo systemctl reload nginx
The reload command normally returns no output on success. Check that Nginx is still active:
systemctl is-active nginx
Expected output:
active
Verify the Nginx Server Block Locally
Test the new server block by sending the expected host header to localhost:
curl -H "Host: example.com" http://127.0.0.1/
Expected output:
Example Domain via Nginx
Then check the response headers:
curl -I -H "Host: example.com" http://127.0.0.1/
Relevant headers include:
HTTP/1.1 200 OK Server: nginx
If the local host-header test works but the public domain still fails, check DNS, firewall rules, cloud security groups, CDN proxy settings, or any hosting panel that sits in front of the origin server.
Configure Multiple Nginx Server Blocks
Multiple Nginx server blocks can share the same listen 80 socket as long as their server_name values are different. This is the normal way to host several static sites or applications on one server.
Create a second site root and test file:
sudo mkdir -p /var/www/example.net/public_html
printf '%s\n' 'Second Domain via Nginx' | sudo tee /var/www/example.net/public_html/index.html > /dev/null
sudo find /var/www/example.net -type d -exec chmod 755 {} \;
sudo find /var/www/example.net -type f -exec chmod 644 {} \;
Add a second server block in its own file, such as /etc/nginx/sites-available/example.net or /etc/nginx/conf.d/example.net.conf:
server {
listen 80;
listen [::]:80;
server_name example.net www.example.net;
root /var/www/example.net/public_html;
index index.html index.htm;
access_log /var/log/nginx/example.net.access.log;
error_log /var/log/nginx/example.net.error.log;
location / {
try_files $uri $uri/ =404;
}
}
Enable the second file if your layout uses sites-available and sites-enabled:
sudo ln -s /etc/nginx/sites-available/example.net /etc/nginx/sites-enabled/example.net
Test and reload Nginx:
sudo nginx -t && sudo systemctl reload nginx
Verify each virtual host with a separate host-header request:
curl -H "Host: example.com" http://127.0.0.1/
curl -H "Host: example.net" http://127.0.0.1/
Expected output from the two commands should show different site content:
Example Domain via Nginx Second Domain via Nginx
If both commands return the same site, the hostname did not match the server block you expected. Check the server_name values, duplicate names, and which file is marked as the default server.
Configure a Default Nginx Server Block
A default server block catches requests that arrive on an address and port without a matching server_name. Without an explicit default, the first matching server block for that socket becomes the fallback, which can expose the wrong site to unknown hostnames.
Create a small catch-all file, such as /etc/nginx/conf.d/00-default.conf or /etc/nginx/sites-available/00-default, depending on your layout:
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
return 404;
}
The server_name _; value is not magic. It is just an intentionally unused name. The default_server parameter on listen is what makes this block the fallback for unmatched HTTP requests.
Only one server block can be the
default_serverfor the same address and port. If another file already haslisten 80 default_server;, remove the duplicate before reloading Nginx.
For HTTPS, the fallback server needs its own valid certificate, usually a certificate for a real fallback hostname or a wildcard certificate. Do not point unmatched HTTPS traffic at a random site’s certificate unless that is an intentional edge design.
Add HTTPS to Nginx Server Blocks
Add HTTPS only after the HTTP server block works. That keeps certificate issuance, redirects, and application troubleshooting separate from basic host matching.
Use the dedicated Let’s Encrypt guides when you need distro-specific Certbot installation and renewal details:
After certificates exist, a typical HTTPS version of the static server block looks like this:
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
return 301 https://example.com$request_uri;
}
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name example.com www.example.com;
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
root /var/www/example.com/public_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 HTTP block redirects both listed hostnames to the canonical HTTPS hostname, while the HTTPS block serves the site. The fixed hostname avoids reflecting an arbitrary Host header if this block ever becomes the fallback. If you need to preserve both apex and www hosts, create separate HTTP redirect blocks or make sure a separate default server handles unmatched names first. For more redirect patterns, use the Nginx redirect rules guide.
HTTP/2 syntax differs by Nginx version. On Nginx 1.25.1 and newer, use
http2 on;inside the HTTPS server block when supported by your build. Older packaged builds may still uselisten 443 ssl http2;. Runsudo nginx -tbefore reloading.
Use Nginx Server Blocks for Applications
A server block can also route a domain to an application backend instead of a static document root. This is the common pattern for Node.js, Python, Go, Ruby, API services, and dashboards that listen on localhost or a private port.
For a simple local backend on port 3000, use a reverse proxy server block:
server {
listen 80;
listen [::]:80;
server_name app.example.com;
access_log /var/log/nginx/app.example.com.access.log;
error_log /var/log/nginx/app.example.com.error.log;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Check the backend directly before blaming the server block:
curl http://127.0.0.1:3000
If the direct backend request fails, fix the application listener first. If the backend works directly but fails through Nginx, use the Nginx reverse proxy guide or the Nginx 502 Bad Gateway troubleshooting guide for upstream, socket, timeout, and header issues.
Manage Nginx Server Block Files Safely
Use one server block file per site or application unless the blocks are tightly related, such as an HTTP redirect block paired with its HTTPS server block. Domain-based filenames make Nginx easier to audit six months later.
List enabled Debian-style server block symlinks:
ls -l /etc/nginx/sites-enabled/
List conf.d server block files:
ls -l /etc/nginx/conf.d/
Disable a Debian-style site by removing only the enabled symlink:
sudo unlink /etc/nginx/sites-enabled/example.com
sudo nginx -t && sudo systemctl reload nginx
Disable a conf.d site by moving the file out of the included .conf pattern:
sudo mv /etc/nginx/conf.d/example.com.conf /etc/nginx/conf.d/example.com.conf.disabled
sudo nginx -t && sudo systemctl reload nginx
To re-enable the conf.d site, move it back to a .conf filename and reload after a syntax test.
Troubleshoot Common Nginx Server Block Problems
Server block problems usually come from one of five layers: the file is not included, the host name does not match, the wrong block is default, the filesystem path is wrong, or another layer such as DNS, a CDN, container mapping, or a hosting panel owns the public request.
| Symptom | Likely Cause | First Check |
|---|---|---|
| Nginx welcome page appears | The request reached the default server block instead of your site. | curl -H "Host: example.com" http://127.0.0.1/ |
| Both domains show the same site | Duplicate or missing server_name, or one block is catching unmatched traffic. | sudo nginx -T and search for the hostname. |
nginx -t reports duplicate default server | More than one block has default_server on the same socket. | Search for default_server across Nginx configs. |
| 404 from the correct hostname | The server block matched, but root, index, or try_files points to the wrong path. | Check the site root and error log. |
| Works locally but not publicly | DNS, firewall, cloud security group, CDN, proxy manager, or load balancer issue. | Compare localhost, server public IP, and external domain tests. |
Fix Nginx Server Block Not Being Included
If Nginx does not include the file, the server block cannot match any request. Check the active configuration dump for your domain:
sudo nginx -T 2>/dev/null | grep -F "server_name example.com"
Relevant output should include your server block:
server_name example.com www.example.com;
If there is no output, check that the file lives under a directory included by nginx.conf. On Debian and Ubuntu, also check that sites-enabled contains a symlink to the file in sites-available.
Fix Nginx Showing the Default Welcome Page
The welcome page usually means the request landed in the packaged default server block. Test the hostname directly against the local Nginx listener:
curl -I -H "Host: example.com" http://127.0.0.1/
If that returns the correct site locally, Nginx server block matching is working and the public problem is outside the server block. Check DNS, CDN proxying, firewall rules, or an upstream load balancer. If localhost still returns the welcome page, inspect the active server names and disable or demote the packaged default block after confirming it is not serving another site.
Fix Duplicate Nginx default_server Errors
A duplicate default server error means two server blocks both claim the fallback role for the same address and port.
The error usually looks like this:
nginx: [emerg] a duplicate default server for 0.0.0.0:80 in /etc/nginx/conf.d/example.com.conf:2 nginx: configuration file /etc/nginx/nginx.conf test failed
Search the Nginx configuration tree for every default_server entry. Use a file search here because nginx -T can stop before dumping the full configuration when the duplicate listener makes the config invalid:
sudo grep -R --line-number "default_server" /etc/nginx/ 2>/dev/null
Keep default_server on only one server block for each exact address and port combination. For example, one IPv4 default on listen 80 default_server; and one IPv6 default on listen [::]:80 default_server; is normal, but two IPv4 defaults on 0.0.0.0:80 is not.
Fix Nginx 404 Errors in a Server Block
If the correct server block answers but returns 404, check the filesystem path that root and try_files create.
sudo namei -l /var/www/example.com/public_html/index.html
sudo tail -n 20 /var/log/nginx/example.com.error.log
A missing file usually appears in the error log as a failed open path. The path shown in the log is the path Nginx actually tried. Fix the root directive or move the file to match the configured document root. The Nginx access and error logs guide can help when the log path or message needs deeper inspection.
For path-mapping differences between root and alias, use the Nginx root vs alias guide. For deeper 404 diagnosis across static files, PHP front controllers, and proxied apps, use the Nginx 404 Not Found troubleshooting guide.
If the hostname matches but only certain paths reach the wrong handler, troubleshoot Nginx location block priority before changing the server block itself.
Fix SELinux Blocking Nginx Site Files
On SELinux-enabled Fedora, RHEL, CentOS Stream, Rocky Linux, and AlmaLinux servers, Nginx may fail to read files that have the wrong SELinux context. This is more likely when the site root is outside the normal web directories.
For files under /var/www, restore the default labels first:
sudo restorecon -Rv /var/www/example.com
For a custom root such as /srv/www/example.com, label it as readable web content, then restore contexts:
sudo semanage fcontext -a -t httpd_sys_content_t "/srv/www/example.com(/.*)?"
sudo restorecon -Rv /srv/www/example.com
Verify the expected SELinux type before retesting the site:
matchpathcon /srv/www/example.com /srv/www/example.com/index.html
Relevant output should include httpd_sys_content_t for the custom web root and its files:
/srv/www/example.com system_u:object_r:httpd_sys_content_t:s0 /srv/www/example.com/index.html system_u:object_r:httpd_sys_content_t:s0
If semanage is missing, install the policy management package for your distribution before applying the label. Do not disable SELinux just to make a server block work; fix the file context instead. If the visible symptom is a 403 response, use the Nginx 403 Forbidden troubleshooting guide to separate file permissions, directory indexes, and SELinux labels.
If you later stop serving from that custom root, remove only the file-context mapping you added, then restore labels on the old path:
sudo semanage fcontext -d "/srv/www/example.com(/.*)?"
sudo restorecon -Rv /srv/www/example.com
Fix Public Requests Reaching Another Layer
A localhost host-header test proves only the origin Nginx configuration. Public traffic may still be changed by a CDN, reverse proxy, Nginx Proxy Manager, Docker port mapping, Kubernetes ingress, cloud load balancer, cPanel, Plesk, or a firewall rule.
Compare the origin test with a public DNS test:
curl -I -H "Host: example.com" http://127.0.0.1/
curl -I http://example.com/
If the origin response is correct but the public response is not, leave the server block alone and inspect the external layer that owns the public request path.
Conclusion
Nginx is serving the intended site when each server block has a clear listener, hostname, fallback role, and verified document root or backend. Keep the include layout explicit, test every edit with sudo nginx -t, reload only after the syntax check passes, and use local host-header probes before chasing DNS, CDN, firewall, or load-balancer behavior.


Formatting tips for your comment
You can use basic HTML to format your comment. Useful tags currently allowed in published comments:
<code>command</code>command<strong>bold</strong><em>italic</em><blockquote>quote</blockquote>