Alternar tema

Ajuste de desempenho do Nginx: gzip, cache e pool de conexoes

Easton editorial illustration: before-and-after response pipe, gzip compression module, cache chamber, connection pool manifold

Na semana passada recebi um alerta: o tempo de carregamento da pagina inicial de um e-commerce tinha subido para 4 segundos. Abri o Chrome DevTools e vi que o HTML tinha 120KB, enquanto CSS mais JS somavam outros 350KB, tudo como arquivos originais sem compressao. Pior ainda: cada requisicao batia no backend, e a taxa de acerto do cache era de apenas 12%. Naquela noite, depois do expediente, gastei 2 horas ajustando a configuracao do Nginx: ativei gzip, completei a estrategia de cache e ajustei os parametros de pool de conexoes. Na manha seguinte, a pagina inicial ja carregava em 1,6 segundo, e o QPS do backend tinha caido quase pela metade.

Esse tipo de problema e comum demais. Muita gente instala o Nginx, deixa rodando e para por ai. O gzip fica desativado por padrao, o cache recebe duas linhas improvisadas, e o limite de conexoes fica no valor original. Quando chega o pico de trafego, o servidor perde o folego.

Neste artigo, organizei configuracoes de ajuste de desempenho do Nginx que ja validei em producao. A compressao gzip pode reduzir 60-80% do volume transferido; trocar para Brotli ainda economiza mais 15-25%; quando a taxa de acerto do cache chega a 95%, a pressao no backend pode cair 90%; com pool de conexoes bem configurado, a capacidade concorrente pode multiplicar por tres ou quatro; e, com recursos avancados como Thread Pools e reuseport, uma unica maquina pode chegar a 50K-80K RPS. Vou abrir os detalhes de configuracao, os problemas que ja encontrei e os dados medidos em cada modulo.

Capitulo 1: configuracao de compressao gzip/Brotli - reduzindo o volume transferido

Vamos comecar pelo motivo de gzip ser tao importante. Imagine que seu arquivo HTML original tenha 100KB. Depois da compressao gzip, ele pode cair para apenas 20-25KB. Esses 75-80KB de banda economizada significam carregamento mais rapido para o usuario e custo de trafego menor para voce.

A primeira vez que percebi isso de verdade foi em um projeto de cliente. Mais de 60% dos usuarios eram mobile, muitos acessando em redes 4G. A pagina inicial levava 3-4 segundos para carregar, e a taxa de rejeicao subiu para 70%. Depois de ativar gzip, o volume transferido caiu diretamente 70%, e o tempo de carregamento da primeira tela desceu para cerca de 1,5 segundo.

1.1 Configuracao basica de gzip: primeiro faca funcionar

A configuracao de gzip no Nginx nao e complicada. O essencial cabe em poucas linhas:

http {
    gzip on;
    gzip_vary on;
    gzip_min_length 1000;
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml;
}

gzip on e o interruptor, sem misterio. gzip_vary on e muito importante: ele adiciona Vary: Accept-Encoding ao cabecalho de resposta, avisando ao CDN e ao navegador que o conteudo da resposta muda conforme a capacidade de compressao do cliente. Isso evita confusao no cache.

gzip_min_length definido como 1000 bytes significa que arquivos menores que 1KB nao serao comprimidos. Em arquivos pequenos demais, o ganho e baixo e ainda consome CPU. gzip_types define os tipos MIME que serao comprimidos. Por padrao, o Nginx so comprime text/html; voce precisa incluir CSS, JS, JSON e XML.

1.2 Configuracao avancada de gzip: nivel de compressao e lista de tipos MIME

O nivel de compressao exige equilibrio. No Nginx, gzip_comp_level pode ir de 1 a 9. Quanto maior o numero, maior a taxa de compressao, mas tambem maior o consumo de CPU.

Fiz uma bateria de testes:

Nivel de compressaoCompressao HTMLTempo de CPU (ms)Cenario recomendado
165%2CPU apertada
472%3Equilibrado (recomendado)
675%5Banda limitada (recomendado)
978%12Cenario extremo

Falando com franqueza, os niveis 4 e 6 sao a melhor escolha na maioria dos casos. No nivel 9, o consumo de CPU dobra, mas a taxa de compressao melhora so alguns pontos percentuais. Nao compensa.

Esta e a configuracao gzip completa que uso em producao:

# Configuracao de compressao gzip
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_min_length 1000;
gzip_types
    text/plain
    text/css
    text/xml
    text/javascript
    application/json
    application/javascript
    application/xml
    application/xml+rss
    application/xhtml+xml
    application/x-javascript;
