Headers de segurança HTTP com Nginx

Headers HTTP de segurança instruem o browser a se proteger contra XSS, clickjacking, sniffing de MIME e outras vulnerabilidades, sem alterar uma linha do código da aplicação. Configurar esses headers no Nginx significa que todos os serviços por trás do proxy se beneficiam automaticamente.

Headers de segurança essenciais

Conjunto básico de headers que toda aplicação deveria ter:

nginx
# /etc/nginx/conf.d/security-headers.conf
# Incluir em todos os server blocks: include /etc/nginx/conf.d/security-headers.conf;

# Forçar HTTPS por 1 ano (HSTS)
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;

# Prevenir clickjacking: não permitir incorporação em iframe
add_header X-Frame-Options "SAMEORIGIN" always;

# Prevenir MIME sniffing
add_header X-Content-Type-Options "nosniff" always;

# Controlar informações no Referer header
add_header Referrer-Policy "strict-origin-when-cross-origin" always;

# Desabilitar FLoC/Topics API e outras features sensíveis
add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), interest-cohort=()" always;

# Remover header Server (não revelar versão do Nginx)
server_tokens off;

# X-XSS-Protection (legacy — navegadores modernos ignoram)
add_header X-XSS-Protection "1; mode=block" always;

Content Security Policy (CSP)

CSP é o header mais poderoso, define de onde cada tipo de recurso pode ser carregado:

nginx
# CSP básica para API (sem frontend):
add_header Content-Security-Policy "default-src 'none'; frame-ancestors 'none';" always;

# CSP para aplicação web completa:
add_header Content-Security-Policy "
    default-src 'self';
    script-src 'self' 'nonce-NONCE_AQUI' https://cdn.jsdelivr.net;
    style-src 'self' 'unsafe-inline' https://fonts.googleapis.com;
    img-src 'self' data: https://images.exemplo.com;
    font-src 'self' https://fonts.gstatic.com;
    connect-src 'self' https://api.seudominio.com.br wss://api.seudominio.com.br;
    frame-ancestors 'none';
    form-action 'self';
    upgrade-insecure-requests;
" always;

# CSP Report-Only (testar sem bloquear — reportar violações):
add_header Content-Security-Policy-Report-Only "
    default-src 'self';
    report-uri https://api.seudominio.com.br/csp-report;
" always;

# Diretivas importantes:
# default-src 'self': apenas recursos do próprio domínio por padrão
# script-src 'nonce-XYZ': permitir apenas scripts com o nonce correto
# frame-ancestors 'none': substituiu X-Frame-Options (mais moderno)
# upgrade-insecure-requests: converter http:// para https:// automaticamente

Configurar CORS com segurança

CORS (Cross-Origin Resource Sharing) para APIs acessadas por domínios diferentes:

nginx
# /etc/nginx/conf.d/cors.conf — CORS para API
# ATENÇÃO: nunca use Access-Control-Allow-Origin: * para APIs autenticadas

# Map para validar origins permitidas
map $http_origin $allowed_origin {
    default "";
    "https://app.seudominio.com.br"   "https://app.seudominio.com.br";
    "https://admin.seudominio.com.br" "https://admin.seudominio.com.br";
    # Desenvolvimento:
    "http://localhost:3000"           "http://localhost:3000";
}

server {
    location /api/ {
        # Responder preflight OPTIONS
        if ($request_method = 'OPTIONS') {
            add_header Access-Control-Allow-Origin $allowed_origin;
            add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS";
            add_header Access-Control-Allow-Headers "Authorization, Content-Type, X-Requested-With";
            add_header Access-Control-Allow-Credentials "true";
            add_header Access-Control-Max-Age 86400;
            add_header Content-Length 0;
            return 204;
        }

        # Headers CORS para requisições normais
        add_header Access-Control-Allow-Origin $allowed_origin always;
        add_header Access-Control-Allow-Credentials "true" always;

        proxy_pass http://127.0.0.1:3000;
    }
}

