
Poner un sitio en marcha con HTTPS solía ser una receta de tres ingredientes: un servidor web, el certbot y un cron para renovar el certificado — y un nginx.conf de cincuenta líneas para atarlo todo. El Caddy lo resuelve en una sola pieza: escribes el nombre del dominio en el archivo de configuración y este emite el certificado, lo renueva solo y redirige HTTP a HTTPS. Esta guía instala Caddy en Ubuntu y en Debian desde el repositorio oficial, configura un sitio estático, proxy inverso con balanceo y PHP, y muestra dónde quedan los certificados, los logs y la API de administración.
Qué es Caddy
Caddy es un servidor web y proxy inverso escrito en Go, distribuido como un único binario y licenciado bajo Apache 2.0. Matt Holt empezó a escribirlo en 2014, todavía en la universidad; la primera versión pública (0.5.0) salió en abril de 2015, y la línea 2.x, reescrita desde cero, llegó en mayo de 2020. La versión estable actual es la 2.11.7, del 3 de octubre de 2026.
Tres características lo separan de nginx y Apache:
- HTTPS automático y por defecto. Todo sitio con nombre de dominio público obtiene un certificado de una CA ACME (Let’s Encrypt o ZeroSSL) y renovación automática. Los nombres locales, como
localhost, reciben certificados de una CA interna del propio Caddy. - Protocolos modernos activados de fábrica. HTTP/2 y, desde la 2.6 (septiembre de 2022), HTTP/3. La 2.10, de abril de 2025, añadió intercambio de claves postcuántica (
x25519mlkem768) por defecto y soporte para Encrypted ClientHello (ECH). - Configuración mediante API. El formato nativo es JSON, cargado y modificado en vivo por una API REST en
localhost:2019. El Caddyfile, que usaremos aquí, es un adaptador más legible que convierte ese JSON.
Lo que no hace: no tiene el ecosistema de módulos dinámicos de nginx ni el .htaccess de Apache. Los plugins se incluyen compilados en el binario — lo veremos al final.

