Como o nginx escolhe o site pelo nome do host, e o que responde quando nada combina
Entenda a ordem em que o nginx compara o server_name, crie um bloco padrão de propósito e teste cada domínio com curl antes de apontar o DNS.
Você publica o segundo site no mesmo servidor, abre o domínio novo e aparece o site antigo. Ou alguém aponta um domínio qualquer para o seu IP e o nginx mostra a sua loja com o nome de outra pessoa na barra de endereço. A resposta curta: o nginx primeiro separa os blocos server pelo IP e pela porta do listen, depois compara o cabeçalho Host com o server_name nesta ordem: nome exato, curinga no começo, curinga no fim, expressão regular. Se nada combinar, responde o servidor padrão daquela porta, que é o bloco marcado com default_server ou, na falta dele, o primeiro bloco carregado.
Quase todo caso de "o nginx abriu o site errado" se explica por uma de duas coisas: o nome não casou do jeito que você achava, ou o bloco padrão é um site real que ninguém escolheu para esse papel.
Como o nginx decide qual bloco server responde
Quando o pedido chega, o nginx ainda não olha nome nenhum. Primeiro ele separa os blocos que escutam no endereço e na porta onde a conexão entrou. Um bloco com listen 80 nunca vai responder a uma conexão na 443, por mais que o nome bata. Só dentro desse grupo começa a comparação de nomes.
O navegador manda o domínio no cabeçalho Host. Em HTTPS, ele também manda o nome no SNI, durante o handshake TLS, para o servidor saber qual certificado apresentar. O nginx compara esse nome com os server_name do grupo nesta ordem:
- Nome exato, como
loja.exemplo.com.br. - O curinga mais longo que começa com asterisco, como
*.exemplo.com.br. - O curinga mais longo que termina com asterisco, como
loja.*. - A primeira expressão regular que combinar, na ordem em que aparece na configuração.
Repare que a ordem dos arquivos não importa para nome exato nem para curinga. Ela importa em dois lugares: entre expressões regulares e na escolha do bloco padrão quando ninguém marcou default_server. É aí que mora a surpresa. Em muitas instalações, os sites entram por um include com asterisco, carregados em ordem alfabética. O site cujo arquivo começa com "a" vira o padrão sem que ninguém tenha decidido isso.
Existe ainda a forma especial .exemplo.com.br, com ponto na frente. Ela cobre o domínio sem prefixo e todos os subdomínios ao mesmo tempo. É prática, mas esconde erro: lojja.exemplo.com.br, digitado errado, abre o mesmo site em vez de falhar, e você nunca descobre que alguém está usando um endereço que não existe no seu plano.
Por que a expressão regular é o último recurso
Nomes exatos e curingas ficam em tabelas hash, consultadas rapidamente. Expressões regulares são testadas uma a uma, em sequência, a cada pedido que não casou antes. Com três regex isso não faz diferença perceptível. O problema real é outro: regex ampla captura o que você não previu e é difícil de ler meses depois. Use quando você precisa capturar parte do nome, por exemplo para escolher a pasta pelo subdomínio, e não como atalho para pegar tudo "para garantir".
Como configurar os sites e o bloco padrão
- Liste os hosts reais de cada site: o domínio sem www, com www e os subdomínios que existem de verdade.
- Crie um bloco por site, com
server_namecontendo só esses nomes. - Decida o que o nome secundário faz. O comum é o www redirecionar com 301 para o domínio sem www, ou o contrário. Escolha um e mantenha.
- Crie um bloco padrão explícito, com
default_server, em cada combinação de endereço e porta que você usa, inclusive IPv6. - Rode
sudo nginx -tantes de recarregar e só entãosudo systemctl reload nginx. - Teste cada nome com curl direto no IP, antes e depois de mexer no DNS.
Um exemplo com uma loja, o redirecionamento do www e do HTTP, e os blocos padrão:
# Padrão na porta 80: qualquer Host que não casou com outro bloco
server {
listen 80 default_server;
listen [::]:80 default_server;
server_name _;
return 444;
}
# Padrão na porta 443: recusa o handshake sem expor certificado nenhum
server {
listen 443 ssl default_server;
listen [::]:443 ssl default_server;
server_name _;
ssl_reject_handshake on;
}
# HTTP de qualquer nome da loja vai para o endereço oficial
server {
listen 80;
listen [::]:80;
server_name loja.exemplo.com.br www.loja.exemplo.com.br;
return 301 https://loja.exemplo.com.br$request_uri;
}
# HTTPS com www também vai para o endereço oficial
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name www.loja.exemplo.com.br;
ssl_certificate /etc/letsencrypt/live/loja.exemplo.com.br/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/loja.exemplo.com.br/privkey.pem;
return 301 https://loja.exemplo.com.br$request_uri;
}
# O site de verdade
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name loja.exemplo.com.br;
ssl_certificate /etc/letsencrypt/live/loja.exemplo.com.br/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/loja.exemplo.com.br/privkey.pem;
root /var/www/loja/public;
index index.html;
}
Três detalhes nesse exemplo. O return 444 é um código próprio do nginx: ele fecha a conexão sem mandar resposta, o que serve bem para scanners que batem no IP. A diretiva ssl_reject_handshake on existe a partir do nginx 1.19.4 e faz o bloco padrão da 443 recusar a conexão sem precisar de certificado. Em versões mais antigas, o bloco padrão precisa de algum certificado, e o mais comum é um autoassinado. Por fim, o certificado usado no bloco do www precisa cobrir os dois nomes, senão o navegador reclama antes de o redirecionamento acontecer.
Como testar sem depender do DNS
O navegador guarda redirecionamento 301 e HSTS, e isso confunde qualquer teste. Use curl, forçando o nome contra o IP do servidor:
curl -I -H "Host: loja.exemplo.com.br" http://203.0.113.10/
curl -I -H "Host: www.loja.exemplo.com.br" http://203.0.113.10/
curl -I -H "Host: nao-existe.exemplo.org" http://203.0.113.10/
curl -I --resolve loja.exemplo.com.br:443:203.0.113.10 https://loja.exemplo.com.br/
curl -I --resolve outro.exemplo.org:443:203.0.113.10 https://outro.exemplo.org/
Os dois primeiros devem devolver 301 para o endereço oficial. O terceiro deve terminar com "Empty reply from server", que é o 444 fazendo seu trabalho. O quarto usa --resolve para que o SNI também vá com o nome certo, e deve devolver 200. O último deve falhar no handshake. Se algum deles abrir a loja, o nome casou onde não devia.
Para ver tudo que o nginx carregou de fato, incluindo arquivos que você esqueceu que existiam, use sudo nginx -T e procure as linhas de server_name e listen. A mesma lógica vale para quem usa Apache, com outros nomes de diretiva, e está explicada em vários sites no mesmo Apache.
Erros comuns
- Achar que
server_name _é um curinga. O sublinhado é só um nome que nunca casa com nada. Quem torna o bloco padrão é odefault_servernolisten. Sem ele, o padrão continua sendo o primeiro bloco carregado. - Criar o padrão só na porta 80. Na 443, o primeiro site com certificado vira padrão e passa a responder por qualquer domínio apontado para o seu IP, com aviso de certificado inválido na frente.
- Esquecer o
listen [::]em um dos blocos. Quem chega por IPv6 cai em outro bloco e vê outro site, e o problema só aparece para parte dos usuários. - Repetir o mesmo nome em dois blocos da mesma porta. O
nginx -tavisa com "conflicting server name ... ignored" e só o primeiro vale. Quem não lê o aviso passa horas editando o bloco que nunca responde. - Usar curinga amplo no site principal.
*.exemplo.com.brno site de produção faz qualquer subdomínio novo, inclusive de teste, abrir a produção. - Ignorar o erro de hash. Com muitos nomes longos, o nginx recusa iniciar com "could not build server_names_hash". A correção é aumentar
server_names_hash_bucket_sizeno blocohttp, não apagar nomes às cegas.
Checklist
- Cada site tem um bloco com apenas os nomes que ele realmente atende.
- Existe um bloco com
default_serverem cada porta e em IPv4 e IPv6. - O nome secundário, com ou sem www, redireciona com 301 para o oficial.
- Os certificados cobrem todos os nomes que passam pela 443.
sudo nginx -tsai sem erros e sem avisos de nome em conflito.- Um curl com Host inventado não devolve nenhum site real.
- O teste com
--resolveconfirma o comportamento em HTTPS antes da troca de DNS.
Mande agora, do seu computador, um curl com um Host inventado para o IP do seu servidor. Qual site respondeu, e foi você quem escolheu esse site para o papel?
Referência: http://nginx.org/en/docs/http/server_names.html