Caddy es un servidor web y proxy inverso escrito en Go que obtiene y renueva certificados TLS de Let's Encrypt o ZeroSSL de forma automática, sin Certbot ni tareas programadas. Su archivo de configuración, el Caddyfile, es mucho más corto que el equivalente en Nginx o Apache. En este tutorial instalarás Caddy desde su repositorio oficial en Ubuntu 24.04, publicarás un sitio estático con HTTPS y lo configurarás como proxy inverso para una aplicación que corre en el mismo servidor.

Requisitos previos

  • Un servidor con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath.
  • Un usuario no root con privilegios sudo.
  • Un dominio con registros DNS de tipo A (y AAAA si usas IPv6) para your_domain y www.your_domain apuntando a la IP del servidor. Para el paso 6 necesitarás además app.your_domain.
  • Los puertos 80 y 443 libres: si tienes Nginx o Apache instalados, detenlos antes de empezar.

A lo largo de la guía sustituye your_domain por tu dominio.

Paso 1: Instalar Caddy desde el repositorio oficial

El paquete caddy de los repositorios de Ubuntu va varias versiones por detrás. Usa el repositorio oficial del proyecto, alojado en Cloudsmith. Instala las dependencias y descarga la clave de firma:

sudo apt update
sudo apt install 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

Añade el repositorio. El archivo que proporciona Caddy ya apunta a la clave que acabas de guardar mediante signed-by:

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 /etc/apt/sources.list.d/caddy-stable.list

Instala Caddy:

sudo apt update
sudo apt install caddy

El paquete crea el usuario caddy, la configuración en /etc/caddy/Caddyfile y un servicio de systemd que arranca automáticamente. Compruébalo:

caddy version
systemctl status caddy --no-pager
v2.11.4 h1:...
● caddy.service - Caddy
     Loaded: loaded (/usr/lib/systemd/system/caddy.service; enabled; preset: enabled)
     Active: active (running) since Thu 2026-09-25 10:15:02 UTC; 20s ago

Paso 2: Abrir los puertos en el firewall

Caddy necesita el puerto 80 para el reto HTTP de Let's Encrypt y para redirigir a HTTPS, y el 443 para servir el tráfico cifrado. Abre también 443/udp para HTTP/3, que Caddy activa por defecto:

sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 443/udp
sudo ufw status
Status: active

To                         Action      From
--                         ------      ----
OpenSSH                    ALLOW       Anywhere
80/tcp                     ALLOW       Anywhere
443/tcp                    ALLOW       Anywhere
443/udp                    ALLOW       Anywhere

Si visitas http://your_server_ip en el navegador verás la página de bienvenida de Caddy, que sirve la configuración por defecto.

Paso 3: Crear el contenido del sitio

Crea el directorio del sitio y una página de prueba:

sudo mkdir -p /var/www/your_domain
echo '<h1>Hola desde Caddy</h1>' | sudo tee /var/www/your_domain/index.html

Caddy se ejecuta como el usuario caddy, que solo necesita permiso de lectura. Los permisos por defecto (755 para directorios y 644 para archivos) son suficientes; no hace falta cambiar el propietario.

Paso 4: Configurar el sitio con HTTPS automático

Sustituye la configuración por defecto. Haz antes una copia por si quieres consultarla:

sudo cp /etc/caddy/Caddyfile /etc/caddy/Caddyfile.orig
sudo nano /etc/caddy/Caddyfile
{
	email admin@your_domain
}

your_domain {
	root * /var/www/your_domain
	encode zstd gzip
	file_server

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

	log {
		output file /var/log/caddy/your_domain.log
	}
}

www.your_domain {
	redir https://your_domain{uri} permanent
}

Qué hace cada parte:

  • El bloque global inicial ({ email ... }) indica el correo de contacto para la autoridad de certificación, que lo usa para avisos importantes.
  • your_domain { ... }: al usar un nombre de dominio como dirección del sitio, Caddy obtiene el certificado, lo renueva y redirige HTTP a HTTPS sin más configuración.
  • root y file_server sirven los archivos del directorio; encode comprime las respuestas con zstd o gzip.
  • header añade cabeceras de seguridad y -Server elimina la que identifica al servidor.
  • log escribe el log de acceso en JSON en /var/log/caddy/, directorio que crea el paquete. Caddy rota el archivo automáticamente.
  • El bloque www.your_domain también recibe su certificado y redirige a la versión sin www.

Formatea el archivo con el estilo estándar y valida la configuración antes de aplicarla:

sudo caddy fmt --overwrite /etc/caddy/Caddyfile
caddy validate --config /etc/caddy/Caddyfile
Valid configuration

