Como forçar redirect HTTPS da porta 80 para 443 no Nginx
Aprenda a configurar o Nginx para forçar redirect permanente da porta 80 para 443, garantindo HTTPS em todas as requisições com 301 e HSTS.
Quando um servidor web aceita conexões em HTTP (porta 80) e HTTPS (porta 443) simultaneamente, o tráfego não criptografado continua viável — qualquer cliente que digite o domínio sem prefixo, clique em link antigo ou siga bookmark http:// vai bater na porta 80 e trafegar credenciais em texto plano. A correção é forçar redirect permanente 301 da porta 80 pra porta 443 no Nginx, garantindo que toda requisição seja terminada em TLS.
Este tutorial mostra a configuração correta de redirect HTTP → HTTPS no Nginx, incluindo o tratamento do desafio http-01 do Let’s Encrypt (que precisa de HTTP puro pra validar), ativação progressiva de HSTS e validação da configuração antes do reload. Aplica-se a Nginx 1.18+ em qualquer distribuição Linux (Ubuntu, Debian, Rocky, Alma).
Tempo de execução: cerca de 10 minutos, incluindo testes. Persona-alvo: developer ou sysadmin com Nginx já configurado servindo o site nas duas portas, que precisa consolidar tudo em HTTPS sem quebrar renovação de certificado.
Pré-requisitos
Você precisa de Nginx 1.18+ rodando em uma VPS Linux, com certificado TLS válido já instalado (Let’s Encrypt, ZeroSSL ou comercial), acesso sudo via SSH e o domínio respondendo nas portas 80 e 443 atualmente. Confirme com nginx -v e curl -I https://seudominio.com.
80 (HTTP) 443 (HTTPS) 301 Permanent /etc/nginx/sites-available/ Antes de mexer no Nginx, faça backup da configuração atual. Um erro de sintaxe num server block deixa o serviço sem subir até você corrigir, e mexer em produção sem rede de segurança é convite pra downtime.
sudo cp -r /etc/nginx /etc/nginx.bak.$(date +%Y%m%d)
O comando duplica todo o diretório com timestamp — se algo der errado, basta reverter com sudo rm -rf /etc/nginx && sudo mv /etc/nginx.bak.YYYYMMDD /etc/nginx.
Estrutura recomendada: dois server blocks
A abordagem correta separa o redirect HTTP em um server block próprio, deixando o HTTPS num segundo block. Isso evita lógica condicional (if ($scheme = http)) que o autor do Nginx desencoraja explicitamente — o if dentro de location em Nginx tem comportamento contra-intuitivo e historicamente foi fonte de bugs.
Abra o arquivo de configuração do site em sites-available. Em distribuições baseadas em Debian/Ubuntu, o padrão é /etc/nginx/sites-available/seudominio.com. Em Rocky/Alma, pode estar em /etc/nginx/conf.d/seudominio.conf:
sudo nano /etc/nginx/sites-available/seudominio.comSe você ainda não tem arquivo separado por site e tudo está em nginx.conf, esta é uma boa oportunidade de extrair os server blocks pra arquivos individuais — facilita manutenção e auditoria.
Substitua o conteúdo pelo template abaixo. Dois server blocks: o primeiro escuta a 80 e redireciona tudo (exceto o desafio ACME) pra HTTPS; o segundo termina o TLS e serve o site:
# Server block HTTP — redirect 301 pra HTTPS
server {
listen 80;
listen [::]:80;
server_name seudominio.com www.seudominio.com;
# Mantém o desafio do Let's Encrypt funcionando
location ^~ /.well-known/acme-challenge/ {
root /var/www/letsencrypt;
default_type "text/plain";
allow all;
}
# Redirect catch-all pra HTTPS
location / {
return 301 https://$host$request_uri;
}
}
# Server block HTTPS — termina TLS e serve o site
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name seudominio.com www.seudominio.com;
ssl_certificate /etc/letsencrypt/live/seudominio.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/seudominio.com/privkey.pem;
# HSTS — comece com 300s, suba depois de validar
add_header Strict-Transport-Security "max-age=300" always;
root /var/www/seudominio.com;
index index.html index.php;
location / {
try_files $uri $uri/ =404;
}
}A diretiva return 301 https://$host$request_uri preserva o domínio acessado ($host) e o path completo com query string ($request_uri), então http://seudominio.com/blog?id=42 vira https://seudominio.com/blog?id=42 sem perda de contexto.
Garanta que o diretório do desafio ACME existe e tem permissão de leitura pro usuário do Nginx:
sudo mkdir -p /var/www/letsencrypt/.well-known/acme-challenge
sudo chown -R www-data:www-data /var/www/letsencrypt
sudo chmod -R 755 /var/www/letsencryptEm Rocky/Alma o usuário é nginx, não www-data — ajuste conforme sua distribuição. Esse diretório precisa estar acessível em HTTP puro pro Certbot renovar o certificado a cada 60-90 dias.
Nunca rode nginx -s reload sem testar a sintaxe primeiro. Um erro de configuração derruba todos os sites servidos pelo mesmo daemon, não só o que você está editando.
Teste a sintaxe da configuração antes de aplicar:
sudo nginx -tA saída esperada é:
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successfulSe vir erro, leia a mensagem — o Nginx aponta o arquivo e linha exata. Corrija antes de prosseguir.
Aplique a nova configuração com reload (não restart) — reload faz graceful swap, mantendo conexões ativas:
sudo systemctl reload nginxEm sistemas sem systemd: sudo nginx -s reload. O serviço continua respondendo durante a troca; conexões em andamento terminam no worker antigo e novas requests vão pro worker com a config nova.
Verificação
Confirme que o redirect está ativo testando via curl sem abrir navegador (cache de browser pode mascarar problemas):
curl -I http://seudominio.com
A resposta deve mostrar:
HTTP/1.1 301 Moved Permanently
Server: nginx
Location: https://seudominio.com/
Para confirmar que o destino HTTPS responde corretamente, use -L pra seguir o redirect:
curl -IL http://seudominio.com
Você verá duas respostas: primeiro o 301 do Nginx, depois o 200 OK do HTTPS. Se a segunda for 502, 503 ou outro erro, o problema não é o redirect — é o backend HTTPS.
Teste também o desafio ACME continua acessível em HTTP:
sudo mkdir -p /var/www/letsencrypt/.well-known/acme-challenge
echo "teste-ok" | sudo tee /var/www/letsencrypt/.well-known/acme-challenge/teste
curl http://seudominio.com/.well-known/acme-challenge/teste
A resposta deve ser teste-ok em texto puro — não um redirect 301. Se vier redirect, a ordem dos location está errada.
Resolução de problemas
Loop de redirect infinito
Sintoma: navegador mostra erro “ERR_TOO_MANY_REDIRECTS”. Causa comum: a aplicação por trás do Nginx (PHP, Node) detecta $_SERVER['HTTPS'] como vazio e redireciona pra HTTPS de novo, mas o Nginx já redirecionou — ciclo infinito.
Solução: certifique-se que o server block HTTPS passa o header correto pro upstream:
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
A aplicação deve confiar nesse header em vez de inspecionar $_SERVER['HTTPS'] direto.
HSTS travado em HTTPS quebrado
Se você ativar max-age=31536000 (1 ano) e depois descobrir que o certificado tem problema, o navegador vai recusar HTTP por um ano inteiro mesmo que você reverta a config. A única saída é fazer o usuário limpar HSTS manualmente em chrome://net-internals/#hsts (cada navegador tem seu path).
Por isso o template começa com max-age=300. Depois de uma semana validando que tudo funciona, suba pra max-age=31536000; includeSubDomains — esse é o valor pra produção estabilizada.
Renovação Let’s Encrypt falha com 301
Sintoma: certbot renew retorna erro “Failed authorization procedure” depois que você ativou o redirect. Causa: a diretiva location ^~ /.well-known/acme-challenge/ está abaixo do location / no arquivo, então o catch-all redireciona primeiro.
O modificador ^~ no template tem prioridade sobre location /, mas se você editou e perdeu o ^~, a ordem importa. Garanta que o location do ACME aparece antes do catch-all OU use ^~ que dá prioridade independente da ordem.
Próximos passos
Com HTTPS forçado e validação confirmada, aprofunde a segurança e performance do servidor:
- Hardening TLS: ajuste
ssl_protocols TLSv1.2 TLSv1.3essl_cipherspra remover suítes legadas — use o gerador da Mozilla SSL Config como base. - OCSP Stapling: ative
ssl_stapling onpra reduzir latência de validação de certificado no primeiro request. - HSTS preload: depois de 1 ano com
max-age=31536000; includeSubDomains; preload, submeta o domínio em hstspreload.org pra entrar na lista de browsers. - HTTP/3: Nginx 1.25+ suporta QUIC nativo — vale ativar pra clientes com latência alta.
- Logs separados: configure
access_logespecífico do server block HTTPS pra facilitar análise de tráfego.
Se você está rodando esta config em produção, uma VPS Hostini já vem com Ubuntu/Debian otimizado e SSH endurecido — boa base pra subir Nginx com TLS sem tunar kernel manualmente.
Perguntas frequentes
Por que usar 301 e não 302 no redirect HTTPS?
O 301 é permanente — sinaliza pro navegador e pros buscadores cachear o destino HTTPS indefinidamente, transferindo PageRank corretamente. O 302 é temporário e força o cliente a refazer a requisição HTTP toda vez, desperdiçando latência e atrapalhando SEO. Para HTTPS forçado em produção, sempre 301.
Posso usar `return 301` ou preciso de `rewrite`?
Use `return 301` — é mais barato pro Nginx (não compila regex) e mais explícito. `rewrite ... permanent` faz a mesma coisa mas força o parser de regex e historicamente foi causa de loops quando combinado com `if`. Desde o Nginx 0.9.1 o `return` é o padrão recomendado pra redirects literais.
O redirect quebra o `http-01` do Let's Encrypt?
Quebra se você não excluir o path `/.well-known/acme-challenge/` do redirect. O Certbot precisa servir arquivos via HTTP puro na porta 80 durante a validação. A configuração mostrada no tutorial usa um `location` específico que serve o desafio em HTTP antes do redirect catch-all, mantendo a renovação automática funcionando.
Devo ativar HSTS junto com o redirect?
Sim, mas com cuidado. HSTS (Strict-Transport-Security) faz o navegador lembrar de só acessar via HTTPS por N segundos, evitando o primeiro request em HTTP. Comece com `max-age=300` (5 min) pra testar; só depois suba pra `max-age=31536000` (1 ano) com `includeSubDomains`. Errar HSTS trava o domínio em HTTPS quebrado por meses no cache do browser.
O redirect funciona se o cliente acessar por IP em vez de domínio?
Não com a configuração `server_name exemplo.com`. O Nginx só responde com aquele server block quando o Host header bate. Pra capturar requests por IP, adicione um server block default com `listen 80 default_server` que retorna 444 (close connection sem resposta) ou redireciona pro domínio canônico. Isso bloqueia scanners aleatórios.
Como verifico se o redirect está ativo sem abrir o navegador?
Use `curl -I http://seudominio.com` — a resposta deve mostrar `HTTP/1.1 301 Moved Permanently` e o header `Location: https://seudominio.com/`. Se vir 200 OK, o redirect não está ativo. Adicione `-L` pra seguir o redirect e confirmar que o HTTPS responde 200 no destino.