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

Antes de começar

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.

Porta origem 80 (HTTP)
Porta destino 443 (HTTPS)
Status code 301 Permanent
Arquivo config /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.

01

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.com

Se 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.

02

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.

03

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/letsencrypt

Em 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.

Sintaxe antes de reload

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.

04

Teste a sintaxe da configuração antes de aplicar:

sudo nginx -t

A saída esperada é:

nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

Se vir erro, leia a mensagem — o Nginx aponta o arquivo e linha exata. Corrija antes de prosseguir.

05

Aplique a nova configuração com reload (não restart) — reload faz graceful swap, mantendo conexões ativas:

sudo systemctl reload nginx

Em 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

HSTS é difícil de desfazer

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.3 e ssl_ciphers pra remover suítes legadas — use o gerador da Mozilla SSL Config como base.
  • OCSP Stapling: ative ssl_stapling on pra 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_log especí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.

Tópicos:
Próximos passos Cloud Ryzen com NVMe e proteção DDoS sempre ativa.Coloque em produção numa VPS Hostini →
Esse tutorial foi útil?
Falar no WhatsApp