Como Configurar Nginx Reverse Proxy com SSL e Load Balancing em 2026

Colocar o Nginx à frente de uma aplicação web é, hoje, o padrão de facto para terminar TLS, distribuir carga por vários backends e proteger serviços internos da exposição directa à Internet. O sintoma que leva a maioria dos sysadmins a procurar esta configuração é simples: a aplicação só escuta em 127.0.0.1:8080 (ou num socket Unix), não tem HTTPS nativo, e o tráfego precisa de ser servido em https://dominio.pt com um certificado válido. A causa é a ausência de um reverse proxy — um processo que recebe o pedido do cliente na porta 443, estabelece o handshake TLS, descodifica os cabeçalhos e reencaminha o tráfego para o backend em HTTP simples (ou num túnel encriptado). A solução passa por instalar o Nginx, configurar a directiva proxy_pass num server block, adicionar os cabeçalhos X-Real-IP e X-Forwarded-For, emitir um certificado via Let’s Encrypt (ou self-signed para laboratório), e fechar a porta 80 com um redirect permanente para HTTPS. Em 2026 o Nginx mantém-se como o servidor web mais usado na web (≈34% de market share, à frente do Apache), com LTS na linha 1.26.x e suporte completo para TLS 1.3, HTTP/2 e HTTP/3 (Nginx Documentation).

⚠ ⚠️ **Atenção

** Nunca exponha um backend directamente à Internet sem reverse proxy. Uma aplicação Flask/Gunicorn, Node.js ou uma instância Tomcat sem TLS e sem rate limiting é um alvo trivial. O reverse proxy é também a camada onde se aplica o limit_req (rate limiting) e o proxy_cache — sem ele, cada pedido chega intacto ao backend.

1. O que Está a Acontecer — Reverse Proxy, TLS Termination e Load Balancing

Um reverse proxy é um servidor que se coloca entre o cliente (browser, API consumer) e a aplicação de backend. Ao contrário de um forward proxy (que representa o cliente), o reverse proxy representa o servidor — o cliente nem sabe que existem vários backends por trás. O Nginx recebe o pedido na porta 80 ou 443, descodifica o TLS (se aplicável), e reencaminha o pedido para um ou mais servidores de backend definidos num bloco upstream (nginx.org/en/docs/http/ngx_http_upstream_module.html).

Existem três papéis distintos que o Nginx pode desempenhar simultaneamente neste cenário:

  1. TLS Termination — o Nginx é o único ponto que detém a chave privada do certificado. O backend recebe tráfego em HTTP simples, o que simplifica a configuração da aplicação e concentra a gestão de certificados num sítio só. Isto é o padrão recomendado pela própria documentação do Nginx (ngx_http_ssl_module).
  2. Reverse Proxy — a directiva proxy_pass reencaminha o pedido para um backend. Os cabeçalhos X-Real-IP e X-Forwarded-For preservam o IP original do cliente, porque o backend, sem eles, só vê o IP do Nginx (geralmente 127.0.0.1 ou um IP interno).
  3. Load Balancer — quando existem vários backends no bloco upstream, o Nginx distribui os pedidos entre eles segundo um algoritmo (round-robin por omissão, least_conn, ip_hash). O proxy_next_upstream permite que, se um backend falhar, o pedido seja reenviado para outro, actuando como health check passivo.

A grande vantagem em 2026 é que tudo isto se configura num único ficheiro (/etc/nginx/nginx.conf ou um site em /etc/nginx/conf.d/), sem qualquer middleware externo. Não há HAProxy, nem Envoy, nem consul — só Nginx e um ficheiro de texto.

