SSD Nodes Learn 🎉 VPS desde $5.50/mes
Guías Matt ConnorPor Matt Connor · Actualizado 2026-08-12

Nginx vs Caddy vs Traefik: cual elegir para proxy inverso

Comparamos Nginx, Caddy y Traefik para gestionar servicios bajo una IP pública. Analizamos la automatizacion de certificados TLS, configuracion en Docker y rendimiento de red.

Nginx frente a Caddy frente a Traefik: la respuesta corta

Nginx, Caddy y Traefik realizan la misma función como proxy inverso: escuchan en el puerto 443, leen el nombre de host en cada petición y lo envían al servicio correcto en su VPS. Cualquiera de los tres permite situar cuatro aplicaciones autohospedadas tras una única dirección IP pública, y todos son lo suficientemente rápidos como para que sus aplicaciones sean el cuello de botella. Lo que varía es cómo obtiene cada uno el certificado TLS (seguridad de la capa de transporte) y cuánto esfuerzo de configuración requiere cada aplicación adicional. La otra diferencia aparece más adelante, el día que necesite algo que los tutoriales comunes omiten.

Elija Caddy si desea que la gestión de HTTPS sea automática y sus servicios son aplicaciones web convencionales. Elija Traefik si todo se ejecuta en Docker Compose y añade un servicio nuevo cada pocas semanas. Elija Nginx si ya lo utiliza, o si necesita caché de respuestas, certificados de cliente, reenvío TCP puro o una configuración existente extensa que prefiere no reescribir.

¿Cómo obtiene cada uno un certificado TLS?

Este eje es el factor decisivo para la mayoría, así que empiece por aquí. Los tres terminan con el mismo certificado de la misma autoridad. El trabajo necesario para lograrlo no es el mismo.

Caddy solicita el certificado porque usted definió un nombre de host. Escriba app.example.com como dirección del sitio y Caddy solicitará un certificado mediante ACME (entorno de gestión automática de certificados) a Let's Encrypt, recurrirá a ZeroSSL si falla, servirá la redirección de HTTP a HTTPS en el puerto 80 y se renovará automáticamente. No hay una segunda herramienta ni un temporizador que comprobar. Los certificados residen en el directorio de datos del usuario caddy, /var/lib/caddy/.local/share/caddy en una instalación mediante paquetes, así que añada esa ruta a sus copias de seguridad o acepte una nueva emisión tras una reconstrucción. Para un nombre de host que no sea público, tls internal firma con la propia autoridad de certificación local de Caddy. Esto le ofrece lo mismo que crear un certificado autofirmado en Ubuntu, con la renovación gestionada por usted.

Nginx no tiene un cliente ACME. Certbot obtiene el certificado y su plugin --nginx reescribe su bloque de servidor para añadir el listener 443 y la redirección. La renovación se ejecuta desde un temporizador de systemd que instala el paquete, por lo que hay dos partes móviles y dos elementos que verificar: systemctl list-timers | grep certbot muestra que el temporizador existe y sudo certbot renew --dry-run demuestra que la ruta de renovación sigue funcionando. El paso a paso se encuentra en Certbot en Ubuntu 24.04 con Nginx, y la misma herramienta cubre un certificado wildcard mediante el desafío DNS-01 cuando tiene más subdominios de los que desea listar.

Traefik incluye su propio cliente ACME. Usted configura un resolvedor de certificados en la configuración estática y cada router puede utilizarlo. Todo el estado, incluyendo la clave de cuenta y los certificados, reside en un único archivo acme.json. Traefik se niega a utilizar ese archivo si es legible por alguien que no sea su propietario, y se lo notificará antes de descartar el resolvedor:

The ACME resolver "le" is skipped from the resolvers list because: unable to get ACME account: permissions 660 for /letsencrypt/acme.json are too open, please use 600

Monte un directorio y deje que Traefik cree el archivo por sí mismo. Si lo crea primero con touch, heredará su umask, que es como la mayoría de los usuarios se encuentran con ese error.