gzip_disable "msie6";

gzip_proxied any e facil de esquecer. Se o seu Nginx atua como proxy reverso e a resposta do backend nao tem Content-Length, por padrao ela pode nao ser comprimida. Com any, voce forca a compressao de todas as respostas que atendem aos criterios.

gzip_disable "msie6" existe para compatibilidade com o antigo IE6, que tinha problemas com gzip. Hoje o IE6 praticamente desapareceu, entao essa linha poderia ser removida. Eu costumo deixa-la como defesa extra.

1.3 Quais tipos de arquivo trazem mais ganho?

Pelos meus testes, a diferenca de compressao entre tipos de arquivo e grande:

Tipo de arquivoTamanho originalDepois da compressaoTaxa de compressao
HTML100KB20-25KB75-80%
CSS80KB24-28KB65-70%
JavaScript120KB36-42KB65-70%
JSON API50KB20-25KB50-60%
Imagem/videoJa comprimidoSem sentido0-5%

Imagens e videos ja passam por compressao propria, como JPEG, PNG e MP4. Aplicar gzip de novo pode ate aumentar o tamanho. Por isso, nao coloque image/* nem video/* em gzip_types; isso vira efeito negativo.

Uma vez ajudei a investigar um problema e encontrei image/jpeg dentro de gzip_types. O resultado era que as imagens ficavam 3-5% maiores. Esse tipo de erro basico eu tambem provavelmente ja cometi; quando voce ainda nao entende bem, da vontade de adicionar todos os tipos MIME possiveis.

1.4 Brotli: mais 15-25% de economia sobre gzip

Se voce quer ir um passo alem, Brotli e uma escolha melhor. Ele e um algoritmo de compressao desenvolvido pelo Google e, no mesmo nivel de compressao, costuma comprimir 15-25% melhor que gzip. Ele e especialmente bom para recursos estaticos, ja que o suporte dos navegadores a Brotli hoje e bem amplo.

Mas ha uma pegadinha: Brotli nao e um modulo padrao do Nginx. Voce precisa compilar ou instalar um modulo dinamico extra. Eu uso o caminho de modulo dinamico oficial, sem recompilar o Nginx:

# Carregue os modulos antes, se estiver usando modulos dinamicos
load_module modules/ngx_http_brotli_filter_module.so;
load_module modules/ngx_http_brotli_static_module.so;

http {
    brotli on;
    brotli_comp_level 6;
    brotli_types text/plain text/css application/javascript application/json;
    brotli_min_length 256;
}

O nivel de compressao do Brotli vai de 1 a 11, mas recomendo 6. Niveis muito altos, como 11, aumentam bastante o tempo de compressao e nao sao ideais para conteudo gerado dinamicamente. Para arquivos estaticos, voce pode pre-comprimir no nivel maximo e deixar o Nginx retornar diretamente o arquivo .br.

Comparativo medido:

Metodo de compressaoHTML de 100KB depoisTempo de compressaoSuporte do navegador
gzip (level 6)25KB5msQuase todos
Brotli (level 6)18KB15ms95%+
Brotli (pre-compressao level 11)15KB095%+

Minha recomendacao: use Brotli level 4-6 para conteudo dinamico e pre-compressao para recursos estaticos. Se compilar o Nginx for trabalhoso demais, gzip ja resolve bem; afinal, 75% de compressao ja e um resultado excelente.

Capitulo 2: estrategia de cache - acelerando conteudo estatico

Cache e uma das partes com retorno mais direto em ajuste de desempenho. Com a configuracao certa, 95% das requisicoes podem ser respondidas diretamente pelo Nginx, sem chegar ao backend. Ja vi muitos sistemas em que o backend trabalhava no limite, enquanto o cache do Nginx praticamente nao ajudava. Nao era falta de cache; era cache configurado errado.

2.1 Como escolher entre proxy_cache e fastcgi_cache?

O Nginx oferece dois mecanismos de cache:

  • proxy_cache: armazena respostas de servidores upstream, ideal para proxy reverso, como servicos Node.js, Python e Go
  • fastcgi_cache: armazena respostas de processos FastCGI, ideal para cenarios com PHP-FPM

A escolha depende da sua stack de backend. Se usa PHP, va de fastcgi_cache. Se usa Node.js, Python, Go e similares, use proxy_cache. A logica de configuracao e quase igual; abaixo uso proxy_cache como exemplo.

2.2 Configuracao completa de proxy_cache

Primeiro defina o caminho de cache no bloco http:

http {
    proxy_cache_path /var/cache/nginx
                     levels=1:2
                     keys_zone=my_cache:10m
                     max_size=10g
                     inactive=60m
                     use_temp_path=off;
}

Explicando linha por linha:

  • levels=1:2: profundidade dos diretorios de cache. 1:2 cria dois niveis e evita arquivos demais em um unico diretorio
  • keys_zone=my_cache:10m: nome da zona de cache e tamanho de memoria para metadados. 10m armazena cerca de 80 mil chaves de cache
  • max_size=10g: limite total do cache. Ao ultrapassar, o Nginx remove entradas pelo algoritmo LRU
  • inactive=60m: caches sem acesso por 60 minutos sao limpos
  • use_temp_path=off: grava diretamente no diretorio de cache, evitando o custo de mover arquivos temporarios

Depois ative no server ou no location:

server {
    listen 80;
    server_name example.com;

    location / {
        proxy_pass http://backend;
        proxy_cache my_cache;

        # Configuracao de validade do cache
        proxy_cache_valid 200 302 10m;
        proxy_cache_valid 404 1m;
        proxy_cache_valid any 1m;

        # Desenho da chave de cache
        proxy_cache_key $scheme$request_method$host$request_uri;

        # Estrategia de degradacao
        proxy_cache_use_stale error timeout http_500 http_502 http_503 http_504;

        # Cabecalho de status do cache, util para depuracao
        add_header X-Cache-Status $upstream_cache_status;
    }
}

proxy_cache_valid e a configuracao central. Ela define quanto tempo cada status code fica em cache:

  • 200 302 10m: respostas normais em cache por 10 minutos
  • 404 1m: erros 404 em cache por 1 minuto, evitando que requisicoes maliciosas batam sem parar no backend
  • any 1m: outros status codes em cache por 1 minuto

2.3 Desenho da chave de cache e estrategia de invalidacao

A chave de cache, proxy_cache_key, decide quais requisicoes sao consideradas “iguais”. O padrao e $scheme$proxy_host$request_uri, mas eu prefiro declarar explicitamente:

proxy_cache_key $scheme$request_method$host$request_uri;

Assim a chave inclui protocolo, metodo HTTP, host e URI completa. Se o seu site suporta GET e POST ou varios dominios, essa configuracao e mais precisa.

Invalidacao de cache costuma dar dor de cabeca. As estrategias comuns sao:

  1. Expiracao por tempo: configurar proxy_cache_valid e deixar expirar automaticamente
  2. Limpeza ativa: usar proxy_cache_bypass para contornar o cache
  3. Purge de cache: no Nginx Plus comercial, usar proxy_cache_purge

Eu normalmente uso a segunda abordagem, controlando por cabecalho de requisicao:

# Ignora o cache com um cabecalho especifico
proxy_cache_bypass $http_x_nocache;

# Ou ignora por parametro especifico
proxy_cache_bypass $arg_nocache;

Quando precisar atualizar o cache, basta adicionar ?nocache=1 ou o cabecalho X-Nocache: 1.

2.4 Estrategia de degradacao: o backend caiu, mas o servico continua

proxy_cache_use_stale e uma configuracao muito util. Quando o backend falha ou da timeout, o Nginx pode retornar conteudo de cache expirado, em vez de devolver erro direto.

proxy_cache_use_stale error timeout http_500 http_502 http_503 http_504;

No ultimo Dia dos Solteiros, tivemos um problema durante a expansao do backend, e o servico de API passou a retornar 502 de forma intermitente. Felizmente, o cache estava com essa estrategia de degradacao configurada, e os usuarios quase nao sentiram impacto. O cache estava alguns minutos vencido, mas o conteudo ainda podia ser retornado normalmente. Depois que o backend voltou, o cache se atualizou sozinho.

Comparativo medido:

MetricaSem cacheCache hitModo degradado
Tempo de resposta150-200ms5-10ms5-10ms
QPS do backend1000500
Experiencia do usuarioNormalNormalUm pouco mais lenta

Com acerto de cache, o tempo de resposta cai de 200ms para 5-10ms, quase 20 vezes mais rapido. Esse ganho e muito visivel.

2.5 Microcache: conteudo dinamico tambem pode acelerar

Muita gente acha que conteudo dinamico nao pode ser armazenado em cache, mas da para usar microcache. A ideia e guardar paginas dinamicas por 1-5 segundos, reduzindo muito a pressao no backend durante picos de concorrencia.

Testei essa abordagem em uma pagina inicial de e-commerce. Ela tinha recomendacoes em tempo real e exibicao de estoque, ou seja, era totalmente dinamica. Mas durante uma grande promocao, o trafego disparou e o backend nao aguentou. Adicionei 5 segundos de microcache:

proxy_cache_path /var/cache/nginx/micro levels=1:2 keys_zone=micro:10m max_size=1g;

location / {
    proxy_cache micro;
    proxy_cache_valid 200 5s;  # Armazena por apenas 5 segundos
    proxy_cache_lock on;        # Evita penetracao concorrente durante atualizacao do cache
    proxy_cache_background_update on;  # Atualiza o cache de forma assincrona em segundo plano
}

proxy_cache_lock on e essencial. Quando o cache expira, a primeira requisicao busca dados novos no backend, enquanto as demais continuam esperando ou usando o cache antigo. Assim voce evita a situacao em que, no instante da expiracao, uma enxurrada de requisicoes atravessa o cache e atinge o backend ao mesmo tempo.

O efeito foi impressionante. O TTFB da home caiu de 800ms para cerca de 5ms, e o QPS do backend caiu de 2000 para 400. Os usuarios praticamente nao percebem 5 segundos de atraso no cache, mas a pressao sobre o servidor cai muito.

2.6 Requisicoes condicionais: uma tecnica para economizar banda

proxy_cache_revalidate faz o Nginx usar requisicoes condicionais, como If-Modified-Since e If-None-Match, para validar no backend se o cache expirou. Se o backend retornar 304 Not Modified, nao e preciso transferir o conteudo completo de novo; apenas os metadados do cache sao atualizados.

proxy_cache_revalidate on;

Essa configuracao e especialmente util em cenarios sensiveis a banda. Por exemplo, se o backend retorna arquivos grandes, mas o conteudo muda pouco, requisicoes condicionais economizam muito trafego.

Capitulo 3: configuracao de pool de conexoes - essencial para alta concorrencia

gzip e cache resolvem “como transferir mais rapido”; pool de conexoes resolve “como suportar mais requisicoes”. Na configuracao padrao, um unico worker do Nginx suporta no maximo 1024 conexoes concorrentes. Quando o trafego sobe, isso simplesmente nao basta.

3.1 worker_connections: calcule o limite com clareza

A formula para o numero maximo de conexoes concorrentes e:

Maximo de concorrencia = worker_processes x worker_connections

Suponha que seu servidor tenha 8 nucleos de CPU, worker_processes definido como 8, ou auto, e worker_connections definido como 4096:

Maximo de concorrencia = 8 x 4096 = 32768

Esse numero parece grande, mas lembre-se: cada requisicao normalmente ocupa duas conexoes, uma do cliente para o Nginx e outra do Nginx para o backend. Entao a concorrencia real de requisicoes fica por volta da metade desse valor.

A configuracao fica no bloco events:

events {
    worker_connections 4096;
    use epoll;
    multi_accept on;
}

use epoll ja e o padrao no Linux, entao nao precisa ser escrito explicitamente, mas deixa a configuracao mais clara. multi_accept on permite que o worker aceite varias novas conexoes de uma vez, reduzindo filas em cenarios de alta concorrencia.

3.2 keepalive do cliente: reutilizando conexoes e reduzindo custo

Estabelecer uma conexao TCP exige three-way handshake, e isso tem custo. keepalive permite reutilizar a conexao entre cliente e Nginx, sem recria-la a cada requisicao.

http {
    keepalive_timeout 65;
    keepalive_requests 1000;
}

keepalive_timeout 65: mantem a conexao por 65 segundos e fecha depois disso. O valor nao deve ser alto demais, para nao ocupar recursos do servidor em excesso; tambem nao deve ser baixo demais, para nao reduzir o ganho de reutilizacao. Algo entre 60 e 75 segundos e razoavel.

keepalive_requests 1000: um unico canal pode processar ate 1000 requisicoes. Se for baixo demais, as desconexoes ficam frequentes; se for alto demais, pode haver vazamento de recursos. Nos meus testes, 1000 foi um valor estavel.

Ha tambem um parametro mais novo, keepalive_time, que controla o tempo total de vida de uma conexao, independentemente de quantas requisicoes ela processou. Se seu servidor roda por muito tempo e conexoes acumuladas podem causar problemas de recursos, adicione:

keepalive_time 1h;  # Uma conexao pode viver no maximo 1 hora

3.3 upstream keepalive: pool de conexoes com o backend

Muita gente nao conhece essa configuracao, mas o efeito e claro. A conexao entre Nginx e backend tambem pode ser reutilizada, reduzindo o custo de criar TCP repetidamente.

upstream backend {
    server 127.0.0.1:8080;
    server 127.0.0.1:8081;

    keepalive 64;
    keepalive_timeout 60s;
    keepalive_requests 1000;
}

server {
    location / {
        proxy_pass http://backend;
        proxy_http_version 1.1;
        proxy_set_header Connection "";
    }
}

keepalive 64: mantem um pool de 64 conexoes ociosas. Ajuste esse valor conforme a quantidade de servicos backend; em geral, uso de 4 a 8 vezes o numero de servidores backend.

proxy_http_version 1.1 e proxy_set_header Connection "" sao obrigatorios. HTTP/1.1 suporta keepalive por padrao, e limpar o cabecalho Connection permite reutilizar a conexao. Sem essas duas linhas, upstream keepalive nao funciona.

Comparativo medido:

ConfiguracaoCriacoes de conexao/minutoCusto de CPUCenario recomendado
Sem upstream keepalive6000AltoBaixo trafego
keepalive 323000MedioTrafego medio
keepalive 641500BaixoAlto trafego

Depois de adicionar upstream keepalive, o custo de criacao de conexoes cai diretamente 50%. Essa configuracao e especialmente importante em cenarios de alta concorrencia.

3.4 Limite de descritores de arquivo: nao esqueca a camada do sistema

O numero de conexoes do Nginx e limitado pelo teto de descritores de arquivo do sistema. Se worker_connections esta em 4096, mas o sistema so permite abrir 1024 arquivos por processo, ainda nao vai bastar.

Verifique o limite atual:

ulimit -n

Se o valor retornado for menor que 65536, aumente na configuracao do sistema. Edite /etc/security/limits.conf:

* soft nofile 65536
* hard nofile 65536

Depois declare tambem na configuracao do Nginx:

worker_rlimit_nofile 65536;

Essa configuracao fica no bloco main, no mesmo nivel de worker_processes, para que o Nginx ja solicite descritores de arquivo suficientes ao iniciar.

3.5 Tabela rapida de parametros recomendados

Organizei uma referencia para diferentes cenarios:

ParametroBaixo trafego (<1000 QPS)Trafego medio (1000-5000 QPS)Alto trafego (>5000 QPS)
worker_processesautoautoauto
worker_connections102420484096
keepalive_timeout606575
keepalive_requests1005001000
upstream keepalive163264
worker_rlimit_nofile4096819265536

Isso e apenas ponto de partida. O ajuste real precisa considerar dados de teste de carga. Eu geralmente uso wrk ou ab, observo as curvas de conexoes e tempo de resposta e encontro o melhor valor.

Capitulo 4: otimizacoes avancadas - Thread Pools e reuseport

Os tres capitulos anteriores cobrem a maior parte dos cenarios. Se o seu trafego e muito alto, com meta de mais de 50K RPS por maquina, ou se voce e muito sensivel a latencia, ainda ha duas configuracoes avancadas que vale testar.

4.1 Thread Pools: quebrando o gargalo do sendfile

Por padrao, o Nginx usa um modelo single-threaded orientado a eventos, eficiente o suficiente para a maioria dos casos. Mas em servico de arquivos estaticos com alta concorrencia existe um gargalo escondido: sendfile e zero-copy, mas a leitura do arquivo e a escrita no socket acontecem no mesmo worker. Quando o IO de disco fica lento, o worker inteiro pode bloquear.

Thread Pools resolve esse problema. Ele separa leitura e envio de arquivos para um pool independente de threads; o worker apenas agenda as tarefas e nao fica preso em IO.

O blog oficial do Nginx tem um caso de teste com aumento de 9 vezes no desempenho de uma unica maquina. O cenario era download de arquivos de 1MB, antes limitado por IO de disco; depois de adicionar Thread Pools, o gargalo foi removido.

Configuracao:

http {
    thread_pool default threads=32 max_queue=65536;
    aio threads=default;
    sendfile_max_chunk 512k;
}

threads=32 indica 32 threads no pool, e max_queue=65536 e o numero maximo de tarefas em fila. aio threads=default ativa IO assincrono e define qual pool usar. sendfile_max_chunk 512k controla o tamanho de bloco enviado por vez, evitando que arquivos grandes ocupem o worker por tempo demais.

Mas Thread Pools nao e remedio universal. Se seu backend e principalmente dinamico, como um servico de API, e IO de disco nao e gargalo, ativar isso pode ate aumentar o custo de troca de contexto. Minha recomendacao: considere apenas para servico de arquivos estaticos e downloads grandes.

4.2 Socket Sharding (reuseport): reduzindo latencia de conexao

O Nginx 1.9.1 introduziu o parametro reuseport, que permite a varios workers escutar a mesma porta de forma independente, evitando disputa de lock entre workers.

No modo tradicional, todos os workers compartilham um socket de escuta. Quando chega uma nova conexao, eles disputam o accept mutex. Em alta concorrencia, essa disputa gera custo visivel e oscilacao de latencia.

Com reuseport:

server {
    listen 80 reuseport;
}

Cada worker tem seu proprio socket de escuta, e o kernel distribui novas conexoes automaticamente entre os workers. A disputa de lock desaparece.

Dados medidos, vindos do blog oficial do Nginx:

MetricaSem reuseportCom reuseport
Latencia media15.65ms12.35ms
Desvio padrao da latencia3.5ms1.2ms
Distribuicao de conexoesDesbalanceadaUniforme

A latencia cai 21%, e o mais importante e que a oscilacao diminui bastante. Essa configuracao aparece melhor em alta concorrencia, acima de 20K QPS; em baixo trafego, talvez voce quase nao perceba.

4.3 open_file_cache: cache de descritores de arquivo

Em cenarios de servico de arquivos estaticos, voce pode usar open_file_cache para armazenar descritores de arquivo e metadados, evitando consultar o disco a cada requisicao:

http {
    open_file_cache max=10000 inactive=30s;
    open_file_cache_valid 60s;
    open_file_cache_min_uses 2;
    open_file_cache_errors on;
}
  • max=10000: armazena informacoes de ate 10000 arquivos
  • inactive=30s: remove entradas sem acesso por 30 segundos
  • open_file_cache_valid 60s: valida a cada 60 segundos se o cache expirou
  • open_file_cache_errors on: tambem armazena estados de erro, como arquivo inexistente

Essa configuracao ajuda bastante em sites estaticos. Em conteudo dinamico, nao recomendo, porque arquivos podem mudar com frequencia e o cache pode fazer o conteudo nao atualizar.

Capitulo 5: template integrado - pronto para producao

Nos quatro capitulos anteriores, falamos dos principios. Agora deixo um template integrado de configuracao. Voce pode ajustar parametros conforme o cenario real, mas a estrutura e generica.

# Template de producao nginx.conf para cenario de alto trafego

user nginx;
worker_processes auto;
worker_rlimit_nofile 65536;

events {
    worker_connections 4096;
    use epoll;
    multi_accept on;
}

http {
    # Configuracao de compressao gzip
    gzip on;
    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 6;
    gzip_min_length 1000;
    gzip_types text/plain text/css text/xml text/javascript
               application/json application/javascript application/xml
               application/xml+rss application/xhtml+xml;
    gzip_disable "msie6";

    # Configuracao do caminho de cache
    proxy_cache_path /var/cache/nginx
                     levels=1:2
                     keys_zone=my_cache:10m
                     max_size=10g
                     inactive=60m
                     use_temp_path=off;

    # Configuracao de conexoes do cliente
    keepalive_timeout 65;
    keepalive_requests 1000;
    keepalive_time 1h;

    # Cache de arquivos, opcional para sites estaticos
    open_file_cache max=10000 inactive=30s;
    open_file_cache_valid 60s;
    open_file_cache_min_uses 2;

    # Grupo de servidores backend
    upstream backend {
        server 127.0.0.1:8080;
        server 127.0.0.1:8081;
        keepalive 64;
        keepalive_timeout 60s;
        keepalive_requests 1000;
    }

    server {
        listen 80 reuseport;
        server_name example.com;

        location / {
            proxy_pass http://backend;
            proxy_http_version 1.1;
            proxy_set_header Connection "";

            # Configuracao de cache
            proxy_cache my_cache;
            proxy_cache_valid 200 302 10m;
            proxy_cache_valid 404 1m;
            proxy_cache_key $scheme$request_method$host$request_uri;
            proxy_cache_use_stale error timeout http_500 http_502 http_503 http_504;
            proxy_cache_revalidate on;

            # Cabecalho de resposta para depuracao
            add_header X-Cache-Status $upstream_cache_status;
        }
    }
}

Configuracoes diferentes para tres tipos de cenario

E-commerce: a home e as paginas de produto mudam com frequencia, entao o tempo de cache deve ser menor; 10-15 minutos costuma bastar. upstream keepalive deve ser maior, porque ha muitas consultas ao banco e mais pressao no backend. Durante grandes promocoes, microcache pode ser ativado com 1-3 segundos.

Servico de API: a exigencia de tempo real e maior, entao proxy_cache_valid pode ficar em apenas 1-5 minutos. gzip funciona muito bem em JSON, entao nao deixe de configurar. Se Brotli puder ser instalado, melhor ainda: respostas de API sao pequenas, mas frequentes, e a compressao compensa.

Site estatico: HTML, CSS e JS quase nao mudam, entao o cache pode ficar em 1 hora ou mais. gzip oferece grande ganho, porque arquivos estaticos costumam ter muito texto. open_file_cache e Thread Pools podem ser ativados; cache de descritores de arquivo e IO assincrono ajudam bastante em conteudo estatico.

Comparativo de ganho de desempenho (versao atualizada)

Item de configuracaoAntesDepoisMelhoria
Volume transferido de HTML (gzip)100KB25KB75% ↓
Volume transferido de HTML (Brotli)100KB18KB82% ↓
Tempo de resposta de API (cache hit)200ms8ms96% ↓
Capacidade de conexoes concorrentes100040004x ↑
RPS (apos otimizacao em uma maquina)10K50K-80K5-8x ↑
TTFB (reuseport)15.65ms12.35ms21% ↓

Esses dados combinam meus testes reais e documentacao oficial. O efeito final depende da configuracao do servidor, do ambiente de rede e das caracteristicas do negocio; validar com teste de carga e essencial.

Capitulo 6: problemas comuns e diagnostico

Algumas perguntas frequentes que ja encontrei, com solucoes diretas.

Q: gzip nao funciona, e a resposta nao tem Content-Encoding: gzip

Confira tres pontos:

  1. Se gzip on esta no nivel correto de configuracao, normalmente o bloco http
  2. Se gzip_types inclui o tipo MIME da sua resposta
  3. Se o tamanho da resposta e maior que gzip_min_length

Teste com curl: curl -H "Accept-Encoding: gzip" -I http://your-site.com

Q: a taxa de acerto do cache e baixa, e X-Cache-Status aparece quase sempre como MISS

Causas comuns:

  • A chave de cache foi mal desenhada, e cada requisicao e tratada como “diferente”
  • proxy_cache_valid esta curto demais, e o cache expira antes de ser aproveitado
  • O backend retorna cabecalhos como Cache-Control: no-cache ou Set-Cookie

Verifique os cabecalhos de resposta e confirme que nao ha diretivas proibindo cache.

Q: worker_connections nao e suficiente e aparecem erros 502

Veja os logs de erro do Nginx. Se houver worker_connections are not enough, a concorrencia ultrapassou o limite.

Solucoes:

  1. Aumentar o valor de worker_connections
  2. Verificar se ha vazamento de conexoes, como keepalive mal configurado
  3. Considerar adicionar mais servidores para balanceamento de carga

Q: gzip entra em conflito com sendfile?

Nao. gzip comprime o conteudo da resposta; sendfile trata a forma de transferencia do arquivo. Os dois podem ser ativados ao mesmo tempo. O unico ponto de atencao e que gzip em conteudo dinamico precisa comprimir em memoria e nao passa por sendfile; arquivos estaticos pre-comprimidos podem usar sendfile diretamente.

Q: upstream keepalive nao funciona?

Dois motivos comuns:

  1. Faltou proxy_http_version 1.1; HTTP/1.0 nao suporta keepalive por padrao
  2. Faltou proxy_set_header Connection ""; se esse cabecalho nao for limpo, a conexao sera fechada

Essas duas linhas devem ficar no bloco location, nao no bloco upstream.

Q: reuseport mostra o erro “duplicate listen options”?

reuseport so pode ser declarado uma vez em uma linha listen; nao pode ser repetido em outros lugares. Garanta que cada bloco server tenha no maximo um listen com reuseport. Se varios servers escutam a mesma porta, cada um precisa declarar reuseport de forma independente.

Q: o uso de memoria esta alto e o servidor entra em OOM com frequencia

Possiveis causas:

  • keys_zone em proxy_cache_path esta grande demais
  • Existem arquivos de cache demais, elevando o uso de memoria mapeada
  • O pool keepalive esta grande demais, e conexoes ociosas ocupam recursos

Reduza esses parametros conforme necessario ou aumente a memoria do servidor.


Para fechar

Depois de tudo isso, o nucleo do ajuste de desempenho do Nginx se resume a tres coisas: compressao, cache e pool de conexoes. Com alguns recursos avancados, como Brotli, microcache, Thread Pools e reuseport, da para levar uma unica maquina de 10K RPS para 50K-80K.

Ajuste de desempenho nao e algo que se faz uma vez e esquece. Recomendo seguir esta ordem:

  1. Ative gzip primeiro: menor mudanca, retorno mais direto, resolvido em dez minutos
  2. Depois configure cache: desenhe a estrategia conforme o cenario de negocio; da para concluir em um dia
  3. Em seguida ajuste o pool de conexoes: exige validacao com teste de carga e faz sentido quando o trafego ja esta estavel
  4. Por fim teste otimizacoes avancadas: Thread Pools e reuseport so valem quando o trafego e realmente alto

Depois de cada mudanca, valide com teste de carga. wrk ou ab servem bem; observe tempo de resposta, QPS e taxa de erro. Nao ajuste por sensacao: deixe os dados falarem.

Segue uma checklist final para conferir:

  • gzip esta ativado, com tipos MIME completos
  • gzip_comp_level esta entre 4 e 6, equilibrando CPU e taxa de compressao
  • Brotli esta configurado, se disponivel
  • proxy_cache_path esta configurado, com tamanho de cache razoavel
  • proxy_cache_valid foi definido conforme o cenario de negocio
  • Microcache esta ativado em cenarios dinamicos de alta concorrencia
  • proxy_cache_use_stale esta configurado como estrategia de degradacao
  • worker_connections esta em 4096 ou mais
  • keepalive_timeout esta entre 60 e 75 segundos
  • upstream keepalive esta configurado, incluindo HTTP/1.1 e cabecalho Connection
  • reuseport esta ativado em cenarios de alto trafego
  • O limite de descritores de arquivo foi aumentado para 65536
  • A resposta inclui X-Cache-Status para depuracao

E isso. Se tiver duvidas, deixe um comentario; respondo quando puder.

FAQ

A compressao gzip nao funciona e o cabecalho de resposta nao tem Content-Encoding: gzip. O que fazer?
Confira tres pontos: 1) se gzip on esta no bloco http; 2) se gzip_types inclui o tipo MIME da resposta; 3) se o corpo da resposta e maior que gzip_min_length. Valide com curl -H "Accept-Encoding: gzip" -I.
A taxa de acerto do cache e muito baixa, e X-Cache-Status aparece quase sempre como MISS. Como investigar?
Causas comuns: chave de cache mal desenhada, proxy_cache_valid curto demais, ou backend retornando Cache-Control: no-cache ou Set-Cookie. Verifique os cabecalhos de resposta para confirmar que nao ha diretivas que impedem cache.
worker_connections nao e suficiente e aparecem erros 502. Como resolver?
Verifique os logs de erro do Nginx. Se aparecer "worker_connections are not enough", a concorrencia passou do limite. Solucoes: aumentar worker_connections, verificar vazamento de conexoes ou adicionar servidores para balanceamento de carga.
Quais sao as causas comuns de upstream keepalive nao funcionar?
Dois erros comuns: 1) esquecer proxy_http_version 1.1, pois HTTP/1.0 nao suporta keepalive por padrao; 2) esquecer proxy_set_header Connection "". Essas duas linhas precisam ficar no bloco location.
Como escolher entre Brotli e gzip?
Brotli comprime 15-25% melhor que gzip, mas exige instalar modulo adicional. Use Brotli level 4-6 para conteudo dinamico e pre-compressao level 11 para recursos estaticos. Se compilar Nginx for trabalhoso, gzip level 6 ja resolve bem e pode chegar a 75% de compressao.
Quais parametros voce recomenda para diferentes niveis de trafego?
Baixo trafego (<1000 QPS): worker_connections 1024, keepalive 16; trafego medio (1000-5000 QPS): 2048, keepalive 32; alto trafego (>5000 QPS): 4096, keepalive 64. Na pratica, ajuste com base em testes de carga.

22 min de leitura · Publicado em: 15 mai 2026 · Atualizado em: 14 jul 2026

Comentários

Entre com GitHub para comentar

Easton BlogEaston Blog