SSD Nodes Learn Hosting plans →
Guías Matt ConnorPor Matt Connor · Actualizado 2026-08-24

Nginx, Caddy o Traefik: qué proxy elegir

Compara Nginx, Caddy y Traefik en un VPS con una IP pública: certificados TLS, coste de configuración por aplicación, websockets y enrutamiento Docker.

Nginx frente a Caddy frente a Traefik: respuesta breve

Nginx, Caddy y Traefik hacen el mismo trabajo como reverse proxy: escuchan en el puerto 443, leen el nombre de host de cada petición y la reenvían al servicio correcto en su VPS. Cualquiera de los tres permite publicar cuatro aplicaciones autohospedadas detrás de una sola dirección IP pública, y todos tienen un rendimiento suficiente para que sus aplicaciones sean el componente más lento. La diferencia está en cómo obtiene cada uno el certificado TLS (seguridad de la capa de transporte) y en cuánto trabajo de configuración requiere cada aplicación adicional. La otra diferencia aparece más adelante, el día que necesita algo que los tutoriales habituales omiten.

Elija Caddy si quiere que HTTPS se gestione automáticamente 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 almacenamiento en caché de respuestas, certificados de cliente, reenvío de TCP sin modificar o una configuración existente extensa que prefiere no reescribir.

¿Cómo obtiene cada uno un certificado TLS?

Este criterio es decisivo para la mayoría de las personas, así que conviene empezar por aquí. Los tres terminan usando el mismo certificado emitido por la misma autoridad. El trabajo necesario para obtenerlo no es el mismo.

Caddy solicita el certificado porque se ha definido 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. Si falla, recurrirá a ZeroSSL, servirá la redirección de HTTP a HTTPS en el puerto 80 y renovará el certificado por su cuenta. No necesita una segunda herramienta ni un temporizador que comprobar. Los certificados se almacenan en el directorio de datos del usuario caddy, /var/lib/caddy/.local/share/caddy en una instalación mediante paquetes. Añada esa ruta a las copias de seguridad o acepte una nueva emisión después de reconstruir el sistema. Para un nombre de host que no sea público, tls internal firma el certificado con la autoridad certificadora local de Caddy. El resultado es equivalente a crear un certificado autofirmado en Ubuntu, pero Caddy gestiona la renovación.

Nginx no incluye un cliente ACME. Certbot obtiene el certificado y su complemento --nginx modifica el bloque del servidor para añadir el listener de 443 y la redirección. La renovación se ejecuta mediante un temporizador de systemd que instala el paquete. Por tanto, hay dos componentes y dos aspectos que verificar: systemctl list-timers | grep certbot muestra que existe el temporizador y sudo certbot renew --dry-run confirma que el proceso de renovación sigue funcionando. El procedimiento paso a paso está en Certbot en Ubuntu 24.04 con Nginx, y la misma herramienta permite gestionar un certificado wildcard mediante el desafío DNS-01 cuando tiene más subdominios de los que quiere enumerar.

Traefik incluye su propio cliente ACME. Configure un único resolvedor de certificados en la configuración estática y, después, cualquier router podrá usarlo. Todo el estado, incluida la clave de cuenta y los certificados, se almacena en un solo archivo acme.json. Traefik se niega a usar ese archivo si puede leerlo alguien que no sea su propietario y se lo indica 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 permita que Traefik cree el archivo. Créelo primero con touch para que herede su umask. Así es como la mayoría de las personas cumple ese requisito.

Hay un aspecto común a los tres. El desafío HTTP-01 necesita que el puerto 80 sea accesible desde Internet, porque la autoridad certificadora se conecta a él. Si abre sólo el puerto 443, la emisión falla de una forma que parece un problema de DNS.

El mismo enrutamiento de dos aplicaciones en tres configuraciones

La tarea es la siguiente: app.example.com se dirige a un servicio en 127.0.0.1:8080 y files.example.com se dirige a otro en 127.0.0.1:8081. Ambos usan HTTPS. Aquí se muestra el conjunto completo en cada proxy, para que la diferencia de extensión sea visible y no sólo 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;
    }
}

