Caddy no Linux: servidor web com HTTPS automático

Mascote do LinuxPro encaixando um cadeado verde de HTTPS no portão com o logo do Caddy, por onde o tráfego da internet passa até os racks de servidores, com o cachorro caramelo sentado ao lado

Colocar um site no ar com HTTPS costumava ser uma receita de três ingredientes: um servidor web, o certbot e um cron para renovar o certificado — e um nginx.conf de cinquenta linhas para amarrar tudo. O Caddy resolve isso numa peça só: você escreve o nome do domínio no arquivo de configuração e ele emite o certificado, renova sozinho e redireciona HTTP para HTTPS. Este guia instala o Caddy no Ubuntu e no Debian pelo repositório oficial, configura site estático, proxy reverso com balanceamento e PHP, e mostra onde ficam os certificados, os logs e a API de administração.

O que é o Caddy

O Caddy é um servidor web e proxy reverso escrito em Go, distribuído como um binário único e licenciado sob Apache 2.0. Matt Holt começou a escrevê-lo em 2014, ainda na faculdade; a primeira versão pública (0.5.0) saiu em abril de 2015, e a linha 2.x, reescrita do zero, chegou em maio de 2020. A versão estável atual é a 2.11.7, de 3 de outubro de 2026.

Três características o separam do nginx e do Apache:

  • HTTPS automático e por padrão. Todo site com nome de domínio público ganha certificado de uma CA ACME (Let’s Encrypt ou ZeroSSL) e renovação automática. Nomes locais, como localhost, recebem certificados de uma CA interna do próprio Caddy.
  • Protocolos modernos ligados de fábrica. HTTP/2 e, desde a 2.6 (setembro de 2022), HTTP/3. A 2.10, de abril de 2025, acrescentou troca de chaves pós-quântica (x25519mlkem768) por padrão e suporte a Encrypted ClientHello (ECH).
  • Configuração por API. O formato nativo é JSON, carregado e alterado ao vivo por uma API REST em localhost:2019. O Caddyfile, que usaremos aqui, é um adaptador mais legível que vira esse JSON.

O que ele não faz: não tem o ecossistema de módulos dinâmicos do nginx nem o .htaccess do Apache. Plugins entram compilados no binário — veremos como no fim.

Diagrama: clientes acessam o Caddy nas portas 443 e 80 de um servidor Ubuntu ou Debian; o Caddy obtém certificados de uma CA ACME, guarda-os em /var/lib/caddy, lê o /etc/caddy/Caddyfile e encaminha para site estático, aplicações em loopback e PHP-FPM; a API de administração fica em localhost:2019
O Caddy fica na frente de tudo: só as portas 80 e 443 ficam abertas, e os aplicativos escutam apenas no loopback.

Instalando pelo repositório oficial

O projeto mantém pacotes para Debian, Ubuntu e Raspberry Pi OS num repositório hospedado no Cloudsmith. Os comandos abaixo são os da documentação oficial; testei-os num Debian 13 limpo e o pacote instalou a 2.11.7:

sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' \
  | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' \
  | sudo tee /etc/apt/sources.list.d/caddy-stable.list
sudo chmod o+r /usr/share/keyrings/caddy-stable-archive-keyring.gpg
sudo chmod o+r /etc/apt/sources.list.d/caddy-stable.list
sudo apt update
sudo apt install caddy

Se o sistema não tiver o gpg, instale antes com sudo apt install -y gpg. O pacote faz bastante coisa sozinho:

  • cria o usuário de sistema caddy, com $HOME em /var/lib/caddy e membro do grupo www-data;
  • instala e já inicia o serviço caddy.service, que lê /etc/caddy/Caddyfile;
  • instala também o caddy-api.service, desativado, para quem prefere configurar só pela API;
  • deixa um Caddyfile de exemplo servindo /usr/share/caddy na porta 80.
caddy version
systemctl status caddy --no-pager
curl -I http://localhost

Libere as portas no firewall. O HTTP/3 roda sobre UDP, então a 443 precisa estar aberta nos dois protocolos:

sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 443/udp

Primeiro site com HTTPS automático

