Nginx ou Caddy: qual colocar na frente da sua aplicação na VPS
Subindo projetos na VPS, precisei decidir quem fica na porta 80/443 cuidando de domínio, HTTPS e proxy. Comparo Nginx e Caddy no mesmo cenário, com configs lado a lado, e deixo um checklist de como escolher.
Nos últimos dias eu estava colocando trabalhos no ar na minha VPS e cheguei naquele ponto que todo mundo chega: a aplicação já roda, o domínio já aponta pro IP, mas falta alguém na frente dela. Alguém que receba a requisição na porta 80/443, cuide do HTTPS e entregue tudo pra aplicação certa.
Os dois nomes que sempre aparecem são Nginx e Caddy. Testei os dois e esta entrada é o resumo do que aprendi, do jeito mais direto que consegui.
Primeiro: o que esse "alguém na frente" faz?
Pensa num prédio comercial com várias empresas. O visitante chega na recepção, diz pra onde vai, e a recepcionista manda ele pro andar certo. Ela também confere quem está entrando.
Na VPS, essa recepção é o reverse proxy. Ele:
- recebe tudo que chega nas portas 80 (HTTP) e 443 (HTTPS);
- olha o domínio pedido (
api.cliente.com,loja.cliente.com...) e repassa pra aplicação certa, que normalmente roda numa porta interna comolocalhost:8000; - cuida do certificado SSL, pra sua aplicação não precisar se preocupar com isso;
- pode servir arquivos estáticos, comprimir respostas, colocar cabeçalhos de segurança etc. Tanto o Nginx quanto o Caddy fazem tudo isso. A diferença está em como você chega lá.
Nginx em um minuto
O Nginx é o veterano. Escrito em C, roda em uma quantidade enorme de servidores no mundo e é praticamente o padrão da indústria. Qualquer erro que você encontrar, alguém já encontrou antes e postou a solução.
O ponto que pesa: ele não faz HTTPS sozinho. Você configura o site e depois usa uma ferramenta à parte, normalmente o Certbot, pra pedir o certificado no Let's Encrypt e programar a renovação.
Caddy em um minuto
O Caddy é mais novo, escrito em Go, e a proposta dele é: HTTPS automático por padrão. Você escreve o domínio no arquivo de configuração e pronto. Ele pede o certificado, instala, renova antes de vencer e já redireciona HTTP pra HTTPS. Sem Certbot, sem cron, sem lembrar de nada.
O arquivo de configuração dele, o Caddyfile, também é bem mais curto que o do Nginx.
O mesmo cenário nos dois
Cenário: tenho uma API rodando em localhost:8000 (pode ser Spring Boot, Node, php artisan serve, um container...) e quero que ela responda em https://api.exemplo.com.
Com Nginx
Arquivo em /etc/nginx/sites-available/api.exemplo.com:
server {
listen 80;
server_name api.exemplo.com;
location / {
proxy_pass http://127.0.0.1:8000;
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 ativa, testa e recarrega:
sudo ln -s /etc/nginx/sites-available/api.exemplo.com /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
E só agora vem o HTTPS:
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d api.exemplo.com
O Certbot edita o seu arquivo, adiciona o bloco da porta 443 e deixa a renovação agendada.
Com Caddy
Arquivo /etc/caddy/Caddyfile:
api.exemplo.com {
reverse_proxy localhost:8000
}
caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddy
É isso. HTTPS, renovação e redirecionamento de HTTP pra HTTPS já vêm junto. Os cabeçalhos X-Forwarded-* também são repassados por padrão.
Foi nesse momento que eu entendi por que tanta gente fala do Caddy.
E pra um projeto Laravel com PHP-FPM?
Aqui não tem "porta 8000": o servidor web conversa direto com o PHP-FPM.
Nginx:
server {
listen 80;
server_name app.exemplo.com;
root /var/www/app/public;
index index.php;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/run/php/php8.4-fpm.sock;
}
}
(e depois o Certbot, de novo)
Caddy:
app.exemplo.com {
root * /var/www/app/public
encode zstd gzip
php_fastcgi unix//run/php/php8.4-fpm.sock
file_server
}
O php_fastcgi do Caddy já faz o papel daquele try_files mandando tudo pro index.php.
Pegadinha de Laravel atrás de proxy: se a sua aplicação roda atrás de um proxy (em container, por exemplo) e começa a gerar links
http://ou dar loop de redirecionamento, falta confiar no proxy. No Laravel 11/12 isso fica nobootstrap/app.php:->withMiddleware(function (Middleware $middleware) { $middleware->trustProxies(at: '*'); })
Lado a lado
| Nginx | Caddy | |
|---|---|---|
| HTTPS | Manual, via Certbot | Automático, já vem ligado |
| Renovação do certificado | Agendada pelo Certbot | Feita pelo próprio Caddy |
| Configuração | Mais verbosa, muito flexível | Curta e legível |
| HTTP/3 | Suportado, mas precisa configurar | Ligado por padrão |
| Material e exemplos na internet | Gigantesco | Bom, mas bem menor |
| Uso de memória | Muito baixo | Baixo (um pouco maior que o Nginx) |
| Painéis de hospedagem e tutoriais de empresas | Quase sempre assumem Nginx | Raramente |
| Curva de aprendizado | Média | Baixa |
Sobre desempenho: pra uma VPS rodando alguns sites e APIs, os dois sobram. Gargalo nesse tamanho de projeto quase sempre é banco de dados ou código, não o proxy.
Como eu escolho agora
Vou de Caddy quando:
-
é uma VPS minha ou de cliente pequeno, com poucos domínios;
-
quero subir rápido e não quero lembrar de certificado nunca mais;
-
o proxy é só proxy: recebe e repassa, sem regra muito exótica;
-
estou usando Docker e quero um
docker-compose.ymlsimples. Vou de Nginx quando: -
o servidor já tem Nginx (não vale migrar o que funciona só por gosto);
-
o cliente ou a empresa já usa Nginx e outras pessoas vão dar manutenção;
-
preciso de configuração muito específica: cache avançado, regras complexas de rewrite, balanceamento com ajustes finos;
-
vou seguir um tutorial ou documentação oficial de alguma ferramenta que só traz exemplo pra Nginx. Resumindo numa frase: Caddy pra começar rápido e sem dor de cabeça com HTTPS; Nginx quando o contexto exige o padrão da indústria ou controle fino. Nenhuma das duas escolhas é errada.
Bônus: Caddy no Docker Compose
Como na minha VPS quase tudo roda em container, esse foi o formato que mais usei:
services:
caddy:
image: caddy:2
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "443:443/udp" # HTTP/3
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- caddy_data:/data
- caddy_config:/config
api:
image: minha-api:latest
restart: unless-stopped
volumes:
caddy_data:
caddy_config:
E no Caddyfile, em vez de localhost, uso o nome do serviço:
api.exemplo.com {
reverse_proxy api:8000
}
Coisas que dão errado (e aconteceriam com qualquer um dos dois)
- O DNS ainda não apontou pro IP da VPS. O Let's Encrypt precisa acessar seu domínio pra emitir o certificado. Se o DNS não propagou, a emissão falha. Confere com
dig api.exemplo.comantes. - Portas 80 e 443 fechadas no firewall. Se usa UFW:
sudo ufw allow 80,443/tcp(e443/udpse quiser HTTP/3). - Os dois instalados ao mesmo tempo. Só um programa pode escutar na porta 80. Se o Caddy não sobe, veja se o Nginx não ficou rodando:
sudo ss -tlnp | grep ':80'. - Perder o volume
/datado Caddy no Docker. É ali que ficam os certificados. Sem volume, a cada recriação do container ele pede tudo de novo, e o Let's Encrypt tem limite de emissões. Por isso ocaddy_datano compose acima. - Esquecer de testar a configuração antes de recarregar.
nginx -tecaddy validateexistem pra isso. Um erro de digitação derruba todos os sites do servidor de uma vez.
O que ficou de lição
Eu achava que a escolha seria por desempenho, e no fim ela é sobre tempo e contexto. Nos meus projetos pessoais e de clientes pequenos, o Caddy me poupa a parte mais chata (certificado) e deixa a configuração tão curta que eu entendo ela de novo daqui a seis meses. Já quando o ambiente é de outra pessoa ou o cenário é mais complexo, o Nginx continua sendo a escolha mais segura justamente por ser o padrão.
Se você está subindo a primeira aplicação numa VPS agora, minha sugestão é: começa com Caddy, entende o que um reverse proxy faz, e aprende Nginx quando aparecer um motivo concreto.