A continuación, vincúlelo, pruébelo, vuelva a cargar la configuración 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

Ejecutar nginx -t mostrando syntax is ok y test is successful es la comprobación que debe realizar antes de cada recarga. La segunda aplicación usa el mismo bloque, con el nombre de host y el puerto modificados. 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 tanto, una aplicación que genere URL absolutas a partir de la cabecera Host enviará a los usuarios a localhost. En esta explicación de un bloque server de nginx se detalla, directiva por directiva, para qué sirve cada una de esas cuatro cabeceras y por qué una barra final en proxy_pass cambia silenciosamente la ruta que recibe la aplicación.

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. De forma predeterminada, ignora lo que el cliente envíe en esas cabeceras, por lo que una petición no puede falsear ante el backend su origen. Los certificados, la redirección desde el puerto 80 y la renovación se derivan de las dos direcciones de sitio. Ninguna otra línea del archivo los solicita.

Traefik

Traefik necesita una configuración estática antes de poder enrutar peticiones. Como servicio de Compose, con la etiqueta de imagen actualizada 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

Después, cada aplicación define su propio enrutamiento mediante labels, en su propio archivo de 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:, y esa es la ventaja principal: sólo Traefik se publica. La configuración completa, incluida la red compartida y el middleware de redirección, se encuentra en esta guía para enrutar varias aplicaciones con Traefik y Docker Compose.

¿Cuánta configuración requiere 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
  }
]

Según los bloques anteriores. El bloque de servidor de Nginx tiene 11 líneas no vacías y hay que escribirlo de nuevo para cada nombre de host. El bloque de sitio de Caddy tiene 3 líneas. Traefik necesita 17 líneas de configuración estática antes de atender una sola petición y, después, 5 etiquetas por aplicación.

Analice el equilibrio, no sólo cuál gana. Traefik requiere más configuración antes de la primera aplicación y menos por cada aplicación adicional, y los dos totales se igualan aproximadamente en el tercer sitio. Por debajo de ese punto, la configuración estática es una sobrecarga innecesaria. Por encima, las etiquetas toman ventaja y la mantienen, porque el enrutamiento queda junto al servicio al que se aplica. Si elimina el servicio, también desaparece su ruta. Esto resuelve un problema habitual de los archivos de configuración centralizados: conservar 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 editar Caddy requiere una recarga y editar Traefik no requiere ningún comando. Los tres se recargan sin cerrar las conexiones activas. La diferencia está en el número de pasos independientes que debe recordar a la una de la mañana.

¿Cuál conoce sus contenedores?

Traefik supervisa el socket de Docker y crea routers a partir de las etiquetas de los contenedores cuando estos se inician y se detienen. Ninguna otra opción de esta lista hace eso. Nginx y Caddy necesitan editar la configuración y recargarla cuando aparece un contenedor nuevo. También necesitan una dirección que puedan alcanzar: un puerto publicado en loopback o una red Docker compartida a la que el proxy esté conectado.

Esa función tiene un coste, y conviene indicarlo 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. Eso proporciona acceso root al host. Montarlo en modo de solo lectura reduce el riesgo, pero no lo elimina. Si esto es importante para su modelo de amenazas, coloque un proxy de socket entre ambos que exponga únicamente los endpoints de listado de contenedores que Traefik necesita.

Caddy puede realizar el descubrimiento basado en etiquetas mediante un plugin de la comunidad. Sin embargo, los plugins de Caddy se compilan dentro del binario. Por tanto, debe crear un binario personalizado o una imagen personalizada con xcaddy y hacerse responsable de esa compilación y de sus actualizaciones. Para tres o cuatro servicios, editar un Caddyfile requiere menos trabajo.

WebSockets y streaming: qué se rompe y por qué

