SSD Nodes Learn 🎉 VPS desde $5.50/mês
Guias Matt ConnorPor Matt Connor · Atualizado 2026-08-13

Nginx, Caddy ou Traefik: qual proxy escolher?

Compare Nginx, Caddy e Traefik em um VPS com um IP público: certificados TLS, custo de configuração por app, WebSockets e roteamento no Docker.

Nginx vs Caddy vs Traefik: a resposta curta

Nginx, Caddy e Traefik desempenham a mesma função como reverse proxy: escutam na porta 443, leem o nome do host em cada pedido e encaminham o pedido para o serviço correto no seu VPS. Qualquer uma das três ferramentas permite colocar quatro aplicações self-hosted atrás de um único endereço IP público, e todas têm desempenho suficiente para que as suas aplicações sejam o componente mais lento. As diferenças estão na forma como cada uma obtém um certificado TLS (transport layer security) e na quantidade de configuração necessária para cada aplicação adicional. A outra diferença torna-se evidente mais tarde, no dia em que precisar de algo que os tutoriais comuns não explicam.

Escolha Caddy se quiser que o HTTPS seja tratado automaticamente e os seus serviços forem aplicações web comuns. Escolha Traefik se tudo correr em Docker Compose e adicionar um novo serviço a cada poucas semanas. Escolha Nginx se já o utilizar ou se precisar de cache de respostas, certificados de cliente, encaminhamento de TCP bruto ou uma configuração existente extensa que prefira não reescrever.

Como cada um obtém um certificado TLS?

Este eixo decide a escolha da maioria das pessoas, por isso comece por aqui. Os três acabam por utilizar o mesmo certificado da mesma autoridade. O trabalho necessário para o obter não é igual.

O Caddy solicita o certificado porque foi definido um nome de host. Escreva app.example.com como endereço do site e o Caddy solicita um certificado através do ACME (automatic certificate management environment) à Let's Encrypt. Se isso falhar, recorre à ZeroSSL. Também serve o redirecionamento de HTTP para HTTPS na porta 80 e renova o certificado automaticamente. Não é necessária uma segunda ferramenta nem um temporizador para verificar o processo. Os certificados ficam no diretório de dados do utilizador caddy, /var/lib/caddy/.local/share/caddy numa instalação através de pacote. Inclua esse caminho nos backups ou aceite uma nova emissão depois de reconstruir o servidor. Para um nome de host que não seja público, tls internal assina com a própria autoridade de certificação local do Caddy. O resultado é equivalente a criar um certificado autoassinado no Ubuntu, mas a renovação fica a cargo do Caddy.

O Nginx não tem um cliente ACME. O Certbot obtém o certificado e o plugin --nginx reescreve o seu server block para adicionar o listener na porta 443 e o redirecionamento. A renovação é executada por um temporizador systemd instalado pelo pacote. Existem, portanto, dois componentes e dois pontos a verificar: systemctl list-timers | grep certbot mostra se o temporizador existe e sudo certbot renew --dry-run confirma que o processo de renovação continua a funcionar. O procedimento passo a passo está em Certbot no Ubuntu 24.04 com Nginx. A mesma ferramenta também permite obter um certificado wildcard através do desafio DNS-01 quando existem mais subdomínios do que aqueles que pretende listar.

O Traefik inclui o próprio cliente ACME. Configure um resolver de certificados na configuração estática. Depois, cada router pode utilizá-lo. Todo o estado, incluindo a chave da conta e os certificados, fica num único ficheiro acme.json. O Traefik recusa utilizar esse ficheiro se este puder ser lido por alguém que não seja o respetivo proprietário. Também informa disso antes de remover o resolver:

The ACME resolver "le" is skipped from the resolvers list because: unable to get ACME account: permissions 660 for /letsencrypt/acme.json are too open, please use 600

Monte um diretório e deixe o Traefik criar o ficheiro. Crie primeiro o ficheiro com touch. Assim, ele herda o seu umask, que é a forma mais comum de cumprir essa exigência.