Una cosa es cierta para los tres. El desafío HTTP-01 requiere que el puerto 80 sea accesible desde Internet, ya que la autoridad de certificación se conecta de vuelta a él. Si solo abre el puerto 443, la emisión fallará con un mensaje que parece un error de DNS.

La misma tarea de enrutamiento para dos aplicaciones en tres configuraciones

La tarea: app.example.com se dirige a un servicio en 127.0.0.1:8080, files.example.com se dirige a uno en 127.0.0.1:8081, ambos mediante HTTPS. Aquí se muestra la configuración completa en cada proxy, para que la diferencia en verbosidad sea visible en lugar de solo una afirmación.

Nginx

# /etc/nginx/sites-available/app.example.com
server {
    listen 80;
    server_name app.example.com;

    location / {
        proxy_pass http://127.0.0.1:8080;
        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;
    }
}

Luego, cree el enlace simbólico, pruébelo, recargue y añada el certificado.

sudo ln -s /etc/nginx/sites-available/app.example.com /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx
sudo certbot --nginx -d app.example.com

nginx -t ejecutando syntax is ok y test is successful es la comprobación que debe realizar antes de cada recarga. La segunda aplicación es el mismo bloque con el nombre de host y el puerto cambiados. Las líneas proxy_set_header no son decorativas: cuando proxy_pass nombra una dirección, nginx envía Host: 127.0.0.1:8080 al backend de forma predeterminada, por lo que una aplicación que construye URLs absolutas a partir de la cabecera Host enviará a sus usuarios a localhost.

Caddy

app.example.com {
	reverse_proxy 127.0.0.1:8080
}

files.example.com {
	reverse_proxy 127.0.0.1:8081
}
sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddy

Ese es el archivo completo. reverse_proxy configura X-Forwarded-For, X-Forwarded-Proto y X-Forwarded-Host por sí mismo y, de forma predeterminada, ignora lo que el cliente haya enviado en esas cabeceras, por lo que una petición no puede engañar a su backend sobre su origen. Los certificados, la redirección del puerto 80 y la renovación se derivan de las dos direcciones de los sitios. Nada más en el archivo los solicita.

Traefik

Traefik necesita una configuración estática antes de enrutar nada. Como servicio de Compose, con la etiqueta de imagen vigente a agosto de 2026:

services:
  traefik:
    image: traefik:v3.7
    command:
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      - "--certificatesresolvers.le.acme.email=you@example.com"
      - "--certificatesresolvers.le.acme.storage=/letsencrypt/acme.json"
      - "--certificatesresolvers.le.acme.httpchallenge.entrypoint=web"
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - ./letsencrypt:/letsencrypt

Cada aplicación lleva entonces su propio enrutamiento, mediante etiquetas, en su propio archivo compose:

    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.app.rule=Host(`app.example.com`)"
      - "traefik.http.routers.app.entrypoints=websecure"
      - "traefik.http.routers.app.tls.certresolver=le"
      - "traefik.http.services.app.loadbalancer.server.port=8080"

loadbalancer.server.port es el puerto dentro del contenedor, no un puerto publicado, porque Traefik accede al contenedor a través de una red Docker compartida. La aplicación no necesita ninguna línea ports: en absoluto, y ese es el beneficio real: solo Traefik está publicado. La configuración completa, incluyendo la red compartida y el middleware de redirección, se encuentra en enrutamiento de múltiples aplicaciones con Traefik y Docker Compose.

¿Cuánto cuesta configurar cada aplicación adicional?

ChartNon-blank config lines for the same two-app routing job
The data behind this chart
[
  {
    "tool": "Nginx",
    "proxy_setup_lines": 0,
    "lines_per_app": 11
  },
  {
    "tool": "Caddy",
    "proxy_setup_lines": 0,
    "lines_per_app": 3
  },
  {
    "tool": "Traefik",
    "proxy_setup_lines": 17,
    "lines_per_app": 5
  }
]

Contando a partir de los bloques anteriores. El bloque de servidor de Nginx tiene 11 líneas que no están en blanco, y debe escribirlo de nuevo para cada nombre de host. El bloque de sitio de Caddy tiene 3 líneas. Traefik requiere 17 líneas de configuración estática antes de servir una sola petición, y luego 5 etiquetas por aplicación.