Nginx es el componente que necesita ayuda. Una conexión WebSocket comienza como una solicitud HTTP que incluye Upgrade: websocket, y nginx no reenvía las cabeceras hop-by-hop al upstream a menos que se lo indique.

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

Después, 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 faltan, la consola del navegador muestra WebSocket connection to 'wss://app.example.com/ws' failed, mientras que el registro del backend muestra un GET normal. map existe porque un Connection: upgrade definido de forma fija se enviaría en todas las solicitudes, incluidas las solicitudes normales que deberían indicar close.

Hay otros dos valores predeterminados de Nginx que causan problemas. proxy_read_timeout es de 60 segundos y se aplica al túnel después de la actualización, por lo que el proxy cierra un WebSocket que no tiene tráfico durante un minuto. Los eventos enviados por el servidor llegan tarde o en ráfagas hasta que se establece proxy_buffering off; en esa ubicación, porque nginx mantiene la respuesta en su búfer mientras la página espera los datos.

Caddy realiza la actualización y cambia la conexión a un túnel bidireccional sin ninguna directiva. También vacía el búfer inmediatamente cuando la respuesta es text/event-stream o no tiene una longitud conocida, por lo que el streaming funciona sin cambios. Traefik reenvía las actualizaciones y no almacena las respuestas en búfer a menos que añada manualmente su middleware buffering. Si sus servicios incluyen chat, un terminal web, seguimientos de registros o paneles en tiempo real, esta diferencia afecta de forma real a la cantidad de configuración que tendrá que escribir y depurar.

El bloque completo de servidor de Nginx, con WebSockets y SSE incluidos
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;
    }
}

map debe estar en el contexto http, no dentro de server, por lo que debe mantenerlo en su propio archivo bajo /etc/nginx/conf.d/. Desactive proxy_buffering sólo en las ubicaciones que transmiten datos, porque el almacenamiento en búfer permite que nginx libere antes el worker del backend en las respuestas normales. Certbot reescribe este bloque cuando lo ejecuta, así que vuelva a leer el archivo después.

¿Qué ocurre cuando necesita algo poco habitual?

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

  • Certificados de cliente, también llamados mTLS (TLS mutuo), cuando 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 directamente: debe definir una opción TLS en un proveedor de archivos y asociarla al router mediante traefik.http.routers.app.tls.options=mtls@file. El modelo de configurar todo mediante etiquetas tiene una excepción la primera vez que necesita esta función.
  • Cargas grandes. Nginx limita los cuerpos de las peticiones a 1 MB de forma predeterminada. Una carga mayor devuelve 413 Request Entity Too Large y el registro de errores muestra client intended to send too large body. Aumente client_max_body_size. Caddy y Traefik no establecen ningún límite de tamaño del cuerpo de forma predeterminada, por lo que la petición llega a la aplicación y se aplica el límite propio de esta.
  • Caché de respuestas. Nginx incluye proxy_cache y es una función madura. Caddy necesita un plugin compilado. La compilación de código abierto de Traefik no incluye ninguna caché HTTP, lo que sorprende a quienes dan por hecho que todos los proxies almacenan respuestas en caché.
  • TCP o UDP sin procesar, para un puerto de base de datos o un servidor de juegos. Nginx incluye el módulo stream. Traefik tiene routers TCP y UDP en sus propios entrypoints. Caddy necesita otro plugin y, por tanto, otra compilación personalizada.
  • Un servidor web situado detrás del proxy. Si el servicio es una aplicación PHP clásica, una pila LAMP en Ubuntu 24.04 ya incluye Apache, y colocar un proxy delante crea dos lugares que establecen cabeceras y dos que pueden reescribir una URL. Decida cuál termina TLS y mantenga el otro en HTTP sin cifrar, enlazado a loopback.

La trampa del firewall que sigue a esta elección

