TL;DR — Resumen Rápido
Guía completa de Caddy como proxy inverso con HTTPS automático. Caddyfile, balanceo de carga, health checks y Caddyfile de producción para múltiples sitios.
Caddy es un servidor web moderno de código abierto escrito en Go cuya característica definitoria es el HTTPS automático: provisiona y renueva certificados TLS desde Let’s Encrypt o ZeroSSL en el momento en que apuntas un dominio hacia él, sin ninguna configuración manual de Certbot. Como proxy inverso, Caddy combina esa gestión de TLS sin configuración con una sintaxis declarativa limpia, balanceo de carga integrado, HTTP/2 y HTTP/3 habilitados por defecto, y un único binario compilado estáticamente sin dependencias externas. Esta guía cubre todo lo necesario para ejecutar Caddy como proxy inverso en producción: instalación, sintaxis del Caddyfile, todas las políticas de balanceo de carga, health checks activos y pasivos, manipulación de cabeceras, autenticación, TLS bajo demanda, la API de administración y un Caddyfile completo para múltiples sitios.
Por Qué Elegir Caddy como Proxy Inverso
Antes de entrar en la configuración, aquí hay una comparación directa de las principales opciones de proxy inverso disponibles en 2026:
| Característica | Caddy | Nginx | Traefik | HAProxy | Apache |
|---|---|---|---|---|---|
| HTTPS automático | Cliente ACME integrado | Manual (Certbot) | Integrado (vía ACME) | Manual | Manual (mod_md) |
| Formato de configuración | Caddyfile (mínimo) | nginx.conf (verboso) | YAML/TOML/etiquetas Docker | haproxy.cfg | httpd.conf |
| HTTP/2 | Por defecto | Configuración explícita | Por defecto | No (solo TCP) | Configuración explícita |
| HTTP/3 (QUIC) | Por defecto | Experimental | Vía plugin | No | No |
| Binario único | Sí (Go, sin deps) | No (C, con módulos) | Sí (Go) | Sí (C) | No |
| API de configuración | API REST completa | No | API REST completa | Solo socket de estadísticas | No |
| Balanceo de carga | 8 políticas integradas | Limitado integrado | Múltiples proveedores | Excelente | Básico |
| Huella de memoria | ~20-50 MB | ~5-15 MB | ~25-60 MB | ~5-10 MB | ~30-80 MB |
| Curva de aprendizaje | Baja | Media-Alta | Media | Alta | Media-Alta |
Elige Caddy cuando: quieras TLS automático, configuración mínima y HTTP/3 sin builds de plugins ni herramientas externas. Ideal para aplicaciones auto-alojadas, proxy inverso multi-tenant y equipos que quieren cero sobrecarga de gestión de certificados.
Prerrequisitos
- Un servidor Linux con Ubuntu 22.04+, Debian 12+ o una distribución compatible con RHEL/CentOS
- Un nombre de dominio con registros DNS A/AAAA apuntando a la IP pública del servidor
- Los puertos 80 y 443 abiertos en el firewall (requeridos para el desafío HTTP-01 de ACME y el tráfico HTTPS)
- Acceso root o sudo
- Ningún otro proceso vinculado a los puertos 80 o 443
# Abrir puertos requeridos con UFW
sudo ufw allow 80/tcp && sudo ufw allow 443/tcp && sudo ufw status
Instalación de Caddy
Ubuntu y Debian (APT)
sudo apt update && 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 apt update && sudo apt install -y caddy
caddy version
RHEL, CentOS, Fedora (DNF)
dnf install 'dnf-command(copr)'
dnf copr enable @caddy/caddy
dnf install caddy
Docker
docker run -d \
-p 80:80 -p 443:443 -p 443:443/udp \
-v /ruta/a/Caddyfile:/etc/caddy/Caddyfile \
-v caddy_data:/data \
-v caddy_config:/config \
caddy:latest
xcaddy — Compilaciones Personalizadas con Plugins
go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest
xcaddy build \
--with github.com/caddy-dns/cloudflare \
--with github.com/mholt/caddy-ratelimit
sudo mv caddy /usr/bin/caddy && sudo systemctl restart caddy
Sintaxis del Caddyfile
Estructura
# Bloque de opciones globales
{
email admin@ejemplo.com
admin off
grace_period 10s
}
# Bloque de sitio — el dominio activa HTTPS automático
app.ejemplo.com {
directiva argumento
}
Matchers
ejemplo.com {
@api path /api/*
reverse_proxy @api localhost:8000
reverse_proxy /static/* localhost:9000
file_server *
}
Marcadores de posición (Placeholders)
ejemplo.com {
reverse_proxy localhost:3000 {
header_up X-IP-Real {remote_host}
header_up X-Puerto-Reenv {server_port}
header_up X-ID-Solicitud {uuid}
}
}
Configuración del Proxy Inverso
Backend Único Básico
app.ejemplo.com {
reverse_proxy localhost:3000
}
Esta única línea proporciona: HTTPS automático, HTTP/2, HTTP/3, redirección HTTP→HTTPS, reenvío de cabeceras X-Forwarded-For y soporte transparente de WebSocket.
Enrutamiento Basado en Rutas
ejemplo.com {
reverse_proxy /api/* localhost:8000
reverse_proxy /ws/* localhost:4000
reverse_proxy /admin/* localhost:9000
root * /var/www/frontend
file_server
}
Manipulación de Cabeceras
app.ejemplo.com {
reverse_proxy localhost:3000 {
header_up Host {upstream_hostport}
header_up X-IP-Real {remote_host}
header_up X-Proto-Reenviado {scheme}
header_up -Authorization
header_down -X-ID-Servidor-Interno
header_down Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
}
}
WebSocket y Conexiones de Larga Duración
ws.ejemplo.com {
reverse_proxy localhost:4000 {
flush_interval -1
}
}
Balanceo de Carga
Múltiples Backends
app.ejemplo.com {
reverse_proxy localhost:3001 localhost:3002 localhost:3003
}
Todas las Políticas de Balanceo de Carga
app.ejemplo.com {
reverse_proxy localhost:3001 localhost:3002 localhost:3003 {
lb_policy least_conn # Backend con menos conexiones activas
# lb_policy round_robin # Rotación secuencial (por defecto)
# lb_policy first # Siempre el primer backend disponible
# lb_policy random # Selección aleatoria
# lb_policy ip_hash # IP del cliente → backend consistente
# lb_policy cookie caddy_lb # Persistencia de sesión basada en cookie
# lb_policy header X-Shard # Enrutamiento por valor de cabecera
# lb_policy uri_hash # URI de solicitud → backend consistente
}
}
Health Checks Activos
app.ejemplo.com {
reverse_proxy localhost:3001 localhost:3002 localhost:3003 {
lb_policy least_conn
health_uri /health
health_interval 15s
health_timeout 5s
health_status 200
health_headers Authorization "Bearer token-healthcheck"
}
}
Health Checks Pasivos
app.ejemplo.com {
reverse_proxy localhost:3001 localhost:3002 localhost:3003 {
lb_policy round_robin
fail_duration 30s
max_fails 3
unhealthy_latency 500ms
unhealthy_status 5xx
}
}
HTTPS Automático en Profundidad
Tipos de Desafío ACME
{
email admin@ejemplo.com
}
# HTTP-01 (por defecto): Caddy sirve el desafío en el puerto 80
ejemplo.com {
reverse_proxy localhost:3000
}
# TLS-ALPN-01: Usa el puerto 443, funciona cuando el puerto 80 está bloqueado
solo-tls.ejemplo.com {
tls {
challenges tls-alpn-01
}
reverse_proxy localhost:3001
}
# DNS-01: Sin puertos entrantes — requiere xcaddy + plugin DNS
wildcard.ejemplo.com {
tls {
dns cloudflare {env.CF_API_TOKEN}
}
reverse_proxy localhost:3002
}
TLS Bajo Demanda para Dominios Dinámicos
{
on_demand_tls {
ask http://localhost:9001/verificar-dominio
interval 2m
burst 5
}
}
:443 {
tls {
on_demand
}
reverse_proxy localhost:3000
}
CA Interna para Desarrollo
{
local_certs
}
localhost, 127.0.0.1, ::1 {
reverse_proxy localhost:3000
}
Autenticación
Autenticación Básica HTTP
admin.ejemplo.com {
basicauth /admin/* {
alice $2a$14$Zkx19XLiW6VYouLHR5NmfOFU0z2GT.VVKf3KCrXxpHPkAXmn9sQHO
}
reverse_proxy localhost:9000
}
# Generar hash de contraseña
caddy hash-password --plaintext 'tucontraseña'
Autenticación Delegada (Authelia, oauth2-proxy)
app.ejemplo.com {
forward_auth authelia:9091 {
uri /api/verify?rd=https://auth.ejemplo.com
copy_headers Remote-User Remote-Groups Remote-Name Remote-Email
}
reverse_proxy localhost:3000
}
Servidor de Archivos y SPA
Servidor de Archivos Estáticos
estatico.ejemplo.com {
root * /var/www/html
encode gzip zstd
file_server {
hide .git .env .htaccess
}
}
Aplicación de Página Única (SPA)
spa.ejemplo.com {
root * /var/www/app/dist
encode gzip zstd
try_files {path} /index.html
file_server
}
Compresión y Registro de Acceso
ejemplo.com {
encode {
zstd
gzip 6
minimum_length 1024
}
log {
output file /var/log/caddy/acceso.log {
roll_size 100MiB
roll_keep 10
roll_keep_for 720h
}
format json
level INFO
}
reverse_proxy localhost:3000
}
La API de Administración de Caddy
# Ver la configuración actual
curl http://localhost:2019/config/
# Recargar configuración desde Caddyfile
curl -X POST http://localhost:2019/load \
-H "Content-Type: text/caddyfile" \
--data-binary @/etc/caddy/Caddyfile
Caddyfile de Producción para Múltiples Sitios
{
email admin@ejemplo.com
grace_period 30s
admin unix//run/caddy/admin.sock
}
(cabeceras_seguridad) {
header {
Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"
X-Content-Type-Options "nosniff"
X-Frame-Options "SAMEORIGIN"
Referrer-Policy "strict-origin-when-cross-origin"
-Server
-X-Powered-By
}
}
app.ejemplo.com {
import cabeceras_seguridad
encode zstd gzip
log {
output file /var/log/caddy/app-acceso.log { roll_size 50MiB; roll_keep 7 }
format json
}
reverse_proxy localhost:3001 localhost:3002 localhost:3003 {
lb_policy least_conn
health_uri /health
health_interval 10s
health_timeout 3s
fail_duration 30s
max_fails 3
header_up X-IP-Real {remote_host}
header_up X-Proto-Reenviado {scheme}
header_down -X-Traza-Interna
}
}
www.ejemplo.com {
import cabeceras_seguridad
root * /var/www/frontend/dist
encode zstd gzip
@estatico path *.css *.js *.png *.jpg *.webp *.gif *.svg *.woff2 *.ico
header @estatico Cache-Control "public, max-age=31536000, immutable"
try_files {path} /index.html
file_server
}
Errores Comunes y Soluciones
El puerto 80 debe ser accesible para los desafíos HTTP-01. Si tu proveedor de nube o firewall bloquea el puerto 80, usa el desafío tls-alpn-01 o un desafío DNS-01 con xcaddy y un plugin DNS.
Límites de tasa de Let’s Encrypt. El servidor ACME de producción permite 5 certificados duplicados por dominio por semana. Durante las pruebas, establece acme_ca https://acme-staging-v02.api.letsencrypt.org/directory.
El usuario caddy necesita acceso de lectura a tus raíces web. El servicio systemd se ejecuta como el usuario del sistema caddy. Siempre ejecuta sudo chown -R caddy:caddy /var/www/tusite.
El intervalo de vaciado importa para el streaming. Los eventos enviados por el servidor (SSE) y las APIs de streaming requieren flush_interval -1 dentro del bloque reverse_proxy.
Solución de Problemas
# Validar la sintaxis del Caddyfile
caddy validate --config /etc/caddy/Caddyfile
# Formatear el Caddyfile automáticamente
caddy fmt --overwrite /etc/caddy/Caddyfile
# Ver los registros en tiempo real
sudo journalctl -u caddy -f --no-pager
# Verificar en qué puertos escucha Caddy
sudo ss -tlnp | grep caddy
# Verificar el certificado TLS
echo | openssl s_client -connect ejemplo.com:443 -servername ejemplo.com 2>/dev/null \
| openssl x509 -noout -subject -issuer -dates
Resumen
- Caddy es un único binario Go sin dependencias externas que proporciona HTTPS automático vía ACME, HTTP/2 y HTTP/3 por defecto, y una sintaxis Caddyfile mínima
- La directiva
reverse_proxymaneja el proxying, balanceo de carga, manipulación de cabeceras, soporte WebSocket y health checks en un único bloque - Ocho políticas de balanceo de carga cubren todos los escenarios, desde round-robin sin estado hasta afinidad de sesión basada en cookies
- Los health checks activos y pasivos pueden combinarse para una detección robusta de fallos
- El TLS bajo demanda permite la emisión de certificados por tenant para plataformas SaaS sin preconfiguración
- Usa snippets para compartir bloques de configuración entre múltiples definiciones de sitios