Analice el intercambio, no el ganador. Traefik es lo que más cuesta antes de la primera aplicación y lo que menos cuesta por cada aplicación posterior; ambos totales se igualan alrededor del tercer sitio. Por debajo de esa cifra, la configuración estática es una carga innecesaria. Por encima, las etiquetas toman ventaja y la mantienen, ya que el enrutamiento reside junto al servicio al que dirige. Si elimina el servicio, su ruta desaparece con él, algo que los archivos de configuración centralizados gestionan mal: bloques de servidor obsoletos para aplicaciones que dejaron de existir hace meses.

El recuento de líneas también favorece a Nginx. Cada uno de esos bloques requiere un enlace simbólico, un nginx -t, una recarga y una ejecución de certbot, mientras que la edición en Caddy requiere una recarga y la de Traefik no requiere ningún comando. Los tres se recargan sin interrumpir las conexiones activas. La diferencia radica en la cantidad de pasos independientes que debe recordar a las tres de la mañana.

¿Cuál conoce sus contenedores?

Traefik monitoriza el socket de Docker y construye enrutadores a partir de las etiquetas de los contenedores a medida que estos se inician y detienen. Ninguna otra opción hace esto. Tanto Nginx como Caddy requieren una edición de configuración y una recarga cuando aparece un nuevo contenedor, además de necesitar una dirección a la que puedan acceder: ya sea un puerto publicado en loopback o una red Docker compartida a la que esté conectado el proxy.

Esa funcionalidad tiene un precio y merece la pena exponerlo claramente. Traefik lee /var/run/docker.sock. Cualquiera que pueda comunicarse con ese socket puede iniciar un contenedor con el sistema de archivos del host montado en su interior, lo cual equivale a ser root en el host. Montarlo como solo lectura reduce el riesgo sin eliminarlo. Si esto es relevante para su modelo de amenazas, coloque un proxy de socket intermedio que solo exponga los endpoints de la lista de contenedores que Traefik necesita.

Caddy puede realizar el descubrimiento basado en etiquetas mediante un plugin de la comunidad, pero los plugins de Caddy se compilan dentro del binario, por lo que debe crear un binario personalizado o una imagen personalizada con xcaddy y, a partir de ahí, usted es responsable de esa compilación y sus actualizaciones. Para tres o cuatro servicios, editar un Caddyfile requiere menos trabajo.

Websockets y streaming: qué falla y por qué

Nginx es el que requiere configuración adicional. Una conexión WebSocket comienza como una petición HTTP que transporta Upgrade: websocket, y Nginx no transmite cabeceras hop-by-hop al backend a menos que se le indique explícitamente.

# /etc/nginx/conf.d/upgrade-map.conf
map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

Luego, dentro del bloque location, deben estar presentes estas tres líneas:

        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;

Si las omite, la consola del navegador mostrará WebSocket connection to 'wss://app.example.com/ws' failed mientras que el registro del backend mostrará un GET ordinario. La variable map existe porque, de lo contrario, se enviaría un Connection: upgrade codificado de forma rígida en cada petición, incluidas las simples que deberían indicar close.

Otros dos valores predeterminados de Nginx causan problemas. proxy_read_timeout es de 60 segundos y se aplica al túnel tras la actualización; por tanto, un WebSocket sin tráfico durante un minuto será cerrado por el proxy. Además, los eventos enviados por el servidor (SSE) llegan tarde o en ráfagas a menos que configure proxy_buffering off; en esa ubicación, ya que Nginx retiene la respuesta en su búfer mientras la página espera.

Caddy realiza la actualización y cambia la conexión a un túnel bidireccional sin necesidad de directivas. También vacía el búfer inmediatamente cuando la respuesta es text/event-stream o no tiene una longitud definida, por lo que el streaming funciona sin ajustes. Traefik permite el paso de actualizaciones y no almacena respuestas en búfer a menos que añada manualmente su middleware buffering. Si sus servicios incluyen chats, terminales web, seguimiento de registros en tiempo real o paneles dinámicos, esta es una diferencia significativa en la cantidad de configuración que deberá escribir y depurar.