Antes de mexer no Caddyfile, confira os pré-requisitos do certificado público: o registro A/AAAA do domínio apontando para o servidor e as portas 80 e 443 acessíveis pela internet. Sem isso a CA não consegue validar o domínio — e, se você errar muitas vezes, bate no limite de requisições dela.

Substitua o conteúdo de /etc/caddy/Caddyfile:

{
	email admin@exemplo.com.br
}

www.exemplo.com.br {
	root * /var/www/exemplo
	file_server
	encode zstd gzip
}

exemplo.com.br {
	redir https://www.exemplo.com.br{uri} permanent
}

O bloco sem nome no topo guarda as opções globais; o e-mail identifica a conta ACME junto à CA, que pode usá-lo para avisos sobre a conta. Cada bloco seguinte é um site, identificado pelo endereço. Não há porta, caminho de certificado nem bloco de redirecionamento HTTP: ao ver um nome de domínio, o Caddy assume 443, obtém o certificado e responde na porta 80 com redirecionamento 308 para HTTPS.

sudo mkdir -p /var/www/exemplo
echo '<h1>Olá do Caddy</h1>' | sudo tee /var/www/exemplo/index.html

sudo caddy fmt --overwrite /etc/caddy/Caddyfile
sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddy
journalctl -u caddy --no-pager -n 30

O fmt padroniza a indentação (o Caddyfile usa tabulações), o validate carrega a configuração sem aplicá-la e o reload troca a configuração sem reiniciar o processo nem derrubar conexões HTTP comuns (WebSockets abertos são encerrados por padrão; a opção stream_close_delay do reverse_proxy adia esse fechamento). Não use restart nem stop para mudar configuração: a própria documentação avisa que parar o serviço causa indisponibilidade. No log aparece a obtenção do certificado; a partir daí, a renovação é automática.

Proxy reverso com balanceamento

O uso mais comum do Caddy é ficar na frente de aplicações que escutam só no loopback — um Gitea, um Vaultwarden, uma API em Node ou Go. Uma linha basta:

app.exemplo.com.br {
	reverse_proxy 127.0.0.1:3000
}

O Caddy já repassa X-Forwarded-For, X-Forwarded-Proto e X-Forwarded-Host, e trata WebSocket sem configuração extra. Com mais de uma instância, liste os destinos e escolha a política:

(seguranca) {
	header {
		Strict-Transport-Security "max-age=31536000"
		X-Content-Type-Options nosniff
		Referrer-Policy strict-origin-when-cross-origin
		-Server
	}
}

app.exemplo.com.br {
	reverse_proxy 127.0.0.1:8080 127.0.0.1:8081 {
		lb_policy round_robin
		health_uri /healthz
		health_interval 10s
	}
	import seguranca
	log {
		output file /var/log/caddy/app.log
	}
}

O bloco entre parênteses é um snippet: um trecho reutilizável que entra em qualquer site com import. O -Server remove o cabeçalho que identifica o servidor. A verificação ativa de saúde (health_uri) tira do rodízio a instância que parar de responder. O log sai em JSON, uma linha por requisição.

Testei exatamente essa configuração com dois backends: as requisições alternaram entre eles, os três cabeçalhos de segurança chegaram ao cliente, o cabeçalho Server sumiu e o HTTP respondeu 308 para HTTPS. Para validar localmente, sem domínio público, troque o nome por app.localhost: o Caddy emite o certificado pela CA interna (Caddy Local Authority), e o curl -k aceita.

Se você vem do nginx, compare com o bloco de proxy do Gogs: são dois server, quatro proxy_set_header e um certbot à parte para fazer o que esse arquivo faz.

PHP com php-fpm

Para WordPress, Nextcloud ou qualquer aplicação PHP, a diretiva php_fastcgi já traz as regras de reescrita para index.php. Ajuste o caminho do socket à versão instalada (8.4 no Debian 13, 8.3 no Ubuntu 24.04):

blog.exemplo.com.br {
	root * /var/www/blog
	php_fastcgi unix//run/php/php8.4-fpm.sock
	file_server
	encode zstd gzip
}

O usuário caddy já está no grupo www-data, o mesmo do php-fpm no Debian e no Ubuntu. Se você usa várias versões de PHP lado a lado, a lógica de pools e sockets do post Nginx e várias versões de PHP vale igual — muda só a linha do php_fastcgi.

