Programação Por Equipe DevBras

Capa do artigo: Como o nginx escolhe o site pelo nome do host, e o que responde quando nada combina

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:

  1. Nome exato, como loja.exemplo.com.br.
  2. O curinga mais longo que começa com asterisco, como *.exemplo.com.br.
  3. O curinga mais longo que termina com asterisco, como loja.*.
  4. 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

  1. Liste os hosts reais de cada site: o domínio sem www, com www e os subdomínios que existem de verdade.
  2. Crie um bloco por site, com server_name contendo só esses nomes.
  3. 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.
  4. Crie um bloco padrão explícito, com default_server, em cada combinação de endereço e porta que você usa, inclusive IPv6.
  5. Rode sudo nginx -t antes de recarregar e só então sudo systemctl reload nginx.
  6. 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 é o default_server no listen. 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 -t avisa 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.br no 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_size no bloco http, 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_server em 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 -t sai sem erros e sem avisos de nome em conflito.
  • Um curl com Host inventado não devolve nenhum site real.
  • O teste com --resolve confirma 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?

Avalie este artigo

Referência: http://nginx.org/en/docs/http/server_names.html