Server Block #
In the modern era of web development, running one website on a single physical server is a huge waste of computing resources. To solve this, Nginx provides a high-level virtual hosting mechanism through the Server Block concept. Server blocks let you host dozens, hundreds, or even thousands of different domain names in isolation using just one public IP address and a single Nginx instance running on the server.
Understanding how server blocks work isn’t just about writing a domain name in a config file. You need to understand how Nginx interacts with the TCP stack in the Linux kernel, how it selects the right server block based on HTTP protocol headers, and how to design server blocks that are safe from port scanning attacks. This article covers the virtual hosting concept, dissects the socket optimization parameters of the listen directive, analyzes domain matching priority in server_name, and builds a defensive default server configuration.
The Virtual Hosting Concept: Name-Based vs IP-Based #
At the computer networking level, server blocks are implemented through two main virtual hosting models:
1. IP-Based Virtual Hosting #
In this model, your physical server needs either multiple physical network cards (NICs) or multiple IP aliases on a single network card. Each website is bound to a unique IP address.
- How it works: Nginx listens for requests on different IPs. The client connects to that specific IP, and Nginx serves the matching site without needing to inspect HTTP header content.
- Drawbacks: Very wasteful of IP addresses. Given the current scarcity of global IPv4 addresses, allocating one public IP per domain for hundreds of websites is impossible in most production environments.
2. Name-Based Virtual Hosting #
This model is the dominant industry standard today. All websites share the same public IP address and listen on the same network ports (e.g., port 80 for HTTP and 443 for HTTPS).
- How it works: Nginx distinguishes the requested site by examining the
HostHeader inside the HTTP request payload sent by the browser. - Example: When a user visits
example.comandother.com, both pointing to the same server IP (103.22.44.55), the browser sends HTTP requests with headers:Host: example.com-> Nginx routes to virtual host A.Host: other.com-> Nginx routes to virtual host B.
A Deep Dive into the listen Directive Parameters #
The listen directive tells Nginx on which IP address, port, or UNIX path worker processes should open network sockets to listen for incoming connections. This directive is very flexible and supports powerful kernel-level parameters:
# 1. Listen on all IPv4 interfaces on port 80
listen 80;
# 2. Listen on a specific IP address on port 80
listen 192.168.1.50:80;
# 3. Listen on all IPv6 interfaces
listen [::]:80;
# 4. Listen on a UNIX Domain Socket (fast inter-process communication)
listen unix:/var/run/nginx.sock;
# 5. HTTPS configuration with advanced optimization parameters
listen 443 ssl default_server reuseport backlog=4096;
Let’s review the low-level optimization parameters inside the listen directive above:
default_server- Marks this server block as the default destination when an incoming request doesn’t match any
server_nameon that port. Only onedefault_serverdeclaration is allowed per IP:Port combination.
- Marks this server block as the default destination when an incoming request doesn’t match any
reuseport- How it works: By default, all worker processes share one incoming listen socket queue managed by the kernel. Under very high traffic, this triggers lock contention between workers, increasing CPU latency. With the
reuseportparameter, the Linux kernel creates a separate socket queue for each worker process. The kernel automatically distributes incoming TCP connections to those workers using hardware hashing. - Result: Drastically lowers TCP handshake latency on multicore systems and increases overall throughput by up to 3 times.
- How it works: By default, all worker processes share one incoming listen socket queue managed by the kernel. Under very high traffic, this triggers lock contention between workers, increasing CPU latency. With the
backlog=4096- Sets the maximum length of the TCP connection queue waiting for the handshake to complete (half-open TCP connection queue or SYN backlog). If the queue exceeds this value, the Linux kernel rejects new connections. The OS default is usually only
511. Raising it to4096is highly recommended for busy servers so they don’t reject clients during sudden traffic spikes.
- Sets the maximum length of the TCP connection queue waiting for the handshake to complete (half-open TCP connection queue or SYN backlog). If the queue exceeds this value, the Linux kernel rejects new connections. The OS default is usually only
ssl- Instructs Nginx that connections arriving on this port must go through an SSL/TLS encryption negotiation process before processing the HTTP protocol.
Dual-Stack IPv4 and IPv6 Management #
When enabling IPv6 on a server, you often face a problem where Nginx fails to bind an IPv4 socket because the OS kernel is already listening on that port via IPv6 in a stacked manner (IPv6-only binding behavior).
To avoid port collisions on Linux, use the ipv6only=on parameter if you want to separate them, or simply install the following configuration so Nginx listens to both protocols in a clean dual-stack manner:
server {
# Listen on IPv4 on all interfaces
listen 80;
# Listen on IPv6 on all interfaces independently
listen [::]:80 ipv6only=on;
server_name example.com;
}
Unpacking server_name Syntax: Types & Priority #
After Nginx accepts the TCP connection on the specified port, it enters the second stage: matching the Host header of the HTTP request against the server_name directives defined in your server blocks.
Nginx supports four ways to write domains in server_name:
1. Exact Match #
A literal match against the full domain. This is the most common way.
server_name example.com www.example.com;
2. Wildcard Match (Names with an Asterisk *)
#
Nginx allows the asterisk * to match part of a subdomain. The asterisk can only be placed at the beginning or end of the domain, and only covers one name segment:
# Matches: api.example.com, blog.example.com
# Does NOT match: example.com, or dev.api.example.com (two levels)
server_name *.example.com;
# Matches: example.org, example.net (suffix wildcard)
server_name example.*;
3. Regular Expression Match #
If you need complex matching logic, you can use a regex by prefixing the server_name value with a tilde ~.
# Matches domains with optional www. and internal sub-domains
server_name ~^(www\.)?example\.com$;
You can also use Capture Groups to dynamically capture part of the domain and store it in a custom variable. You can then use that variable inside the server block configuration, for example to determine the root folder automatically:
# Captures the subdomain name into the $subdomain variable
server_name ~^(?<subdomain>.+)\.example\.com$;
# Uses the captured variable to build a dynamic root path
root /var/www/html/subdomains/$subdomain;
With this tactic, when a client visits customer-a.example.com, Nginx dynamically routes the root directory to /var/www/html/subdomains/customer-a without you writing dozens of manual server blocks for each new customer.
4. Blank Name / Catch-All #
server_name _;
The underscore _ has no magical meaning; it’s just an invalid domain name used as a global convention to mark a “catch-all” (the server block that matches every request not matching any real server name).
The server_name Evaluation Priority Algorithm #
If there are many server blocks listening on the same port and several of them could potentially match the request domain (e.g., exact match vs regex), Nginx evaluates the match using a very strict priority order:
1. Exact Name Match (highest priority)
-> server_name example.com;
2. Longest Wildcard Match starting with an asterisk (*)
-> server_name *.example.com;
3. Longest Wildcard Match ending with an asterisk (*)
-> server_name example.*;
4. Regular Expression (Regex) Match, evaluated in the ORDER they appear in the configuration file
-> server_name ~^(www\.)?example\.com$;
5. default_server (if no match from steps 1-4 succeeds)
As an illustration, if you have the following configuration:
# Server Block 1
server {
listen 80;
server_name *.example.com;
}
# Server Block 2
server {
listen 80;
server_name ~^api\.example\.com$;
}
When a request comes in for api.example.com, Nginx picks Server Block 1 because the leading wildcard match (Priority 2) ranks higher than the regex match (Priority 4), even though the regex in Server Block 2 defines that domain very precisely.
Server Name Hash Table Limits and Tuning #
When you manage a large-scale web server infrastructure with hundreds of virtual host domain names, or when you use very long domain names (like multi-level subdomains for multi-tenant systems), Nginx often fails memory initialization at startup and dumps the following fatal error:
nginx: [emerg] could not build optimal server_names_hash, you should increase either server_names_hash_max_size: 512 or server_names_hash_bucket_size: 64
Why Does This Problem Happen? #
To match server names at lightning speed in RAM, Nginx doesn’t scan your domain list sequentially from top to bottom for each request. Instead, Nginx builds hash tables of all server names at startup.
The memory allocated for each domain entry is determined by two parameters:
server_names_hash_max_size: The maximum size limit of the entire hash table.server_names_hash_bucket_size: The maximum size of a single memory bucket allocated to store one domain name (including the domain’s string characters). By default, Nginx sets this size aligned with your CPU’s cache line size (usually 32, 64, or 128 bytes).
If one of your domains is very long, that domain name won’t fit in the default memory bucket space. As a result, Nginx fails to build the binary lookup table and refuses to start.
Practical Hash Table Tuning Solutions #
You need to raise the hash table capacity at the global http context level in the nginx.conf file:
http {
# Add or change the following parameters in the http context
# Raise the bucket size to 128 bytes (supports long domain names)
server_names_hash_bucket_size 128;
# Raise the maximum size limit of the global hash table
server_names_hash_max_size 1024;
# ... other configuration ...
}
Setting server_names_hash_bucket_size to 128 is a safe, recommended choice on modern production server architectures, giving enough room for dynamic subdomain names without triggering a significant RAM increase.
Default Server Security: Why It’s Mandatory & How to Set It Up #
By default, if Nginx receives a request with a Host header not registered in any server block, Nginx routes that request to the first server block loaded on the relevant port.
The Security Threat of Mass Scanning (Port Scanning) #
Hacker bots and internet scanners periodically sweep entire ranges of public IP addresses looking for open ports 80 and 443. They send raw HTTP requests directly to your server IP (e.g., GET / HTTP/1.1 with header Host: 103.22.44.55).
If you don’t configure a dedicated default server, Nginx automatically responds using your first virtual host. This is dangerous because:
- Domain Exposure: Leaks the domain and internal technology running on your server to outsiders.
- Exploit Attacks: Bots will try to send web application exploits to your endpoints directly via the IP.
- Resource Waste: Your server stays busy serving useless junk traffic.
Solution: Implementing a Connection-Killing Default Server #
Industry best security practices require creating a dedicated default server block on both HTTP (80) and HTTPS (443) ports that acts as a “defense wall”. This block’s job is to catch all that junk traffic and immediately drop the connection without providing any information.
# /etc/nginx/conf.d/00-default-security.conf
# 1. Reject invalid port 80 traffic
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
# Return the custom Nginx 444 status
return 444;
}
# 2. Reject invalid port 443 traffic
server {
listen 443 ssl default_server;
listen [::]:443 ssl default_server;
server_name _;
# Because an SSL listen needs a valid certificate for the initial handshake,
# we install an empty/self-signed dummy certificate specifically for this defense
ssl_certificate /etc/nginx/ssl/dummy.crt;
ssl_certificate_key /etc/nginx/ssl/dummy.key;
return 444;
}
Why Use Code 444?
#
Status 444 Connection Closed Without Response is a non-standard internal HTTP status unique to Nginx. When Nginx executes return 444;, it immediately closes the client’s TCP socket connection without writing any HTTP response headers (like Server: nginx or status 400 Bad Request) and without sending a single byte of data. The scanner client only sees an empty connection error (Empty Response). This is highly effective for saving your server’s memory, bandwidth, and CPU.
Traffic Redirect Scenarios (HTTP to HTTPS Redirection) #
In modern web standards, all web traffic must run over the encrypted HTTPS protocol. To achieve this, you need to design an efficient and secure HTTP (port 80) to HTTPS (port 443) traffic redirection pattern.
Here’s the industry-standard HTTP to HTTPS redirect configuration pattern:
# /etc/nginx/conf.d/example.com.conf
# 1. HTTP Server Block (Port 80) — Performs the Redirect
server {
listen 80;
listen [::]:80;
server_name example.com www.example.com;
# Permanent 301 redirect to HTTPS
return 301 https://$host$request_uri;
}
# 2. HTTPS Server Block (Port 443) — Handles Real Requests
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name example.com www.example.com;
# Let's Encrypt SSL certificate locations
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
root /var/www/example;
index index.html;
location / {
try_files $uri $uri/ =404;
}
}
Redirect Parameter Analysis: #
301: The HTTP status code for Moved Permanently. This status is very important for SEO because it tells search engines (like Google) that your site’s official address has permanently moved to HTTPS, so the page rank authority is transferred to the HTTPS URL.$host: A built-in Nginx variable containing the domain name requested by the client (from theHostheader).$request_uri: A built-in variable containing the full URL path along with its query string parameters (e.g.,/blog/tutorial-nginx?page=2).- Result: If a client visits
http://www.example.com/blog/tutorial-nginx?page=2, Nginx instantly redirects them tohttps://www.example.com/blog/tutorial-nginx?page=2safely and without truncating application search parameters.
Nginx Server Block Selection Flow Diagram #
To summarize your understanding, the following diagram explains how Nginx processes a request from connection start to the selected server block:
flowchart TD
Init["Network Packet Arrives at a Network Port"] --> Step1["1. Look for IP & Port match in the 'listen' directive"]
Step1 -->|"No Matching Port"| Err1["Ignore / Reject Connection"]
Step1 -->|"Several Server Blocks"| Step2["2. Check the 'Host' Header in the HTTP Request"]
Step2 -->|"Exact Match"| MatchExact["Use Server Block (Exact Match)"]
Step2 -->|"No Exact Match"| Step3["3. Evaluate Leading Wildcard (*.domain)"]
Step3 -->|"Leading Wildcard Match"| MatchWA["Use Server Block (Leading Wildcard)"]
Step3 -->|"No Leading Wildcard Match"| Step4["4. Evaluate Trailing Wildcard (domain.*)"]
Step4 -->|"Trailing Wildcard Match"| MatchWZ["Use Server Block (Trailing Wildcard)"]
Step4 -->|"No Trailing Wildcard Match"| Step5["5. Evaluate Regular Expression (Regex)"]
Step5 -->|"Regex Match"| MatchRegex["Use Regex Server Block <br> (In file declaration order)"]
Step5 -->|"No Regex Match"| Step6["6. Is there a block with 'default_server' <br> on that IP:Port?"]
Step6 -->|"default_server exists"| MatchDefault["Use the default_server Server Block"]
Step6 -->|"No default_server"| MatchFirst["Fallback: Use the First Server Block <br> loaded alphabetically"]
style Init stroke:#0288d1,stroke-width:2.5px
style Step2 stroke:#f57c00,stroke-width:2px
style MatchExact stroke:#388e3c,stroke-width:1.5px
style MatchDefault stroke:#d32f2f,stroke-width:2pxSummary #
- Name-Based Virtual Hosting uses the HTTP
HostHeader to distinguish dozens of websites running on one IP address and port.- The
listendirective controls socket binding. Use thereuseportparameter on high-traffic multicore servers to reduce CPU lock contention, and increasebacklogto absorb TCP queue spikes.- The
server_namepriority order is strictly evaluated: exact match → leading wildcard → trailing wildcard → regex (file reading order) → default server fallback.- Capture regex subdomain variables with the
(?<name>pattern)syntax inserver_nameto build automatic dynamic root directory handling.- Raise
server_names_hash_bucket_sizeto128in thehttpblock if you manage many subdomains or long domain names to avoid startup memory failures.- Protect your server from port scanner attacks by installing a dedicated
default_serverblock returning status444to instantly reject raw IP requests without any data response.