Há um ponto comum aos três. O desafio HTTP-01 precisa que a porta 80 esteja acessível a partir da Internet, porque a autoridade de certificação estabelece uma ligação de retorno a essa porta. Se abrir apenas a porta 443, a emissão falha de uma forma que parece um problema de DNS.

A mesma tarefa de encaminhamento para duas aplicações em três configurações

A tarefa: app.example.com é encaminhado para um serviço em 127.0.0.1:8080, e files.example.com é encaminhado para um serviço em 127.0.0.1:8081, ambos através de HTTPS. Veja a configuração completa em cada proxy, para que a diferença de verbosidade seja visível em vez de apenas afirmada.

Nginx

# /etc/nginx/sites-available/app.example.com
server {
    listen 80;
    server_name app.example.com;

    location / {
        proxy_pass http://127.0.0.1:8080;
        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;
    }
}

Em seguida, crie o link, teste a configuração, faça o reload e adicione o certificado.

sudo ln -s /etc/nginx/sites-available/app.example.com /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
sudo certbot --nginx -d app.example.com

nginx -t que imprime syntax is ok e test is successful é a verificação a executar antes de cada reload. A segunda aplicação usa o mesmo bloco, com o hostname e a porta alterados. As linhas proxy_set_header não são decorativas: quando proxy_pass indica um endereço, o nginx envia Host: 127.0.0.1:8080 ao upstream por predefinição. Assim, uma aplicação que cria URLs absolutos a partir do cabeçalho Host enviará os utilizadores para localhost.

Caddy

app.example.com {
	reverse_proxy 127.0.0.1:8080
}

files.example.com {
	reverse_proxy 127.0.0.1:8081
}
sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddy

Esse é o ficheiro completo. reverse_proxy define X-Forwarded-For, X-Forwarded-Proto e X-Forwarded-Host automaticamente. Por predefinição, também ignora os valores enviados pelo cliente nesses cabeçalhos, para que um pedido não possa fornecer ao backend uma origem falsa. Os certificados, o redirecionamento da porta 80 e a renovação resultam dos dois endereços dos sites. Nenhuma outra linha do ficheiro os solicita.

Traefik

O Traefik precisa de uma configuração estática antes de encaminhar qualquer pedido. Como serviço Compose, com a tag da imagem atual em agosto de 2026:

services:
  traefik:
    image: traefik:v3.7
    command:
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      - "--certificatesresolvers.le.acme.email=you@example.com"
      - "--certificatesresolvers.le.acme.storage=/letsencrypt/acme.json"
      - "--certificatesresolvers.le.acme.httpchallenge.entrypoint=web"
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./letsencrypt:/letsencrypt

Depois, cada aplicação define o seu próprio encaminhamento através de labels, no respetivo ficheiro Compose:

    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.app.rule=Host(`app.example.com`)"
      - "traefik.http.routers.app.entrypoints=websecure"
      - "traefik.http.routers.app.tls.certresolver=le"
      - "traefik.http.services.app.loadbalancer.server.port=8080"

loadbalancer.server.port é a porta dentro do contentor, não uma porta publicada, porque o Traefik acede ao contentor através de uma rede Docker partilhada. A aplicação não precisa de nenhuma linha ports:, e esse é o benefício principal: apenas o Traefik é publicado. A configuração completa, incluindo a rede partilhada e o middleware de redirecionamento, está em encaminhar várias aplicações com Traefik e Docker Compose.

Quanto de configuração custa cada aplicação adicional?

ChartNon-blank config lines for the same two-app routing job
The data behind this chart
[
  {
    "tool": "Nginx",
    "proxy_setup_lines": 0,
    "lines_per_app": 11
  },
  {
    "tool": "Caddy",
    "proxy_setup_lines": 0,
    "lines_per_app": 3
  },
  {
    "tool": "Traefik",
    "proxy_setup_lines": 17,
    "lines_per_app": 5
  }
]