Instalación desde el repositorio oficial
El proyecto mantiene paquetes para Debian, Ubuntu y Raspberry Pi OS en un repositorio alojado en Cloudsmith. Los comandos siguientes son los de la documentación oficial; los probé en un Debian 13 limpio y el paquete instaló la 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
Si el sistema no tiene el gpg, instálalo antes con sudo apt install -y gpg. El paquete hace bastantes cosas por su cuenta:
- crea el usuario de sistema
caddy, con$HOMEen/var/lib/caddyy miembro del grupowww-data; - instala e inicia el servicio
caddy.service, que lee/etc/caddy/Caddyfile; - instala también el
caddy-api.service, desactivado, para quien prefiere configurar solo por la API; - deja un Caddyfile de ejemplo sirviendo
/usr/share/caddyen el puerto 80.
caddy version
systemctl status caddy --no-pager
curl -I http://localhost
Libere los puertos en el cortafuegos. El HTTP/3 funciona sobre UDP, por lo que la 443 debe estar abierta en ambos protocolos:
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 443/udp
Primer sitio con HTTPS automático
Antes de tocar el Caddyfile, verifique los requisitos previos del certificado público: el registro A/AAAA del dominio apuntando al servidor y los puertos 80 y 443 accesibles desde internet. Sin esto, la CA no consigue validar el dominio — y, si se equivoca demasiadas veces, chocará con el límite de peticiones de la misma.
Sustituye el contenido 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
}
El bloque sin nombre en la parte superior guarda las opciones globales; el correo identifica la cuenta ACME ante la CA, que puede usarlo para avisos sobre la cuenta. Cada bloque siguiente es un sitio, identificado por la dirección. No hay puerto, ruta de certificado ni bloque de redirección HTTP: al ver un nombre de dominio, Caddy asume 443, obtiene el certificado y responde en el puerto 80 con redireccionamiento 308 a 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 estandariza la indentación (el Caddyfile usa tabulaciones), el validate carga la configuración sin aplicarla y el reload intercambia la configuración sin reiniciar el proceso ni derribar conexiones HTTP comunes (los WebSockets abiertos se cierran por defecto; la opción stream_close_delay del reverse_proxy adia ese cierre). No use restart ni stop para cambiar la configuración: la propia documentación advierte que detener el servicio provoca indisponibilidad. En el registro aparece la obtención del certificado; a partir de ahí, la renovación es automática.
Proxy inverso con balanceo
El uso más común de Caddy es colocarse delante de aplicaciones que escuchan solo en loopback: un Gitea, un Vaultwarden, una API en Node o Go. Basta una línea:
app.exemplo.com.br {
reverse_proxy 127.0.0.1:3000
}
Caddy ya reenvía X-Forwarded-For, X-Forwarded-Proto e X-Forwarded-Host, y gestiona WebSocket sin configuración extra. Con más de una instancia, liste los destinos y elija la 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
}
}
El bloque entre paréntesis es un snippet: un fragmento reutilizable que se incluye en cualquier sitio con import. El -Server elimina la cabecera que identifica al servidor. La verificación activa de estado (health_uri) retira de la rotación la instancia que deje de responder. El registro se emite en JSON, una línea por petición.
Probé exactamente esa configuración con dos backends: las peticiones se alternaron entre ellos, las tres cabeceras de seguridad llegaron al cliente, la cabecera Server desapareció y HTTP respondió 308 para HTTPS. Para validar localmente, sin dominio público, cambie el nombre por app.localhost: Caddy emite el certificado mediante la CA interna (Caddy Local Authority), y el curl -k acepta.
Si vienes de nginx, compara con el bloque de proxy de Gogs: son dos server, cuatro proxy_set_header y un certbot aparte para hacer lo que este archivo hace.
PHP con php-fpm
Para WordPress, Nextcloud o cualquier aplicación PHP, la directiva php_fastcgi ya trae las reglas de reescritura para index.php. Ajuste la ruta del socket a la versión instalada (8.4 en Debian 13, 8.3 en 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
}
El usuario caddy ya está en el grupo www-data, el mismo que el de php-fpm en Debian y en Ubuntu. Si usas varias versiones de PHP en paralelo, la lógica de pools y sockets del post Nginx y varias versiones de PHP sirve igual: solo cambia la línea de la php_fastcgi.
Dónde quedan certificados, logs y configuración
| Ítem | Ruta (paquete Debian/Ubuntu) |
|---|---|
| Configuración | /etc/caddy/Caddyfile |
| Certificados, claves, cuenta ACME | /var/lib/caddy/.local/share/caddy |
CA local (sitios .localhost) |
/var/lib/caddy/.local/share/caddy/pki/authorities/local/root.crt |
| Última configuración JSON guardada | /var/lib/caddy/.config/caddy |
| Logs del servicio | journalctl -u caddy |
| Logs de acceso | donde la directiva log mandar (en el ejemplo, /var/log/caddy/) |
El directorio de datos debe ser persistente e incluirse en la copia de seguridad: perder /var/lib/caddy significa reemitir todos los certificados, lo que, con muchos dominios, puede toparse con el límite de la CA. Para variables secretas — un token de API de DNS, por ejemplo —, use un drop-in de systemd en lugar de escribir en el Caddyfile:
sudo systemctl edit caddy
# no editor:
# [Service]
# EnvironmentFile=/etc/caddy/.env
Dentro del Caddyfile, la variable entra como {env.NOME}. La gestión de unidades y drop-ins se detalla en Dominando systemd.
La API de administración
Por defecto, Caddy abre una API REST en localhost:2019, accesible solo desde la propia máquina. Es por ella que el systemctl reload caddy (que llama caddy reload) entrega la nueva configuración. También puede consultarla:
curl -s localhost:2019/config/ | jq .
caddy adapt --config /etc/caddy/Caddyfile --pretty | less
El primer comando muestra la configuración JSON en ejecución; el segundo, el JSON que genera el Caddyfile — útil para entender qué hace cada directiva por debajo. No exponga el puerto 2019 en la red: quien llegue a él cambia la configuración del servidor. Si ejecuta código no confiable en la misma máquina, la documentación recomienda aislar procesos o cambiar la dirección de la API por un socket Unix con permisos restringidos.
Docker
La imagen oficial es caddy en Docker Hub. En 5 de octubre de 2026 todavía estaba en la 2.11.6 — dos días por detrás del paquete de apt —, por eso use la etiqueta de serie 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
El volumen /data es el equivalente de /var/lib/caddy: sin él, cada recreación del contenedor pide certificados nuevos. La etiqueta 443:443/udp es la que habilita HTTP/3. Para lo básico de contenedores, vea el curso de Docker.
Complementos: xcaddy y add-package
El paquete oficial trae solo los módulos estándar. Los plugins (proveedores de DNS para certificados wildcard, ), autenticación y rate limit se compilan dentro del binario. La vía recomendada es xcaddy, que necesita Go instalado:
go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest
xcaddy build --with github.com/caddy-dns/cloudflare
El resultado es un binario ./caddy en el directorio actual. Para usarlo con el servicio del paquete, sigue la sección custom builds de la documentación, que usa dpkg-divert para que apt no lo sobrescriba en la próxima actualización. También existe caddy add-package, que descarga un binario con el plugin listo del servidor de builds del proyecto, pero todavía está marcado como experimental.
¿Caddy o nginx?
Caddy gana cuando el problema es poner servicios detrás de HTTPS con poco rozamiento: VPS con media docena de aplicaciones, homelab, entornos de prueba o equipos que no quieren lidiar con certbot y la renovación. nginx sigue siendo fuerte donde ya está instalado y ajustado, en configuraciones muy específicas de caché y reescritura, y donde el equipo domina la sintaxis. Nada impide usar los dos: Caddy en el borde ocupándose de TLS y nginx detrás sirviendo una aplicación heredada.
Si alojas servicios como el Vaultwarden, el Gitea o el Uptime Kuma, cambia el bloque de nginx por tres líneas de Caddyfile y compara.
Enlaces
- caddyserver.com · documentación · github.com/caddyserver/caddy
- Automatic HTTPS · reverse_proxy · Ejecutando Caddy (systemd)
Un binario, un archivo de configuración corto y ningún certbot que recordar para renovar: esa es la propuesta de Caddy, y se sostiene. Instálalo desde el repositorio oficial, mantén /var/lib/caddy en la copia de seguridad, deja la API en localhost y usa reload, nunca restart, para aplicar cambios.