SSD Nodes Learn Hosting plans →
Guias Matt ConnorPor Matt Connor · Atualizado 2026-08-24

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 fazem o mesmo trabalho como reverse proxy: escutam na porta 443, leem o nome de anfitrião de cada pedido e encaminham-no para o serviço correto no seu VPS. Qualquer um dos três permite colocar quatro aplicações self-hosted atrás de um único endereço IP público, e todos são suficientemente rápidos para que as suas aplicações sejam o componente mais lento. O que muda é a forma como cada um obtém um certificado TLS (transport layer security) e o esforço de configuração que cada aplicação adicional exige. A outra diferença surge mais tarde, no dia em que precisar de algo que os tutoriais comuns não explicam.

Escolha Caddy se quiser que o HTTPS seja gerido automaticamente e se os seus serviços forem aplicações web comuns. Escolha Traefik se tudo correr no Docker Compose e se 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 TCP direto ou uma configuração existente extensa que preferiria não reescrever.

Como cada um obtém um certificado TLS?

Este fator decide a escolha para a 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 indicou um hostname. Escreva app.example.com como endereço do site e o Caddy solicita um certificado através do ACME (ambiente de gestão automática de certificados) à Let's Encrypt, recorre à ZeroSSL se isso falhar, serve o redirecionamento de HTTP para HTTPS na porta 80 e renova o certificado automaticamente. Não precisa de uma segunda ferramenta nem de 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 nas cópias de segurança ou aceite uma nova emissão depois de reconstruir o servidor. Para um hostname que não seja público, o tls internal assina o certificado 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 seu plugin --nginx reescreve o bloco do servidor 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 aspetos 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 tem mais subdomínios do que pretende indicar individualmente.

O Traefik inclui o próprio cliente ACME. Configure um certificate resolver na configuração estática e todos os routers poderão utilizá-lo. Todo o estado, incluindo a account key e os certificados, fica num único ficheiro acme.json. O Traefik recusa utilizar esse ficheiro se estiver legível por alguém que não seja o respetivo proprietário e informa-o 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-o primeiro com touch. Assim, o ficheiro herda o seu umask, que é como a maioria das pessoas cumpre esse requisito.

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 para essa porta. Se abrir apenas a porta 443, a emissão falha de uma forma que parece um problema de DNS.

O mesmo encaminhamento de duas aplicações em três configurações

O objetivo é: app.example.com vai para um serviço em 127.0.0.1:8080, e files.example.com vai para um serviço em 127.0.0.1:8081, ambos por HTTPS. Veja a configuração completa em cada proxy, para que a diferença de verbosidade fique visível, em vez de ser 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;
    }
}

Depois, faça o link, teste, recarregue 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

A execução de nginx -t, que imprime syntax is ok e test is successful, é a verificação a fazer 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 identifica um endereço, o nginx envia Host: 127.0.0.1:8080 ao backend por padrão. Assim, uma aplicação que cria URLs absolutas a partir do cabeçalho Host enviará os utilizadores para localhost. A finalidade de cada um desses quatro cabeçalhos e o motivo pelo qual uma barra final em proxy_pass altera silenciosamente o caminho recebido pela aplicação são explicados diretiva a diretiva em este guia sobre um bloco de servidor nginx.

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 por si próprio e, por padrão, ignora o que o cliente enviou nesses cabeçalhos. Assim, um pedido não pode fornecer informações falsas ao backend sobre a sua origem. Os certificados, o redirecionamento da porta 80 e a renovação resultam dos dois endereços dos sites. Nada mais no ficheiro os solicita.

Traefik

O Traefik precisa de uma configuração estática antes de encaminhar qualquer pedido. Como serviço do 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

Cada aplicação inclui depois o seu próprio encaminhamento em 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 custa cada aplicação adicional em configuração?

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
  }
]

Contando os 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 disso, 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 depois disso, e os dois totais convergem por volta do terceiro site. Abaixo desse ponto, a configuração estática é um custo que não era necessário. 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 rota também desaparece. É precisamente aí que um ficheiro de configuração central é menos eficaz: 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 precisa de um symlink, de um nginx -t, de um reload e de uma execução do certbot, enquanto a alteração no Caddy precisa de um único reload e a alteração no Traefik não precisa de 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 é necessário lembrar-se à uma da manhã.

Qual deles conhece os seus containers?

Traefik monitoriza o socket do Docker e cria routers a partir das labels dos containers à medida que estes iniciam e param. Nenhuma das outras opções 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 declarado 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 no seu interior. Isso dá acesso root ao host. Montar o socket como somente 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 e exponha 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 compilados no binário. 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, a menos que isso seja definido explicitamente.

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

