How to Resolve "Nginx server_name Wildcard Matching Not Working"
Quick answer
You configure a wildcard server_name expecting it to match any subdomain, but requests to those subdomains aren't landing in the block you expect β either...
You configure a wildcard server_name expecting it to match any subdomain, but requests to those subdomains aren't landing in the block you expect β either falling through to a default server, or failing to resolve at all. Nginx's wildcard matching has specific placement rules that are easy to get wrong, and DNS is just as often the actual culprit.
The Problem
A subdomain that should match your wildcard config instead serves the wrong content, or fails outright:
$ curl -H "Host: tenant1.yourapp.com" http://127.0.0.1
<html><body>Default server response</body></html>
# expected tenant1's actual content
Or the DNS lookup itself never even reaches Nginx:
$ curl https://tenant1.yourapp.com
curl: (6) Could not resolve host: tenant1.yourapp.com
Why It Happens
Nginx supports wildcards in server_name, but only in specific positions, and the config also needs to actually be the best match among all defined server blocks β a wildcard doesn't automatically win over a more specific exact match, nor does it substitute for DNS configuration. Common causes:
- Wildcard placed incorrectly β Nginx only allows a wildcard at the very start or very end of a name (
*.yourapp.comorwww.yourapp.*), not in the middle, and it must replace one or more full labels, not a partial one. - DNS doesn't actually have a wildcard record β Nginx matching a
Hostheader correctly is meaningless if the subdomain never resolves to your server's IP in the first place, since the request never arrives. - A more specific
server_nameblock or the default server intercepts the request first β when multipleserverblocks could technically match, Nginx applies clearly defined precedence rules, and an unexpected default block can shadow your wildcard. - Missing or incorrect SSL certificate for the wildcard domain β the request reaches Nginx but fails at the TLS handshake stage before
server_namematching even happens, if the certificate doesn't cover the subdomain.
The Fix
First, confirm DNS actually resolves the subdomain to your server, independent of Nginx entirely:
dig tenant1.yourapp.com +short
If nothing comes back, add a wildcard DNS record pointing at your server's IP:
*.yourapp.com. A 203.0.113.10
With DNS confirmed, check your Nginx wildcard syntax is in a valid position:
server {
listen 80;
server_name *.yourapp.com;
location / {
proxy_pass http://127.0.0.1:3000;
}
}
This is valid β a leading *. matching any single subdomain label. An invalid placement like tenant*.yourapp.com won't work as a wildcard at all; Nginx requires the asterisk to replace a complete label, not be embedded within one.
Check for a competing server block that might be matching first. Nginx's precedence order is: exact match, then longest matching wildcard starting with *, then longest matching wildcard ending with *, then the first matching regex, then the default server for that IP/port:
nginx -T | grep server_name
If a default_server is catching the request unexpectedly, make sure your wildcard block is actually configured to handle it, and that there isn't an earlier, unintentionally broader block claiming default_server status:
server {
listen 80 default_server;
server_name _;
return 444; # explicitly reject unmatched hosts instead of serving default content
}
For HTTPS, make sure your certificate actually covers the wildcard domain β a certificate issued only for yourapp.com won't cover tenant1.yourapp.com:
openssl s_client -connect tenant1.yourapp.com:443 -servername tenant1.yourapp.com </dev/null 2>/dev/null | openssl x509 -noout -text | grep DNS
DNS:*.yourapp.com, DNS:yourapp.com
If the certificate doesn't list the wildcard, reissue it (via Certbot or your CA) to explicitly cover *.yourapp.com.
Still Not Working?
If everything above checks out but matching still seems inconsistent across different subdomains, verify you're testing against the actual public IP and port Nginx is listening on, not a stale local cache or a different Nginx instance entirely (common in Docker or multi-server setups where an old container or load balancer node still holds the previous configuration):
curl -v --resolve tenant1.yourapp.com:443:203.0.113.10 https://tenant1.yourapp.com
The --resolve flag forces curl to hit a specific IP directly while still sending the correct Host header and SNI, which isolates whether the problem is DNS-related or genuinely inside Nginx's own server block matching.