Recarga Caddy. reload aplica la configuración nueva sin cortar las conexiones abiertas y, si tuviera errores, mantiene la anterior:

sudo systemctl reload caddy

Paso 5: Verificar HTTPS

Caddy solicita los certificados en cuanto carga la configuración. Sigue el proceso en el journal:

sudo journalctl -u caddy -f

Cuando aparezca un mensaje certificate obtained successfully para cada dominio, pulsa Ctrl+C y comprueba el sitio:

curl -I http://your_domain
curl -sI https://your_domain
curl -sI https://www.your_domain | grep -i location
HTTP/1.1 308 Permanent Redirect
Location: https://your_domain/

HTTP/2 200
alt-svc: h3=":443"; ma=2592000
content-type: text/html; charset=utf-8
strict-transport-security: max-age=31536000; includeSubDomains
x-content-type-options: nosniff

location: https://your_domain/

La cabecera alt-svc anuncia HTTP/3 a los navegadores. Los certificados se guardan en /var/lib/caddy/.local/share/caddy/ y Caddy los renueva solo, aproximadamente un tercio antes de que caduquen.

Paso 6: Configurar Caddy como proxy inverso

El uso más habitual de Caddy es publicar con HTTPS una aplicación que escucha en local, por ejemplo una aplicación Node.js en el puerto 3000. Para probarlo sin tener una, arranca un servidor de prueba en una segunda terminal:

mkdir -p ~/app && echo "Respuesta del backend" > ~/app/index.html
python3 -m http.server 3000 --bind 127.0.0.1 --directory ~/app

Añade un sitio nuevo al final del Caddyfile:

sudo nano /etc/caddy/Caddyfile
app.your_domain {
	reverse_proxy 127.0.0.1:3000
}

reverse_proxy reenvía las peticiones al backend y añade por defecto las cabeceras X-Forwarded-For, X-Forwarded-Proto y X-Forwarded-Host, para que la aplicación conozca la IP del cliente y el esquema original. También gestiona WebSockets sin configuración adicional.

Valida y recarga:

caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddy
curl https://app.your_domain
Respuesta del backend

Enrutar por ruta dentro del mismo dominio

Si la aplicación y el sitio estático comparten dominio (por ejemplo, la API en /api/), usa bloques handle. Caddy evalúa los handle en orden y ejecuta solo el primero que coincide. Este bloque sustituiría al de your_domain del paso 4:

your_domain {
	encode zstd gzip

	handle /api/* {
		reverse_proxy 127.0.0.1:3000
	}

	handle {
		root * /var/www/your_domain
		file_server
	}
}

Si el backend espera las rutas sin el prefijo /api, usa handle_path /api/* en lugar de handle /api/*: elimina el prefijo antes de reenviar la petición.

Repartir carga entre varios backends

Con varias instancias de la aplicación, lista todas en reverse_proxy y activa las comprobaciones de salud activas para que Caddy deje de enviar tráfico a las que no respondan:

app.your_domain {
	reverse_proxy 127.0.0.1:3001 127.0.0.1:3002 {
		lb_policy round_robin
		health_uri /health
		health_interval 10s
	}
}

La aplicación debe responder con un código 2xx en /health. Ajusta la ruta a la que exponga tu aplicación.

Solución de problemas

  • could not get certificate from issuer o errores de reto en el journal: la autoridad de certificación no llega a tu servidor. Comprueba con dig +short your_domain que el DNS apunta a la IP correcta y que los puertos 80 y 443 están abiertos en UFW y en el firewall del proveedor.
  • bind: address already in use al arrancar: otro servidor web ocupa el puerto 80 o 443. Identifícalo con sudo ss -tlnp | grep -E ':80|:443' y detenlo, por ejemplo con sudo systemctl disable --now nginx.
  • Los cambios del Caddyfile no se aplican: systemctl reload conserva la configuración anterior si la nueva tiene errores. Ejecuta caddy validate --config /etc/caddy/Caddyfile y revisa sudo journalctl -u caddy -n 50.
  • 403 Forbidden o 404 en el sitio estático: comprueba que la ruta de root es correcta y que el usuario caddy puede leerla con sudo -u caddy ls /var/www/your_domain.
  • 502 Bad Gateway en el proxy inverso: el backend no responde. Verifica que escucha en la dirección configurada con curl http://127.0.0.1:3000.

Conclusión

Tienes Caddy instalado desde el repositorio oficial, sirviendo un sitio estático con HTTPS automático, redirección de www, cabeceras de seguridad y compresión, y publicando una aplicación local como proxy inverso. Como siguientes pasos, puedes servir aplicaciones PHP con la directiva php_fastcgi y PHP-FPM, organizar cada sitio en su propio archivo con la directiva import, o proteger zonas privadas con basic_auth.