A contagem é feita a partir dos blocos acima. O bloco de servidor do Nginx tem 11 linhas não vazias, e é necessário escrevê-lo novamente para cada hostname. O bloco de site do Caddy tem 3 linhas. O Traefik precisa de 17 linhas de configuração estática antes de servir um único pedido e, depois, de 5 labels por aplicação.

Analise o compromisso, não apenas o vencedor. O Traefik custa mais antes da primeira aplicação e menos por aplicação a partir daí. Os dois totais encontram-se aproximadamente no terceiro site. Abaixo desse ponto, a configuração estática é uma sobrecarga desnecessária. Acima dele, os labels ganham vantagem e continuam a aumentá-la, porque o encaminhamento fica junto do serviço que encaminha. Se eliminar o serviço, a respetiva rota também é eliminada. É precisamente neste ponto que um ficheiro de configuração central é fraco: deixa blocos de servidor obsoletos para aplicações que deixaram de existir há meses.

A contagem de linhas também favorece o Nginx. Cada um desses blocos exige um symlink, um nginx -t, um reload e uma execução do certbot, enquanto a alteração no Caddy exige um reload e a alteração no Traefik não exige nenhum comando. Os três fazem reload sem interromper as ligações ativas. A diferença está no número de passos separados de que tem de se lembrar à uma da manhã.

Qual deles conhece os seus containers?

O Traefik monitoriza o socket do Docker e cria routers a partir das labels dos containers à medida que estes iniciam e param. Nenhum dos outros faz isso. O Nginx e o Caddy precisam de uma alteração de configuração e de um reload quando aparece um novo container. Também precisam de um endereço que consigam alcançar: uma porta publicada em loopback ou uma rede Docker partilhada à qual o proxy esteja ligado.

Essa funcionalidade tem um custo, que deve ser indicado claramente. O Traefik lê /var/run/docker.sock. Qualquer pessoa que consiga comunicar com esse socket pode iniciar um container com o sistema de ficheiros do host montado dentro dele. Isso dá acesso root ao host. Montá-lo apenas para leitura reduz o risco, mas não o elimina. Se isso for relevante para o seu modelo de ameaças, coloque um socket proxy entre ambos. Esse proxy deve expor apenas os endpoints de listagem de containers de que o Traefik precisa.

O Caddy pode fazer descoberta baseada em labels através de um plugin da comunidade. No entanto, os plugins do Caddy são incluídos durante a compilação. Por isso, terá de criar um binário personalizado ou uma imagem personalizada com xcaddy. Depois, ficará responsável por essa compilação e pelas respetivas atualizações. Para três ou quatro serviços, editar um Caddyfile dá menos trabalho.

WebSockets e streaming: o que falha e porquê

É o Nginx que precisa de configuração adicional. Uma ligação WebSocket começa como um pedido HTTP que transporta Upgrade: websocket, e o nginx não encaminha cabeçalhos hop-by-hop para o upstream sem essa indicação.

# /etc/nginx/conf.d/upgrade-map.conf
map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

Depois, dentro do bloco location, são necessárias três linhas, todas presentes:

        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;

Se as omitir, a consola do browser apresenta WebSocket connection to 'wss://app.example.com/ws' failed, enquanto o log do backend mostra um GET normal. map existe porque um Connection: upgrade definido de forma fixa seria enviado em todos os pedidos, incluindo os pedidos normais, que deveriam indicar close.

Há outras duas predefinições do Nginx que causam problemas. proxy_read_timeout é de 60 segundos e aplica-se ao túnel depois do upgrade. Por isso, um WebSocket sem tráfego durante um minuto é fechado pelo proxy. Os eventos enviados pelo servidor chegam atrasados ou em blocos até definir proxy_buffering off; nessa localização, porque o nginx mantém a resposta no buffer enquanto a página espera por ela.