2. Cenários em que Este Problema Aparece

  • Aplicação Node.js/Flask/Gunicorn atrás de Nginx — a app escuta em 127.0.0.1:3000 ou 127.0.0.1:8080 e precisa de ser servida em https://app.dominio.pt com Let’s Encrypt.
  • Cluster de 2+ instâncias Tomcat/Spring Boot — balancear carga entre 10.0.0.11:8080, 10.0.0.12:8080 e 10.0.0.13:8080 com round-robin e failover via proxy_next_upstream.
  • Laboratório ou ambiente de staging — gerar um certificado self-signed com openssl para testar HTTPS sem domínio público nem validação Let’s Encrypt.
  • API REST com rate limiting — proteger um endpoint /api/ contra abuso com limit_req_zone (ex: 10 pedidos/segundo por IP).
  • Site estático + API dinâmica — servir ficheiros estáticos directamente pelo Nginx (/static/) e fazer proxy do resto para o backend (/), reduzindo carga na aplicação.
  • Migração de Apache para Nginx — substituir o mod_proxy e mod_ssl do Apache pela stack Nginx equivalente, mantendo os mesmos redirects HTTP→HTTPS já documentados no kbase.pt (redireccionar-http-https-automaticamente, redirecionar-http-https-apache-nginx-iis-cloudflare).

3. Passo 1 — Instalar o Nginx e Confirmar a Versão

Antes de configurar o reverse proxy é preciso instalar o Nginx. A maioria das distribuições Linux traz um pacote nos repositórios oficiais, mas em produção recomenda-se o repositório oficial do nginx.org para obter a versão estável mais recente (1.26.x LTS em 2026) com patches de segurança atempados (nginx.org/en/linux_packages.html).

O bloco abaixo instala o Nginx em Debian/Ubuntu e em RHEL/AlmaLinux/Rocky, e confirma a versão instalada:

# Debian/Ubuntu (repositório da distro)
sudo apt update
sudo apt install -y nginx# RHEL/AlmaLinux/Rocky
sudo dnf install -y nginx
sudo systemctl enable –now nginx

# Confirmar versão e módulos compilados
nginx -v
nginx -V 2>&1 | tr ‘ ‘ ‘\n’ | grep -E ‘http_ssl|http_v2|http_realip|stream’

Output esperado (Debian 12, nginx 1.26 LTS):

nginx version: nginx/1.26.3
http_ssl_module
http_v2_module
http_realip_module

O que cada parâmetro faz:

  • apt install -y nginx — instala o pacote e as dependências (libc, pcre2, zlib, openssl).
  • systemctl enable --now nginx — arranca o Nginx agora e garante o arranque no boot.
  • nginx -v — mostra a versão (curta).
  • nginx -V — mostra a versão e os argumentos de compilação, incluindo a lista de módulos built-in. Os módulos que interessam para este artigo são http_ssl_module (SSL/TLS), http_v2_module (HTTP/2), http_realip_module (substituir o IP do cliente pelo real) e http_proxy_module (reverse proxy, sempre presente).

4. Passo 2 — Configurar o Reverse Proxy com proxy_pass e Cabeçalhos

Com o Nginx instalado, o passo seguinte é definir um server block que escuta em HTTP (porta 80) e faz proxy para o backend. O bloco abaixo assume um backend Node.js em 127.0.0.1:3000. Crie o ficheiro /etc/nginx/conf.d/app.conf:

server {
listen 80;
listen [::]:80;
server_name app.dominio.pt;# Reverse proxy para o backend
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;

# Preservar o IP real do cliente
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;

# WebSocket (necessário para Socket.io, Chat, etc.)
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection “upgrade”;

# Timeouts
proxy_connect_timeout 5s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
}

Depois de gravar, valide a sintaxe e recarregue:

sudo nginx -t
sudo systemctl reload nginx

O que cada directiva faz:

  • proxy_pass http://127.0.0.1:3000; — reencaminha todos os pedidos do location / para o backend. A barra final e o path têm regras específicas de rescrita (ver proxy_pass docs).
  • proxy_http_version 1.1; — força HTTP/1.1 entre Nginx e backend, indispensável para keep-alive e WebSockets. Por omissão o Nginx usa 1.0, que não suporta Upgrade.
  • proxy_set_header Host $host; — passa o Host original ao backend, para que o backend saiba qual o virtual host pedido (importante em frameworks que geram URLs absolutas).
  • proxy_set_header X-Real-IP $remote_addr; — envia o IP do cliente ($remote_addr) num cabeçalho à parte, que o backend pode ler em vez de ver sempre 127.0.0.1.
  • proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; — acrescenta o IP do cliente à cadeia X-Forwarded-For (formato: client, proxy1, proxy2). Necessário quando há vários proxies encadeados.
  • proxy_set_header X-Forwarded-Proto $scheme; — diz ao backend se o pedido original veio por http ou https, para que ele gere redirects e cookies com o protocolo correcto.
  • Upgrade / Connection "upgrade" — imprescindíveis para WebSocket. Sem Connection "upgrade", o handshake WebSocket falha com 400/502.
  • proxy_connect_timeout 5s; — tempo máximo para estabelecer a ligação TCP ao backend. 5s é razoável; se o backend demora mais, está em baixo.
  • proxy_read_timeout 60s; — tempo máximo para ler a resposta do backend. Para APIs com long-polling ou SSE, aumentar para 600s.