El bloque de servidor Nginx completo, incluyendo websockets y SSE
map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
}

server {
    listen 80;
    server_name app.example.com;
    client_max_body_size 64m;

    location / {
        proxy_pass http://127.0.0.1:8080;
        proxy_http_version 1.1;
        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;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_read_timeout 3600s;
        proxy_buffering off;
    }
}

La directiva map pertenece al contexto http, no al interior de server, así que manténgala en su propio archivo bajo /etc/nginx/conf.d/. Desactive proxy_buffering solo en las ubicaciones que realicen streaming, ya que el almacenamiento en búfer es lo que permite a Nginx liberar al trabajador del backend rápidamente en respuestas ordinarias. Certbot reescribe este bloque al ejecutarlo, así que revise el archivo nuevamente después de hacerlo.

¿Qué ocurre cuando necesita algo inusual?

Aquí es donde Nginx justifica sus líneas adicionales.

  • Certificados de cliente, también llamados mTLS (TLS mutuo), donde el cliente también debe presentar un certificado. Nginx requiere ssl_client_certificate /etc/ssl/ca.pem; y ssl_verify_client on; en el bloque del servidor. Caddy requiere un bloque client_auth dentro de tls. Las etiquetas de Traefik no pueden expresarlo en absoluto: debe definir una opción TLS en un proveedor de archivos y apuntar el enrutador hacia ella con traefik.http.routers.app.tls.options=mtls@file. El modelo de "todo en etiquetas" encuentra una excepción la primera vez que necesita esto.
  • Cargas de archivos grandes. Nginx limita el cuerpo de las peticiones a 1 MB por defecto. Una carga mayor devuelve 413 Request Entity Too Large, y el registro de errores indica client intended to send too large body. Aumente client_max_body_size. Caddy y Traefik no establecen límites de cuerpo por defecto, por lo que la petición llega a su aplicación y es el límite de la propia aplicación el que decide.
  • Caché de respuestas. Nginx cuenta con proxy_cache, y es una función madura. Caddy requiere un plugin compilado. La versión de código abierto de Traefik no tiene caché HTTP, lo cual sorprende a quienes asumen que todo proxy realiza caché.
  • TCP o UDP sin procesar, para un puerto de base de datos o un servidor de juegos. Nginx dispone del módulo stream. Traefik tiene enrutadores TCP y UDP en sus propios puntos de entrada. Caddy requiere otro plugin, lo que implica otra compilación personalizada.
  • Un servidor web ya detrás del proxy. Si el servicio es una aplicación PHP clásica, entonces una pila LAMP en Ubuntu 24.04 ya incluye Apache, y colocar un proxy delante le deja con dos lugares donde configurar cabeceras y dos que pueden reescribir una URL. Decida cuál de ellos termina el TLS y mantenga el otro en HTTP plano vinculado a la interfaz de bucle local (loopback).

La trampa del firewall tras esta elección

El objetivo de un proxy inverso es que solo los puertos 80 y 443 estén abiertos. Docker deshace esto de forma silenciosa. Publicar un puerto con -p 8080:80 escribe una regla DNAT en la tabla nat, y esa regla se evalúa antes que las reglas INPUT que gestiona ufw, por lo que ufw deny 8080 no la bloquea y su aplicación queda expuesta en la internet pública junto al proxy que configuró con tanto cuidado. Vincule los puertos publicados a la interfaz loopback con 127.0.0.1:8080:80, o prescinda de ports: por completo y permita que el proxy alcance al contenedor a través de una red Docker, que es lo que hace el ejemplo de Traefik anterior. El mecanismo y la solución se encuentran en por qué los puertos publicados por Docker omiten ufw.

Realice la prueba desde una máquina que no sea el VPS, ya que una comprobación ejecutada en el propio servidor siempre tendrá éxito:

curl --max-time 5 http://your.server.address:8080

