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

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 compressao | Compressao HTML | Tempo de CPU (ms) | Cenario recomendado |
|---|---|---|---|
| 1 | 65% | 2 | CPU apertada |
| 4 | 72% | 3 | Equilibrado (recomendado) |
| 6 | 75% | 5 | Banda limitada (recomendado) |
| 9 | 78% | 12 | Cenario 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 arquivo | Tamanho original | Depois da compressao | Taxa de compressao |
|---|---|---|---|
| HTML | 100KB | 20-25KB | 75-80% |
| CSS | 80KB | 24-28KB | 65-70% |
| JavaScript | 120KB | 36-42KB | 65-70% |
| JSON API | 50KB | 20-25KB | 50-60% |
| Imagem/video | Ja comprimido | Sem sentido | 0-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 compressao | HTML de 100KB depois | Tempo de compressao | Suporte do navegador |
|---|---|---|---|
| gzip (level 6) | 25KB | 5ms | Quase todos |
| Brotli (level 6) | 18KB | 15ms | 95%+ |
| Brotli (pre-compressao level 11) | 15KB | 0 | 95%+ |
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 diretoriokeys_zone=my_cache:10m: nome da zona de cache e tamanho de memoria para metadados. 10m armazena cerca de 80 mil chaves de cachemax_size=10g: limite total do cache. Ao ultrapassar, o Nginx remove entradas pelo algoritmo LRUinactive=60m: caches sem acesso por 60 minutos sao limposuse_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 minutos404 1m: erros 404 em cache por 1 minuto, evitando que requisicoes maliciosas batam sem parar no backendany 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:
- Expiracao por tempo: configurar
proxy_cache_valide deixar expirar automaticamente - Limpeza ativa: usar
proxy_cache_bypasspara contornar o cache - 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:
| Metrica | Sem cache | Cache hit | Modo degradado |
|---|---|---|---|
| Tempo de resposta | 150-200ms | 5-10ms | 5-10ms |
| QPS do backend | 1000 | 50 | 0 |
| Experiencia do usuario | Normal | Normal | Um 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:
| Configuracao | Criacoes de conexao/minuto | Custo de CPU | Cenario recomendado |
|---|---|---|---|
| Sem upstream keepalive | 6000 | Alto | Baixo trafego |
| keepalive 32 | 3000 | Medio | Trafego medio |
| keepalive 64 | 1500 | Baixo | Alto 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:
| Parametro | Baixo trafego (<1000 QPS) | Trafego medio (1000-5000 QPS) | Alto trafego (>5000 QPS) |
|---|---|---|---|
| worker_processes | auto | auto | auto |
| worker_connections | 1024 | 2048 | 4096 |
| keepalive_timeout | 60 | 65 | 75 |
| keepalive_requests | 100 | 500 | 1000 |
| upstream keepalive | 16 | 32 | 64 |
| worker_rlimit_nofile | 4096 | 8192 | 65536 |
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:
| Metrica | Sem reuseport | Com reuseport |
|---|---|---|
| Latencia media | 15.65ms | 12.35ms |
| Desvio padrao da latencia | 3.5ms | 1.2ms |
| Distribuicao de conexoes | Desbalanceada | Uniforme |
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 arquivosinactive=30s: remove entradas sem acesso por 30 segundosopen_file_cache_valid 60s: valida a cada 60 segundos se o cache expirouopen_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 configuracao | Antes | Depois | Melhoria |
|---|---|---|---|
| Volume transferido de HTML (gzip) | 100KB | 25KB | 75% ↓ |
| Volume transferido de HTML (Brotli) | 100KB | 18KB | 82% ↓ |
| Tempo de resposta de API (cache hit) | 200ms | 8ms | 96% ↓ |
| Capacidade de conexoes concorrentes | 1000 | 4000 | 4x ↑ |
| RPS (apos otimizacao em uma maquina) | 10K | 50K-80K | 5-8x ↑ |
| TTFB (reuseport) | 15.65ms | 12.35ms | 21% ↓ |
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:
- Se
gzip onesta no nivel correto de configuracao, normalmente o bloco http - Se
gzip_typesinclui o tipo MIME da sua resposta - 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_validesta curto demais, e o cache expira antes de ser aproveitado- O backend retorna cabecalhos como
Cache-Control: no-cacheouSet-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:
- Aumentar o valor de
worker_connections - Verificar se ha vazamento de conexoes, como keepalive mal configurado
- 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:
- Faltou
proxy_http_version 1.1; HTTP/1.0 nao suporta keepalive por padrao - 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_zoneemproxy_cache_pathesta grande demais- Existem arquivos de cache demais, elevando o uso de memoria mapeada
- O pool
keepaliveesta 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:
- Ative gzip primeiro: menor mudanca, retorno mais direto, resolvido em dez minutos
- Depois configure cache: desenhe a estrategia conforme o cenario de negocio; da para concluir em um dia
- Em seguida ajuste o pool de conexoes: exige validacao com teste de carga e faz sentido quando o trafego ja esta estavel
- 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?
A taxa de acerto do cache e muito baixa, e X-Cache-Status aparece quase sempre como MISS. Como investigar?
worker_connections nao e suficiente e aparecem erros 502. Como resolver?
Quais sao as causas comuns de upstream keepalive nao funcionar?
Como escolher entre Brotli e gzip?
Quais parametros voce recomenda para diferentes niveis de trafego?
22 min de leitura · Publicado em: 15 mai 2026 · Atualizado em: 14 jul 2026
Guia prático Nginx
Se você chegou pela busca, o caminho mais rápido é ir para o post anterior ou próximo desta série.



Comentários
Entre com GitHub para comentar