El objetivo de un reverse proxy es que sólo estén abiertos 80 y 443. Docker anula esto de forma silenciosa. Publicar un puerto con -p 8080:80 escribe una regla DNAT en la tabla nat. Esa regla se evalúa antes que las reglas INPUT que administra ufw, por lo que ufw deny 8080 no la bloquea. La aplicación queda expuesta en Internet junto al proxy que configuró cuidadosamente. Enlace los puertos publicados a loopback con 127.0.0.1:8080:80, o elimine ports: por completo y permita que el proxy acceda al contenedor a través de una red Docker, como hace el ejemplo de Traefik anterior. El mecanismo y la solución se explican en por qué los puertos publicados por Docker omiten ufw.

Pruebe desde una máquina que no sea el VPS, porque una comprobación ejecutada en el propio servidor siempre tiene éxito:

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

El resultado esperado es Connection refused o un tiempo de espera agotado. Una respuesta HTTP significa que se puede acceder a esa aplicación sin pasar por el proxy. En ese caso, toda la configuración anterior es sólo decorativa.

¿Qué proxy debe elegir?

Principalmente sitios estáticos, además de una o dos aplicaciones: Caddy. HTTPS automático elimina la tarea recurrente más importante. La configuración es lo bastante breve para leerla en una sola pantalla, y un sitio estático se reduce a una línea root y una línea file_server dentro del mismo bloque de sitio. El coste es que hay menos respuestas preparadas para copiar y pegar cuando algo falla de forma inesperada.

Un homelab con docker-compose al que añade servicios con frecuencia: Traefik. A partir del tercer servicio, las etiquetas requieren menos trabajo que editar un archivo central, y al eliminar un servicio también desaparece su ruta. Reserve una tarde para la configuración inicial, porque entrypoints, routers, services y middlewares son conceptos nuevos. Un error tipográfico en una etiqueta suele aparecer como un 404 de Traefik en lugar de impedir el arranque, así que revise docker logs traefik para encontrar el error de análisis antes de suponer que la aplicación está dañada.

Una configuración de Nginx existente o cualquier requisito de la lista anterior: Nginx. Ya ofrece una solución para la caché de respuestas y para los certificados de cliente, y prácticamente todas las guías de terceros lo dan por supuesto. El coste es que los certificados y la compatibilidad con WebSocket se configuran de forma explícita en lugar de estar disponibles automáticamente.

Se aplica una regla independientemente de la opción elegida. Exactamente un proceso escucha en la interfaz pública, y todo lo demás escucha en loopback o en una red Docker privada.

FAQ

¿Qué reverse proxy es mejor para varias aplicaciones Docker en un mismo VPS?

Para tres o cuatro servicios que añade de vez en cuando, Traefik compensa porque cada aplicación incluye sus propias etiquetas de enrutamiento y no es necesario editar un archivo central. Si los servicios son estables y principalmente quiere dejar de gestionar HTTPS, Caddy requiere menos aprendizaje y ofrece menos oportunidades de error. Elija Nginx si ya lo conoce o si necesita una función que los otros dos no ofrecen, como el almacenamiento en caché de respuestas o un listener TCP directo.

¿Caddy realmente no necesita configuración de certificados?

Para el caso normal, sí. Indicar un nombre de host público como dirección del sitio es toda la configuración necesaria: Caddy solicita el certificado mediante ACME, sirve la redirección desde el puerto 80 y lo renueva antes de que caduque. Aun así, 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 ya debe apuntar al VPS, porque la autoridad certificadora resuelve el nombre y se conecta de nuevo a él.

¿Puedo ejecutar Nginx y Traefik en el mismo VPS?

No en los mismos puertos. El que se inicia en segundo lugar no puede enlazarse al puerto, y nginx muestra bind() to 0.0.0.0:443 failed (98: Address already in use), mientras Traefik registra un error de enlace similar y termina. Ejecute un solo proxy en 80 y 443, y coloque todo lo demás detrás de él. Si está migrando, mueva los nombres de host uno por uno: haga que el proxy frontal reenvíe las peticiones al proxy anterior mediante un puerto de loopback hasta trasladar el último sitio.

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

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