Nota sobre a barra final do proxy_pass: proxy_pass http://backend/api/; e proxy_pass http://backend; têm comportamentos distintos. Com barra, o Nginx substitui o prefixo do location pelo path do proxy_pass; sem barra, o path original é preservado. Ver a secção proxy_pass da documentação oficial.

5. Passo 3 — Configurar SSL/TLS com Let’s Encrypt ou Self-Signed

O reverse proxy da secção anterior só escuta em HTTP. Para servir HTTPS é preciso um certificado. Há dois caminhos: Let’s Encrypt (gratuito, automático, validado por CA pública — recomendado para produção) e self-signed (gerado localmente com openssl, sem validação externa — adequado para laboratório ou intranet).

Via Let’s Encrypt com Certbot (recomendado para produção)

O Certbot é o cliente oficial da EFF para Let’s Encrypt. Em 2026 suporta o plugin --nginx, que edita automaticamente o server block e injeta a configuração SSL (certbot.eff.org, letsencrypt.org/getting-started).

# Instalar o certbot e o plugin nginx (Debian/Ubuntu)
sudo apt install -y certbot python3-certbot-nginx# Emitir o certificado e configurar o Nginx automaticamente
sudo certbot –nginx -d app.dominio.pt -d www.app.dominio.pt

# Testar a renovação automática (corre sem alterar nada)
sudo certbot renew –dry-run

O certbot --nginx faz três coisas: (1) valida o domínio via challenge HTTP-01 (coloca um ficheiro temporário em /.well-known/acme-challenge/), (2) obtém o certificado em /etc/letsencrypt/live/app.dominio.pt/, (3) reescreve o server block para escutar em 443 com ssl_certificate, ssl_certificate_key e um redirect HTTP→HTTPS. A renovação é automática de 60 em 60 dias via systemctl list-timers | grep certbot.

 

Via Self-Signed com OpenSSL (alternativa para laboratório)

Para um ambiente sem domínio público ou onde a validação Let’s Encrypt não é possível, gera-se um certificado self-signed. Este certificado produz um aviso de “não fiável” no browser, mas cifra o tráfego na mesma — adequado para intranets, laboratórios e testes de carga.

# Gerar chave privada e certificado self-signed num só passo (validade 365 dias)
sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
-keyout /etc/nginx/ssl/selfsigned.key \
-out /etc/nginx/ssl/selfsigned.crt \
-subj “/C=PT/ST=Lisboa/L=Lisboa/O=Lab/CN=app.dominio.pt”# Restringir permissões da chave
sudo chmod 600 /etc/nginx/ssl/selfsigned.key

# Parâmetros DH para Perfect Forward Secrecy (pode demorar 1-2 min)
sudo openssl dhparam -out /etc/nginx/ssl/dhparam.pem 2048

Depois, no server block, adicionar a configuração SSL (ver secção 7 para o hardening completo). O server block fica assim:

server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name app.dominio.pt;ssl_certificate /etc/nginx/ssl/selfsigned.crt;
ssl_certificate_key /etc/nginx/ssl/selfsigned.key;
ssl_dhparam /etc/nginx/ssl/dhparam.pem;

location / {
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}

6. Passo 4 — Configurar Load Balancing com upstream

Quando a aplicação tem mais do que uma instância, o Nginx pode distribuir os pedidos entre elas. Para isso define-se um bloco upstream e usa-se o seu nome no proxy_pass em vez de um IP directo (ngx_http_upstream_module).