O Caddy executa o upgrade e muda a ligação para um túnel bidirecional sem qualquer diretiva. Também envia imediatamente a resposta quando text/event-stream ou quando o tamanho não é conhecido, por isso o streaming funciona sem alterações. O Traefik encaminha os upgrades e não coloca as respostas em buffer, a menos que adicione manualmente o middleware buffering. Se os seus serviços incluírem chat, um terminal Web, acompanhamento de logs ou dashboards em tempo real, esta diferença afeta diretamente a quantidade de configuração que terá de escrever e depurar.

O bloco completo de servidor Nginx, incluindo WebSockets e SSE
map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

server {
    listen 80;
    server_name app.example.com;
    client_max_body_size 64m;

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_http_version 1.1;
        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;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_read_timeout 3600s;
        proxy_buffering off;
    }
}

map pertence ao contexto http, não dentro de server. Por isso, mantenha-o no seu próprio ficheiro em /etc/nginx/conf.d/. Desative proxy_buffering apenas nas localizações que fazem streaming, porque o buffering permite ao nginx libertar rapidamente o worker do backend nas respostas normais. O Certbot reescreve este bloco quando o executa, por isso leia novamente o ficheiro depois.

O que acontece quando precisa de algo fora do comum?

É aqui que o Nginx justifica as suas linhas adicionais.

  • Certificados de cliente, também chamados mTLS (TLS mútuo), em que o cliente também tem de apresentar um certificado. O Nginx requer ssl_client_certificate /etc/ssl/ca.pem; e ssl_verify_client on; no bloco do servidor. O Caddy requer um bloco client_auth dentro de tls. As labels do Traefik não conseguem expressar esta configuração: defina uma opção TLS num file provider e associe-lhe o router com traefik.http.routers.app.tls.options=mtls@file. O modelo baseado exclusivamente em labels tem uma exceção na primeira vez que precisa desta funcionalidade.
  • Uploads grandes. Por predefinição, o Nginx limita os corpos dos pedidos a 1 MB. Um upload maior devolve 413 Request Entity Too Large, e o log de erros indica client intended to send too large body. Aumente client_max_body_size. O Caddy e o Traefik não definem nenhum limite para o corpo por predefinição, por isso o pedido chega à aplicação e é o limite da própria aplicação que decide.
  • Cache de respostas. O Nginx tem proxy_cache, e esta funcionalidade é madura. O Caddy requer um plugin compilado. A build open source do Traefik não tem qualquer cache HTTP, o que surpreende quem assume que todos os proxies fazem cache.
  • TCP ou UDP em bruto, para uma porta de base de dados ou um servidor de jogos. O Nginx tem o módulo stream. O Traefik tem routers TCP e UDP nos seus próprios entrypoints. O Caddy requer outro plugin e, portanto, outra build personalizada.
  • Um servidor Web já atrás do proxy. Se o serviço for uma aplicação PHP clássica, uma stack LAMP no Ubuntu 24.04 já inclui o Apache, e colocar um proxy à frente cria dois locais que definem cabeçalhos e dois que podem reescrever um URL. Decida qual termina o TLS e mantenha o outro em HTTP simples, associado ao loopback.

A armadilha da firewall que resulta desta escolha

O objetivo de um reverse proxy é manter abertas apenas as portas 80 e 443. O Docker desfaz isso silenciosamente. Publicar uma porta com -p 8080:80 escreve uma regra DNAT na tabela nat. Essa regra é avaliada antes das regras INPUT geridas pelo ufw. Por isso, ufw deny 8080 não a bloqueia, e a aplicação fica exposta na Internet pública ao lado do proxy que configurou cuidadosamente. Associe as portas publicadas ao loopback com 127.0.0.1:8080:80 ou remova ports: por completo. Depois, deixe o proxy alcançar o contentor através de uma rede Docker, como faz o exemplo do Traefik acima. O mecanismo e a correção estão explicados em por que motivo as portas publicadas pelo Docker ignoram o ufw.

Teste a partir de uma máquina que não seja o VPS, porque uma verificação executada no próprio servidor tem sempre sucesso:

curl --max-time 5 http://your.server.address:8080