Ocultar informações do servidor

Reduza a superfície de ataque ocultando versões de software:

nginx
# nginx.conf — bloco http {}

# Remover versão do Nginx dos headers e páginas de erro
server_tokens off;

# Remover header X-Powered-By do backend (Node.js)
# (o Nginx não pode remover headers do backend diretamente, mas pode sobrescrever)
proxy_hide_header X-Powered-By;
proxy_hide_header Server;

# Adicionar um Server header neutro:
add_header Server "VPS" always;

# Remover header X-AspNet-Version (se usar .NET):
proxy_hide_header X-AspNet-Version;
proxy_hide_header X-AspNetMvc-Version;

# No backend Node.js/Express — remover X-Powered-By:
# app.disable('x-powered-by')
# Ou usar helmet: app.use(helmet())

# Verificar headers retornados:
curl -I https://api.seudominio.com.br/
# Deve mostrar Server: VPS (não "nginx/1.25.3")

Testar e verificar os headers

Ferramentas para validar que os headers estão configurados corretamente:

bash
# Verificar headers com curl:
curl -s -I https://seudominio.com.br | grep -i "strict-transport\|x-frame\|content-security\|x-content"

# Teste online:
# https://securityheaders.com — análise completa com nota A-F
# https://observatory.mozilla.org — análise da Mozilla

# Verificar CSP com navegador (DevTools):
# F12 → Console → ver erros "Refused to load..." de violações CSP

# Script para verificar múltiplos headers de uma vez:
for HEADER in "Strict-Transport-Security" "X-Frame-Options" "X-Content-Type-Options" \
              "Content-Security-Policy" "Referrer-Policy" "Permissions-Policy"; do
  VALUE=$(curl -s -I https://seudominio.com.br | grep -i "^${HEADER}:" | tr -d '\r')
  if [ -z "$VALUE" ]; then
    echo "❌ AUSENTE: $HEADER"
  else
    echo "✅ $VALUE"
  fi
done

Não quer configurar manualmente?

Implante o VPS em menos de 5 minutos com a Runstack. Infraestrutura da OPEN Datacenter, com servidores em São Paulo.

Perguntas frequentes

Não completamente, add_header adiciona headers à resposta, mas se o backend já enviou o mesmo header, ambos aparecem (duplicados). Use proxy_hide_header para remover o header do backend antes, então add_header para adicionar o correto. Para headers de segurança: o mais seguro é configurá-los no Nginx e remover do backend com proxy_hide_header.

HSTS regular: sim, mude max-age para 0 e espere os browsers respeitarem (pode levar até o tempo do max-age original). HSTS com preload: muito difícil de reverter, o domínio entra na lista embutida dos browsers e não pode ser removido rapidamente. Use preload apenas em domínios permanentes com HTTPS garantido.

Comece com CSP Report-Only para ver violações sem bloquear. Analise o relatório no console do browser ou no endpoint de report-uri. Adicione as origens legítimas à diretiva correta (script-src, style-src, img-src). Para scripts inline: prefira nonce ou hash em vez de 'unsafe-inline'. Processo iterativo: CSP forte leva tempo para refinar.

X-Frame-Options ainda é amplamente suportado e reconhecido por browsers antigos. frame-ancestors em CSP é mais moderno e flexível (permite listar múltiplos domínios). Recomendação: use ambos, X-Frame-Options para compatibilidade e frame-ancestors 'none' na CSP para browsers modernos. Quando há conflito, a CSP tem precedência nos browsers que a suportam.

Use Content-Security-Policy-Report-Only: aplica a política sem bloquear recursos, apenas reporta violações. Configure report-uri para receber os relatórios e analise por alguns dias antes de ativar a política completa. Ferramenta: https://report-uri.com tem plano gratuito para coletar e analisar relatórios CSP.