# Definir o pool de backends
upstream app_backend {
# Algoritmo round-robin (omissão) — distribui pedidos em sequência
server 10.0.0.11:8080 weight=3;
server 10.0.0.12:8080 weight=2;
server 10.0.0.13:8080;# Health check passivo: se um backend devolver 502/503/504 ou timeout,
# o pedido é reencaminhado para o próximo
proxy_next_upstream error timeout http_502 http_503 http_504;

# Manter a sessão no mesmo backend (sticky session via cookie — requer nginx-plus
# ou módulo sticky da comunidade; em nginx open-source usar ip_hash como alternativa)
keepalive 32;
}

server {
listen 443 ssl;
http2 on;
server_name app.dominio.pt;

ssl_certificate /etc/letsencrypt/live/app.dominio.pt/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/app.dominio.pt/privkey.pem;

location / {
proxy_pass http://app_backend;
proxy_http_version 1.1;
proxy_set_header Connection “”;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}

O que cada directiva faz:

  • upstream app_backend { ... } — define um pool com nome app_backend que pode ser referenciado no proxy_pass.
  • server 10.0.0.11:8080 weight=3; — define um backend. O weight controla a proporção de pedidos: com pesos 3, 2 e 1, o primeiro backend recebe 3/6 (50%), o segundo 2/6 (33%), o terceiro 1/6 (17%). Sem weight, todos têm peso 1.
  • proxy_next_upstream error timeout http_502 http_503 http_504; — se um backend devolver um destes erros, o Nginx tenta o próximo. Isto é o health check passivo — não há sondas activas na edição open-source, mas os backends que falham são temporariamente marcados como indisponíveis.
  • keepalive 32; — mantém até 32 ligações TCP abertas para o backend, evitando o overhead de estabelecer uma nova ligação a cada pedido. Requer proxy_http_version 1.1 e proxy_set_header Connection "".

 

Algoritmos de balanceamento disponíveis no Nginx open-source

Directiva Algoritmo Quando usar
(omissão) round-robin Distribuição uniforme, backends homogéneos. É o padrão.
least_conn; least connections Backends com capacidade diferente ou pedidos com latência variável. Envia para o backend com menos ligações activas.
ip_hash; hash por IP Sticky sessions sem cookies. Garante que o mesmo cliente vai sempre ao mesmo backend (útil para sessões em memória). Quebra se o IP mudar (mobile, NAT).
hash $request_uri; hash por URI Cache consistency — o mesmo URI vai sempre ao mesmo backend, maximizando a cache local.

Exemplo com least_conn:

upstream app_backend {
least_conn;
server 10.0.0.11:8080;
server 10.0.0.12:8080;
server 10.0.0.13:8080 max_fails=3 fail_timeout=30s;
}
  • max_fails=3 — número de falhas (definido por proxy_next_upstream) que marcam o backend como indisponível.
  • fail_timeout=30s — durante 30s o backend é excluído do pool após 3 falhas.

7. Passo 5 — Hardening SSL/TLS (TLS 1.2/1.3, Cifras, HSTS)

O certificado por si só não basta — é preciso restringir as versões de protocolo e as cifras aceitáveis. Em 2026, TLS 1.0 e TLS 1.1 estão obsoletos (RFC 8996) e não devem ser activados. O mínimo é TLS 1.2; o recomendado é activar também TLS 1.3 (ngx_http_ssl_module).

Esta configuração aplica-se dentro do server block que escuta em 443:

server {
listen 443 ssl;
listen [::]:443 ssl;
http2 on;
server_name app.dominio.pt;# — Certificado —
ssl_certificate /etc/letsencrypt/live/app.dominio.pt/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/app.dominio.pt/privkey.pem;

# — Protocolos: só TLS 1.2 e 1.3 —
ssl_protocols TLSv1.2 TLSv1.3;

# — Cifras: preferir cifras modernas, desactivar legacy —
# TLS 1.2: suites ECDHE com AEAD (GCM/ChaCha20)
ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers off; # em TLS 1.3 o cliente escolhe; deixar off

# — Sessões: reduzir overhead do handshake —
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
ssl_session_tickets off; # desactivar tickets para PFS consistente

# — OCSP stapling: o Nginx obtém o estado do certificado e envia ao cliente —
ssl_stapling on;
ssl_stapling_verify on;
resolver 1.1.1.1 8.8.8.8 valid=300s;
resolver_timeout 5s;

# — HSTS: força o browser a usar HTTPS durante 1 ano (incl. subdomínios) —
add_header Strict-Transport-Security “max-age=31536000; includeSubDomains; preload” always;

location / {
proxy_pass http://app_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}

 

O que cada directiva faz:

  • ssl_protocols TLSv1.2 TLSv1.3; — desactiva SSLv3, TLS 1.0 e TLS 1.1. TLS 1.3 (RFC 8446) tem handshake mais rápido (1-RTT) e PFS obrigatório.
  • ssl_ciphers ... — lista explícita de suites. As que começam por ECDHE usam forward secrecy (se a chave privada for comprometida, tráfego passado permanece ilegível). CHACHA20-POLY1305 é otimizado para mobile e ARM.
  • ssl_prefer_server_ciphers off; — em TLS 1.3 o cliente escolhe a suite; deixar off é a recomendação da documentação Nginx em 2026. (Em TLS 1.2 o servidor escolhe, mas com a lista acima não há problema.)
  • ssl_session_cache shared:SSL:10m; — cache de sessões partilhada entre workers (10 MB ≈ 40.000 sessões). Evita repetir o handshake em ligações subsequentes.
  • ssl_session_tickets off; — desactiva session tickets, que usam chaves simétricas que rodando mal podem comprometer PFS. Em ambientes com boa rotação de chaves, pode ficar on.
  • ssl_stapling on; — OCSP stapling: o Nginx vai buscar a revogação do certificado à CA e envia-a ao cliente, poupando uma ida do browser ao servidor OCSP. Acelera o handshake e melhora a privacidade.
  • Strict-Transport-Security (HSTS) — diz ao browser para usar HTTPS durante max-age segundos (1 ano). includeSubDomains estende a todos os subdomínios. Atenção: depois de activar HSTS, não se pode voltar atrás facilmente — se o certificado expirar, os browsers recusar-se-ão a carregar o site. Testar primeiro sem preload e sem includeSubDomains.

Validar a configuração com uma ferramenta externa: SSL Labs deve dar A ou A+. Para validar a partir da linha de comandos:

# Verificar protocolos e cifras aceites
openssl s_client -connect app.dominio.pt:443 -tls1_3 < /dev/null 2>/dev/null | grep -E “Protocol|Cipher”
openssl s_client -connect app.dominio.pt:443 -tls1_2 < /dev/null 2>/dev/null | grep -E “Protocol|Cipher”# Verificar HSTS presente na resposta
curl -sI https://app.dominio.pt | grep -i strict-transport-security

8. Passo 6 — Redirect HTTP→HTTPS, Rate Limiting e Caching

Redirect HTTP→HTTPS

O redirect permanente da porta 80 para 443 garante que todo o tráfego passa por TLS. Este bloco coexiste com o server block da secção 7 (que escuta em 443):

server {
listen 80;
listen [::]:80;
server_name app.dominio.pt www.app.dominio.pt;# Redirect permanente 301 para HTTPS
return 301 https://$host$request_uri;
}

O return 301 é mais eficiente do que rewrite — não envia pedido ao backend, é resolvido no próprio Nginx. Este padrão é o recomendado pela documentação do Nginx e coincide com o que o kbase.pt já documenta para Apache, Nginx, IIS e Cloudflare em redirecionar-http-https-apache-nginx-iis-cloudflare e redireccionar-http-https-automaticamente.

Rate Limiting com limit_req

O limit_req protege o backend contra picos de tráfego e ataques de força bruta (ngx_http_limit_req_module). Define-se uma zona (estado partilhado entre workers) e aplica-se num location:

# Definir a zona no contexto http (fora de qualquer server)
http {
# 10 pedidos/segundo por IP (10 MB = ~160.000 IPs)
limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;server {
# …

location /api/ {
limit_req zone=api_limit burst=20 nodelay;
proxy_pass http://app_backend;
# … headers …
}
}
}

O que cada directiva faz:

  • limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s; — cria uma zona de estado chamada api_limit, com 10 MB de memória, chaveada pelo IP do cliente em formato binário ($binary_remote_addr é mais compacto que $remote_addr). Limite: 10 pedidos por segundo por IP.
  • limit_req zone=api_limit burst=20 nodelay; — aplica a zona a este location. burst=20 permite rajadas de 20 pedidos acima do limite (numa fila). nodelay serve os pedidos da rajada imediatamente em vez de os atrasar — mais adequado para APIs.
  • Sem nodelay, os pedidos acima do rate são atrasados para respeitar o limite; com nodelay, são servidos até ao limite do burst e o excesso recebe 503.

Nota: O limit_req_zone tem de estar no contexto http (em /etc/nginx/nginx.conf ou incluído antes dos server blocks). Se o colocar dentro de um server block, o Nginx devolve erro de sintaxe.

Caching com proxy_cache

O proxy_cache guarda respostas do backend em disco e serve-as directamente nos pedidos seguintes, reduzindo a carga no backend (proxy_cache docs):

http {
# Camada de cache: 100 MB de memória para chaves, 10 GB de disco, 60 min de TTL
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=app_cache:100m
max_size=10g inactive=60m use_temp_path=off;server {
listen 443 ssl;
# … SSL …

location / {
proxy_cache app_cache;
proxy_cache_valid 200 301 302 10m;
proxy_cache_valid 404 1m;
proxy_cache_key “$scheme$request_method$host$request_uri”;
proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;

# Header informativo (X-Cache: HIT/MISS/BYPASS)
add_header X-Cache-Status $upstream_cache_status;

proxy_pass http://app_backend;
# … headers …
}
}
}

O que cada directiva faz:

  • proxy_cache_path /var/cache/nginx ... — define onde as respostas são guardadas. levels=1:2 cria subdirectórios de 2 níveis para evitar um único directório com milhões de ficheiros. keys_zone=app_cache:100m reserva 100 MB de memória partilhada para metadados das chaves. max_size=10g limita o tamanho total da cache em disco. inactive=60m remove entradas não acedidas em 60 min.
  • proxy_cache app_cache; — activa a cache neste location.
  • proxy_cache_valid 200 301 302 10m; — respostas 200/301/302 são válidas durante 10 minutos (sobrepõe-se aos cabeçalhos Cache-Control do backend se estes não disserem nada).
  • proxy_cache_key ... — define a chave de cache. Por omissão inclui scheme, método, host e URI, o que evita colisões entre http/https e entre hosts.
  • proxy_cache_use_stale ... — se o backend estiver em baixo, serve a cache expirada em vez de devolver 502. Isto é o stale-while-revalidate e aumenta significativamente a resiliência.
  • add_header X-Cache-Status $upstream_cache_status; — header de diagnóstico: HIT (servido da cache), MISS (backend), BYPASS, STALE, EXPIRED, UPDATING. Útil para verificar se a cache está a funcionar.

Nota: O directório /var/cache/nginx tem de existir e ter permissões de escrita para o worker do Nginx (www-data em Debian/Ubuntu): sudo mkdir -p /var/cache/nginx && sudo chown www-data:www-data /var/cache/nginx.

9. Outras Causas do Mesmo Aviso

  • Erro 502 Bad Gateway — o Nginx não consegue contactar o backend. Causas: backend em baixo, porta errada, firewall a bloquear, SELinux a negar a ligação, ou backend a escutar só em 127.0.0.1 quando o Nginx está noutro host. Diagnosticar: curl -v http://127.0.0.1:3000/ a partir do próprio servidor do Nginx. Ver proxy_next_upstream docs.
  • Erro 504 Gateway Timeout — o backend aceitou a ligação mas não respondeu dentro do proxy_read_timeout. Causas: query de base de dados lenta, deadlock, ou backend sobrecarregado. Aumentar proxy_read_timeout ajuda como palliative, mas o problema está no backend. Logs: /var/log/nginx/error.log mostra upstream timed out.
  • Erro 400 Bad Request “The plain HTTP request was sent to HTTPS port” — o cliente enviou HTTP para a porta 443. Causa típica: o redirect HTTP→HTTPS não está configurado e o utilizador acedeu a http://dominio.pt:443. Solução: configurar o server block da porta 80 com return 301 https://$host$request_uri; (secção 8).
  • Erro SSL_ERROR_RX_RECORD_TOO_LONG no browser — o Nginx está a servir HTTP na porta que o browser espera HTTPS. Causa: listen 443; sem ssl, ou o ssl_certificate aponta para um ficheiro inexistente. Diagnosticar: sudo nginx -t e curl -vI https://app.dominio.pt.
  • Certificado Let’s Encrypt falha na renovação — o challenge HTTP-01 é bloqueado por firewall ou por outro redirect. Causas: regra return 301 antes do location /.well-known/acme-challenge/, ou portas 80 fechadas. Solução: garantir que /.well-known/acme-challenge/ não é redireccionado. Ver letsencrypt.org/getting-started.
  • WebSocket fecha com 502 após 60s — o proxy_read_timeout por omissão é 60s; ligações WebSocket que ficam inactivas mais tempo são fechadas. Solução: proxy_read_timeout 3600s; no location onde estão os WebSockets.

10. Como Evitar o Problema em Produção

  • Monitorizar a expiração do certificado — Let’s Encrypt tem validade de 90 dias. O certbot renew --dry-run deve correr mensalmente e o systemctl status certbot.timer deve estar activo. Para alertas externos, configurar o certbot renewal hooks com webhook para Slack/email.
  • Usar TLS 1.3 e desactivar TLS 1.0/1.1 — TLS 1.0 e 1.1 estão obsoletos desde 2020 (RFC 8996) e são marcados como fracos por todos os scanners. Em 2026, qualquer cliente moderno suporta TLS 1.3.
  • Separar server blocks para HTTP e HTTPS — nunca forçar SSL na porta 80. O redirect HTTP→HTTPS deve estar num server block próprio (porta 80) e o SSL noutro (porta 443).
  • Definir proxy_next_upstream em todos os upstream com mais do que um backend — sem esta directiva, o Nginx devolve 502 no primeiro erro em vez de tentar o próximo backend.
  • Activar limit_req mesmo em APIs internas — um bug numa aplicação cliente pode gerar picos de tráfego que derrubam o backend. O limit_req com burst generoso protege sem afectar tráfego legítimo.
  • Testar a configuração com nginx -t antes de cada reload — um erro de sintaxe no reload faz o Nginx continuar com a configuração anterior, o que pode mascarar problemas. O -t valida sem aplicar.
  • Manter logs com rotação/var/log/nginx/access.log e error.log podem crescer rapidamente em sites com tráfego. O pacote Debian/Ubuntu já traz logrotate configurado; em RHEL confirmar /etc/logrotate.d/nginx.
  • Documentar a topologia — num cluster com N backends, manter um diagrama de qual backend está em qual host, quais os pesos do upstream, e qual o procedimento de adicionar/remover um backend (sem reload disruptivo, usar down: server 10.0.0.13:8080 down;).
  • Verificar HSTS antes de activar — HSTS com preload é irreversível a curto prazo. Testar primeiro sem includeSubDomains e sem preload, confirmar que o certificado renova correctamente durante 1-2 ciclos, e só depois adicionar preload.

 

Checklist Antes de Aplicar em Produção

Antes de aplicar qualquer comando deste artigo em ambiente produtivo, confirma:

  1. Versão do Nginx (recomenda-se 1.26 LTS ou superior; 1.24 está em manutenção): nginx -v
  2. Módulos SSL e proxy compilados (indispensáveis para este artigo): nginx -V 2>&1 | tr ' ' '\n' | grep -E 'http_ssl|http_v2|http_proxy'
  3. Domínio a apontar para o servidor (se for usar Let’s Encrypt): dig +short app.dominio.pt deve devolver o IP público do servidor
  4. Portas 80 e 443 abertas na firewall: sudo ss -tlnp | grep -E ':80|:443' e regra de firewall (ufw, firewalld, ou security group na cloud)
  5. Backend acessível a partir do Nginx: curl -v http://127.0.0.1:3000/ (ou o IP/porta do backend) tem de responder 200/302/404 — não connection refused
  6. Backup da configuração actual: sudo cp -r /etc/nginx /etc/nginx.bak.$(date +%F) antes de qualquer alteração, e um snapshot de VM se estiver em ambiente virtualizado