Onde ficam certificados, logs e configuração

Item Caminho (pacote Debian/Ubuntu)
Configuração /etc/caddy/Caddyfile
Certificados, chaves, conta ACME /var/lib/caddy/.local/share/caddy
CA local (sites .localhost) /var/lib/caddy/.local/share/caddy/pki/authorities/local/root.crt
Última configuração JSON salva /var/lib/caddy/.config/caddy
Logs do serviço journalctl -u caddy
Logs de acesso onde a diretiva log mandar (no exemplo, /var/log/caddy/)

O diretório de dados precisa ser persistente e entrar no backup: perder /var/lib/caddy significa reemitir todos os certificados, o que, com muitos domínios, pode esbarrar no limite da CA. Para variáveis secretas — um token de API de DNS, por exemplo —, use um drop-in do systemd em vez de escrever no Caddyfile:

sudo systemctl edit caddy
# no editor:
# [Service]
# EnvironmentFile=/etc/caddy/.env

Dentro do Caddyfile, a variável entra como {env.NOME}. A gestão de unidades e drop-ins está detalhada em Dominando o systemd.

A API de administração

Por padrão o Caddy abre uma API REST em localhost:2019, acessível apenas da própria máquina. É por ela que o systemctl reload caddy (que chama caddy reload) entrega a nova configuração. Você também pode consultá-la:

curl -s localhost:2019/config/ | jq .
caddy adapt --config /etc/caddy/Caddyfile --pretty | less

O primeiro comando mostra a configuração JSON em execução; o segundo, o JSON que o Caddyfile gera — útil para entender o que cada diretiva faz por baixo. Não exponha a porta 2019 na rede: quem chega nela troca a configuração do servidor. Se rodar código não confiável na mesma máquina, a documentação recomenda isolar processos ou trocar o endereço da API por um socket Unix com permissões restritas.

Docker

A imagem oficial é caddy no Docker Hub. Em 5 de outubro de 2026 ela ainda estava na 2.11.6 — dois dias atrás do pacote do apt —, por isso use a tag de série 2.11:

docker run -d --name caddy --restart unless-stopped \
  -p 80:80 -p 443:443 -p 443:443/udp \
  -v $PWD/Caddyfile:/etc/caddy/Caddyfile:ro \
  -v caddy_data:/data \
  -v caddy_config:/config \
  caddy:2.11

O volume /data é o equivalente do /var/lib/caddy: sem ele, cada recriação do container pede certificados novos. A publicação 443:443/udp é a que habilita o HTTP/3. Para o básico de containers, veja o curso de Docker.

Plugins: xcaddy e add-package

O pacote oficial traz só os módulos padrão. Plugins — provedores de DNS para certificados wildcard, autenticação, rate limit — são compilados dentro do binário. O caminho recomendado é o xcaddy, que precisa do Go instalado:

go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest
xcaddy build --with github.com/caddy-dns/cloudflare

O resultado é um binário ./caddy no diretório atual. Para usá-lo com o serviço do pacote, siga a seção custom builds da documentação, que usa dpkg-divert para o apt não sobrescrevê-lo na próxima atualização. Existe também o caddy add-package, que baixa um binário com o plugin pronto do servidor de builds do projeto, mas ele ainda é marcado como experimental.

Caddy ou nginx?

O Caddy vence quando o problema é colocar serviços atrás de HTTPS com pouco atrito: VPS com meia dúzia de aplicações, homelab, ambientes de teste, ou equipes que não querem cuidar de certbot e renovação. O nginx continua forte onde já está instalado e afinado, em configurações muito específicas de cache e reescrita, e onde a equipe domina a sintaxe. Nada impede usar os dois: o Caddy na borda cuidando do TLS e o nginx atrás servindo uma aplicação legada.

Se você hospeda serviços como o Vaultwarden, o Gitea ou o Uptime Kuma, troque o bloco de nginx por três linhas de Caddyfile e compare.

Links

Um binário, um arquivo de configuração curto e nenhum certbot para lembrar de renovar: é essa a proposta do Caddy, e ela se sustenta. Instale pelo repositório oficial, mantenha /var/lib/caddy no backup, deixe a API em localhost e use reload, nunca restart, para aplicar mudanças.