Depois, dentro do bloco location, devem estar presentes estas três linhas:

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

Se forem omitidas, a consola do navegador 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á ainda 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. Além disso, os eventos enviados pelo servidor chegam atrasados ou em rajadas até definir proxy_buffering off; nessa localização, porque o nginx mantém a resposta na sua bufferização 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 os dados imediatamente quando a resposta é text/event-stream ou não tem um comprimento conhecido, pelo que o streaming funciona sem alterações. O Traefik encaminha os upgrades e não faz buffer das respostas, a menos que adicione manualmente o middleware buffering. Se os seus serviços incluem 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 de servidor completo do 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 a bufferização permite ao nginx libertar mais cedo 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 incomum?

É aqui que o Nginx justifica as 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 exige ssl_client_certificate /etc/ssl/ca.pem; e ssl_verify_client on; no bloco do servidor. O Caddy exige um bloco client_auth dentro de tls. As labels do Traefik não conseguem expressar isto: defina uma opção TLS num file provider e associe o router a ela com traefik.http.routers.app.tls.options=mtls@file. O modelo em que tudo é configurado por labels abre uma exceção assim que precisa desta funcionalidade.
  • Uploads grandes. O Nginx limita os corpos dos pedidos a 1 MB por predefinição. Um upload maior devolve 413 Request Entity Too Large e o log de erros regista client intended to send too large body. Aumente client_max_body_size. O Caddy e o Traefik não definem nenhum limite de corpo por predefinição. Assim, o pedido chega à aplicação e é o limite da própria aplicação que decide.
  • Cache de respostas. O Nginx tem proxy_cache, que é uma funcionalidade madura. O Caddy precisa de um plugin compilado no binário. A build open source do Traefik não tem cache HTTP, o que surpreende quem assume que todos os proxies fazem cache.
  • TCP ou UDP direto, 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 precisa de outro plugin e, portanto, de 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. Colocar um proxy à frente cria dois locais que definem cabeçalhos e dois que podem reescrever um URL. Decida qual deles termina o TLS. Depois, 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 à Internet pública, ao lado do proxy que configurou cuidadosamente. Ligue as portas publicadas ao loopback com 127.0.0.1:8080:80. Em alternativa, remova ports: por completo e permita que o proxy aceda ao contentor através de uma rede Docker, como faz o exemplo do Traefik acima. O mecanismo e a correção estão explicados em porque as portas publicadas pelo Docker contornam o ufw.

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

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

Connection refused ou um timeout é o resultado esperado. Uma resposta HTTP significa que essa aplicação está acessível sem passar pelo proxy. Nesse caso, toda a configuração feita acima não tem efeito.

Qual proxy 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 suficientemente curta para caber num único ecrã, e um site estático requer uma linha root e uma 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 ao qual adiciona serviços regularmente: Traefik. A partir do terceiro serviço, usar labels dá menos trabalho do que editar um ficheiro central, e um serviço eliminado leva consigo a própria rota. Reserve uma tarde para a configuração inicial, porque entrypoints, routers, services e middlewares são conceitos novos. Um erro de digitação numa label normalmente aparece como um 404 do Traefik, em vez de impedir o arranque, por isso leia 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 soluções para caching de respostas e certificados de cliente, e quase todos os guias de terceiros partem do princípio de que ele está a ser usado. O custo é ter de configurar os certificados e o suporte para WebSocket, em vez de os obter automaticamente.

Aplica-se uma regra independentemente da escolha. Exatamente um processo escuta na interface pública, e todos os restantes escutam em 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 é necessário editar um ficheiro central. Se os serviços forem estáveis e quiser sobretudo deixar de se preocupar com HTTPS, o Caddy exige menos aprendizagem e oferece menos possibilidades de erro. 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, sim. Definir um hostname público como endereço do site é toda a configuração necessária: o Caddy solicita o certificado através do ACME, serve o redirecionamento da porta 80 e renova o certificado antes do fim da validade. Ainda assim, é necessário que duas condições sejam verdadeiras. 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 a ligação de retorno.

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 na porta 80 e na porta 443 e coloque tudo o resto atrás dele. Se estiver a fazer uma migração, altere os hostnames um de cada vez: faça o proxy frontal encaminhar para o proxy antigo numa porta de loopback até o último site ter sido migrado.

Por que motivo os meus websockets são terminados 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 uma frame de ping a cada 30 segundos. O Caddy e o Traefik não fecham ligações atualizadas inativas com 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.