Nginx Server Block Selection: default_server, server_name, and location Matching
Nginx selects a server block by matching the Host header against server_name directives. If no match, it uses the default_server (or the first block if none is declared). The _ server_name is a conventional catch-all — not a wildcard. location blocks have a precedence order: = (exact) > ^~ (prefix priority) > ~ (regex) > plain prefix.
default_server: fallback for unmatched hosts
# Without default_server: nginx uses the first block in config order as fallback
server {
listen 80;
server_name api.example.com;
# ...
}
# With default_server: explicit fallback (only one allowed per IP:port)
server {
listen 80 default_server;
server_name _;
return 444; # close connection for unknown hosts
}
listen 80 without default_server: nginx uses whichever server block appears first in config files for requests that don't match any server_name.
listen 80 default_server: explicitly designates this block as the fallback. Useful for rejecting requests to unknown hosts (return 444 closes the connection with no response) or serving a landing page.
One default_server per IP:port combination. Multiple default_server directives on the same port cause nginx -t to fail with:
nginx: [warn] conflicting server name "_" on 0.0.0.0:80, ignored
server_name _: a catch-all convention
server_name _ is not a wildcard syntax. _ is simply a hostname that no real client will ever send — it's a convention for "this block matches nothing by name, use it as a catch-all when combined with default_server":
server {
listen 80;
server_name example.com;
# matches: Host: example.com
}
server {
listen 80;
server_name _; # matches requests with Host: _ (nobody sends this)
# This block is NOT automatically chosen for unmatched hosts
# Without default_server, the FIRST block is the fallback
}
To test which block handles a request:
# Force the Host header to see which block matches
curl localhost:80/health -H "Host: _"
curl localhost:80/health -H "Host: doesnotexist.com"
location block matching order
nginx evaluates location blocks in this precedence order:
1. = (exact match) → highest priority
2. ^~ (prefix, then stop searching for regex)
3. ~ / ~* (regex, case-sensitive / case-insensitive) → in order of appearance
4. Plain prefix strings → longest match wins
server {
# 1. Exact match — checked first
location = /health {
return 200 'OK';
}
# 2. Prefix with regex suppression
location ^~ /static/ {
root /var/www;
}
# 3. Case-sensitive regex
location ~ \.php$ {
fastcgi_pass php-fpm:9000;
}
# 3. Case-insensitive regex
location ~* \.(jpg|png|gif)$ {
expires 30d;
}
# 4. Plain prefix (longest match wins)
location / {
proxy_pass http://backend;
}
}
location ~ \.php$ — the ~ triggers case-sensitive regex matching. Without ~, nginx treats the pattern as a literal string, so /index.php wouldn't match .php$.
Nginx does not have a built-in wildcard for server_name — use wildcard syntax *.example.com for subdomains
GotchaNginxserver_name _ is a convention, not a wildcard. Real nginx wildcard syntax is *.example.com (matches any single subdomain) or .example.com (matches the domain and all subdomains). Regex server_name uses tilde prefix: ~^api\d+\.example\.com$. The _ catch-all works only because real clients don't send 'Host: _'. If a client sends any legitimate Host that doesn't match other blocks, and the _ block isn't marked default_server, nginx falls back to the first block — not the _ block.
Prerequisites
- HTTP Host header
- Nginx server block matching
- Virtual hosting
Key Points
- server_name _; — catch-all convention (no real client sends Host: _). Not a wildcard.
- server_name *.example.com; — matches api.example.com, www.example.com, etc.
- default_server — explicit fallback for unmatched hosts. Without it, first server block is the fallback.
- nginx -t — validate config; catches duplicate default_server, syntax errors, file not found.
Forwarding protocol through a load balancer
AWS ELB/ALB terminates HTTPS and forwards HTTP to nginx. The application behind nginx sees HTTP and may generate incorrect HTTP redirect URLs. Forward the original protocol:
server {
listen 80;
server_name api.example.com;
location ~ \.php$ {
fastcgi_pass php-fpm:9000;
# Forward original protocol from ELB to application
fastcgi_param HTTP_X_FORWARDED_PROTO $http_x_forwarded_proto;
include fastcgi_params;
}
location / {
proxy_pass http://app-backend;
# For proxy_pass, use proxy_set_header instead
proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto;
proxy_set_header Host $host;
}
}
$http_x_forwarded_proto reads the X-Forwarded-Proto header that the ALB sets (https). The application reads $_SERVER['HTTP_X_FORWARDED_PROTO'] (PHP) or the equivalent to know the original scheme.
You have three nginx server blocks all listening on port 80, none with default_server. A request arrives with Host: unknown.example.com that matches none. Which server block handles it?
easyNo default_server is declared. Nginx must fall back to something when no server_name matches.
ANginx returns 404 for unmatched hosts
Incorrect.Nginx doesn't return 404 — it selects a server block to handle the request. 404 is returned by the selected block if the path doesn't exist, not by the server selection mechanism itself.BThe first server block in the configuration file order handles the request
Correct!When no server_name matches and no default_server is declared, nginx uses the first server block that listens on that IP:port combination. The 'first' is determined by the order config files are loaded (usually alphabetical in /etc/nginx/sites-enabled/ or /conf.d/). This is why explicitly declaring a default_server catch-all block is good practice — it makes the fallback behavior intentional and predictable, rather than accidentally serving a different site's content for unknown hostnames.CThe last server block handles the request
Incorrect.The fallback without default_server is the first block, not the last.DNginx closes the connection with no response
Incorrect.Closing connections without response requires `return 444` in a server block. By default, nginx selects a block and processes the request.
Hint:What does nginx use as a fallback when no server_name matches and no default_server is set?