O resultado pretendido é Connection refused ou um timeout. Uma resposta HTTP significa que essa aplicação está acessível sem passar pelo proxy. Nesse caso, tudo o que configurou acima é apenas decorativo.

Qual proxy você deve escolher?

Principalmente sites estáticos, além de uma ou duas aplicações: Caddy. O HTTPS automático elimina a tarefa recorrente mais trabalhosa. A configuração continua curta o suficiente para ser lida numa única tela, e um site estático consiste numa linha root e numa linha file_server dentro do mesmo bloco de site. O custo é ter menos respostas prontas para copiar e colar quando algo inesperado falha.

Um homelab com docker-compose que você continua a expandir: Traefik. A partir do terceiro serviço, usar labels dá menos trabalho do que editar um ficheiro central, e eliminar um serviço remove também a sua rota. Reserve uma tarde para a configuração inicial, porque entrypoints, routers, services e middlewares são vocabulário novo. Um erro de digitação num label normalmente aparece como um 404 do Traefik, e não como uma falha ao iniciar. Por isso, consulte docker logs traefik para ver o erro de análise antes de concluir que a aplicação está avariada.

Uma configuração Nginx existente ou qualquer requisito da lista acima: Nginx. O Nginx já oferece uma solução para a colocação de respostas em cache e para certificados de cliente, e quase todos os guias de terceiros partem do princípio de que ele está a ser utilizado. O custo é ter de configurar os certificados e o suporte para WebSocket, em vez de os obter por predefinição.

Há uma regra que se aplica independentemente da sua escolha. Exatamente um processo escuta na interface pública, e todos os restantes escutam no loopback ou numa rede Docker privada.

FAQ

Qual é o melhor reverse proxy para algumas aplicações Docker num único VPS?

Para três ou quatro serviços que adiciona ocasionalmente, o Traefik compensa, porque cada aplicação inclui os seus próprios labels de encaminhamento e não exige alterações num ficheiro central. Se os serviços forem estáveis e o seu principal objetivo for deixar de gerir o HTTPS, o Caddy exige menos aprendizagem e apresenta menos pontos de falha. Escolha o Nginx se já o conhecer ou se precisar de uma funcionalidade que os outros dois não oferecem, como cache de respostas ou um listener TCP simples.

O Caddy realmente não precisa de configuração de certificados?

No caso normal, não. Definir um hostname público como endereço do site é toda a configuração necessária: o Caddy solicita o certificado através de ACME, serve o redirecionamento da porta 80 e renova o certificado antes de expirar. Ainda assim, duas condições têm de ser cumpridas. A porta 80 tem de estar acessível a partir da Internet para o desafio HTTP-01, e o registo DNS A ou AAAA do hostname já tem de apontar para o VPS, porque a autoridade certificadora resolve o nome e estabelece uma ligação de retorno ao VPS.

Posso executar Nginx e Traefik no mesmo VPS?

Não nas mesmas portas. O que arrancar em segundo lugar falha ao associar-se às portas, e o nginx apresenta bind() to 0.0.0.0:443 failed (98: Address already in use), enquanto o Traefik regista um erro de associação semelhante e termina. Execute um proxy nas portas 80 e 443 e coloque todo o restante tráfego atrás dele. Se estiver a fazer uma migração, mova os hostnames um de cada vez: faça o proxy frontal encaminhar para o proxy antigo através de uma porta de loopback até o último site ter sido migrado.

Por que motivo os meus websockets são desligados após 60 segundos atrás do Nginx?

proxy_read_timeout tem o valor predefinido de 60 segundos e aplica-se ao túnel depois de a atualização estar concluída. Por isso, uma ligação sem tráfego durante um minuto é fechada pelo proxy e não pela aplicação. Aumente esse valor nessa localização com proxy_read_timeout 3600s; ou faça a aplicação enviar um frame de ping a cada 30 segundos. O Caddy e o Traefik não fecham ligações atualizadas inativas através de um temporizador de um minuto. Por isso, a mesma aplicação pode parecer estável atrás deles e instável atrás do Nginx.