Connection refused o un tiempo de espera (timeout) es el resultado que busca. Una respuesta HTTP significa que la aplicación es accesible sin pasar por su proxy, y todo lo que configuró anteriormente es meramente decorativo.

¿Qué proxy debería elegir?

Sitios mayormente estáticos, además de una o dos aplicaciones: Caddy. El HTTPS automático elimina la mayor tarea recurrente que tendrá. La configuración es lo suficientemente corta como para leerla en una sola pantalla, y un sitio estático es una línea root y una línea file_server dentro del mismo bloque de sitio. El costo es un conjunto menor de respuestas de copiar y pegar cuando algo extraño falla.

Un homelab con docker-compose al que sigue añadiendo servicios: Traefik. Después del tercer servicio, las etiquetas (labels) requieren menos trabajo que editar un archivo central, y un servicio eliminado se lleva su ruta consigo. Reserve una tarde para la configuración inicial, ya que entrypoints, routers, services y middlewares son vocabulario nuevo. Un error tipográfico en una etiqueta suele mostrarse como un 404 de Traefik en lugar de un fallo al iniciar, así que lea docker logs traefik para ver el error de análisis antes de asumir que la aplicación está rota.

Una configuración de Nginx existente, o cualquier requisito de la lista anterior: Nginx. Ya tiene una respuesta para el almacenamiento en caché de respuestas y para certificados de cliente, y casi todas las guías de terceros lo asumen. El costo es que los certificados y el soporte para websockets son cosas que usted configura en lugar de cosas que obtiene.

Una regla se aplica independientemente de lo que elija. Exactamente un proceso escucha en la interfaz pública, y todo lo demás escucha en loopback o en una red privada de Docker.

FAQ

¿Qué proxy inverso es mejor para unas pocas aplicaciones Docker en un mismo VPS?

Para tres o cuatro servicios que se añaden de vez en cuando, Traefik compensa, ya que cada aplicación lleva sus propias etiquetas de enrutamiento y no requiere editar un archivo central. Si los servicios son estables y el objetivo principal es delegar la gestión de HTTPS, Caddy requiere menos aprendizaje y es menos propenso a errores. Elija Nginx si ya lo conoce o si necesita una funcionalidad que los otros dos no ofrecen, como el almacenamiento en caché de respuestas o un listener TCP simple.

¿Realmente Caddy no necesita configuración de certificados?

Para el caso habitual, sí. Definir un nombre de host público como dirección del sitio es toda la configuración necesaria: Caddy solicita el certificado mediante ACME, gestiona la redirección desde el puerto 80 y lo renueva antes de que caduque. Deben cumplirse dos condiciones. El puerto 80 debe ser accesible desde internet para el desafío HTTP-01, y el registro DNS A o AAAA del nombre de host debe apuntar al VPS, ya que la autoridad de certificación resuelve el nombre y se conecta a él.

¿Puedo ejecutar Nginx y Traefik en el mismo VPS?

No en los mismos puertos. El que inicie en segundo lugar fallará al intentar enlazar, y Nginx mostrará bind() to 0.0.0.0:443 failed (98: Address already in use), mientras que Traefik registrará un error de enlace similar y se cerrará. Ejecute un solo proxy en los puertos 80 y 443 y coloque todo lo demás detrás de él. Si está realizando una migración, mueva los nombres de host uno a uno: deje que el proxy frontal redirija al antiguo a través de un puerto de loopback hasta que el último sitio haya sido migrado.

¿Por qué mis websockets se desconectan tras 60 segundos detrás de Nginx?

proxy_read_timeout tiene un valor predeterminado de 60 segundos que se aplica al túnel una vez completada la actualización; por tanto, una conexión sin tráfico durante un minuto es cerrada por el proxy en lugar de por su aplicación. Auméntelo en esa ubicación con proxy_read_timeout 3600s;, o configure la aplicación para que envíe un frame de ping cada 30 segundos. Caddy y Traefik no cierran las conexiones actualizadas inactivas con un temporizador de un minuto, razón por la cual la misma aplicación puede parecer estable detrás de ellos e inestable detrás de Nginx.