DEV Community

Cover image for QUIC: el protocolo de transporte que reemplaza a TCP en HTTP/3
lu1tr0n
lu1tr0n

Posted on • Originally published at elsolitario.org

QUIC: el protocolo de transporte que reemplaza a TCP en HTTP/3

Cuando abrís YouTube o cualquier sitio detrás de Cloudflare, hay una buena posibilidad de que tu navegador ya no use TCP para conectarse: usa el protocolo QUIC, que corre sobre UDP y reduce el saludo inicial de dos vueltas de red a una sola, o incluso a cero en una reconexión.

HTTP/3, la tercera versión del protocolo web, se apoya por completo en QUIC en lugar de TCP. Esta guía explica cómo funciona el protocolo QUIC por dentro, por qué la IETF lo estandarizó, cómo comprobar si un sitio ya lo usa y cómo activarlo en tu propio servidor.

TL;DR

  • Entendés por qué QUIC usa UDP en vez de TCP para evitar el bloqueo de cabeza de línea.- Sabés diferenciar el handshake de 1-RTT de QUIC del handshake de dos vueltas de TCP más TLS 1.3.- Podés comprobar con curl --http3 y con las DevTools de Chrome si un sitio sirve HTTP/3.- Aprendés a activar HTTP/3 en nginx y en Caddy con la configuración mínima necesaria.- Entendés qué es la migración de conexión y por qué le sirve a un celular que cambia de wifi a LTE.- Identificás cuándo no conviene forzar HTTP/3, por ejemplo detrás de firewalls que bloquean UDP.- Distinguís RFC 9000 (QUIC) de RFC 9114 (HTTP/3) y qué define cada documento.

Qué es el protocolo QUIC y por qué existe

El protocolo QUIC (Quick UDP Internet Connections) nació en Google alrededor de 2013 bajo el nombre gQUIC, como un experimento para resolver un problema muy concreto: TCP es demasiado rígido para la velocidad con la que quiere evolucionar la web. Cualquier cambio en el comportamiento de TCP exige actualizar el kernel de millones de servidores, routers, balanceadores de carga y sistemas operativos de usuario final, un proceso que puede tardar años en propagarse por completo.

QUIC evita ese problema moviendo casi toda la lógica de transporte fuera del kernel. Corre sobre UDP, un protocolo mucho más simple que el sistema operativo ya sabe reenviar sin inspeccionar demasiado, y deja que el control de congestión, la retransmisión y el cifrado vivan en espacio de usuario, dentro de la librería o el navegador. Eso significa que Chrome puede mejorar su implementación de QUIC con cada release, sin esperar un parche de kernel en millones de dispositivos.

La IETF tomó el diseño original de Google, lo revisó durante años en su grupo de trabajo QUIC y publicó la especificación final como RFC 9000 en mayo de 2021. El mapeo de HTTP sobre QUIC quedó documentado por separado en RFC 9114, publicado en junio de 2022: ese documento es, literalmente, la definición de HTTP/3.

Conviene separar los dos conceptos con claridad. QUIC es la capa de transporte, equivalente a TCP. HTTP/3 es la capa de aplicación que corre encima, equivalente a como HTTP/1.1 y HTTP/2 corren sobre TCP. El protocolo QUIC se puede usar para otras cosas además de HTTP, como DNS sobre QUIC, igual que TCP sirve para más aplicaciones que solo la web.

Cómo funciona el protocolo QUIC por dentro

La diferencia más visible entre QUIC y TCP es la capa sobre la que corren. TCP es un protocolo del kernel del sistema operativo, con garantías de entrega ordenada que el propio kernel se encarga de cumplir. QUIC, en cambio, vive en espacio de usuario y usa UDP solo como transporte crudo de paquetes, sin heredar ninguna de las garantías de TCP: UDP no ordena, no retransmite y no confirma nada por sí solo. QUIC reconstruye esas garantías por su cuenta, pero con un diseño distinto que le permite ser mucho más flexible.

El siguiente diagrama compara la pila de protocolos de HTTP/2 clásico contra la de HTTP/3 con QUIC:

flowchart TD
    subgraph HTTP2["Pila de HTTP/2 clasica"]
    A1["HTTP/2"] --> A2["TLS 1.3"]
    A2 --> A3["TCP"]
    A3 --> A4["IP"]
    end
    subgraph HTTP3["Pila de HTTP/3 con QUIC"]
    B1["HTTP/3"] --> B2["QUIC con TLS 1.3 integrado"]
    B2 --> B3["UDP"]
    B3 --> B4["IP"]
    end
Enter fullscreen mode Exit fullscreen mode

Notá que en HTTP/2 el cifrado con TLS es una capa separada de TCP. En QUIC, TLS 1.3 está integrado dentro del propio protocolo de transporte, no es una capa aparte. Esa integración es lo que le permite a QUIC cifrar cosas que TCP nunca pudo cifrar, como los números de secuencia de los paquetes, dejando mucha menos información visible para cualquiera que esté mirando el tráfico en el medio.
QUIC corre sobre UDP, no sobre TCP, para esquivar la rigidez del kernel.
Cada conexión QUIC puede abrir múltiples streams independientes dentro del mismo socket UDP. Un stream es, en la práctica, una secuencia de bytes ordenada que la aplicación usa para una petición HTTP puntual. La clave es que la pérdida de un paquete que pertenece al stream 1 no bloquea la entrega de los paquetes del stream 2 o 3, algo que sí pasa en HTTP/2 sobre TCP, porque TCP entrega los bytes en un único orden global sin distinguir a qué stream lógico pertenecen.

El handshake: por qué QUIC negocia una conexión en una sola vuelta

Abrir una conexión HTTPS clásica sobre TCP implica al menos dos idas y vueltas antes de que viaje el primer byte de la aplicación: una para el handshake de TCP (SYN, SYN-ACK, ACK) y otra para el handshake de TLS 1.3 (ClientHello, ServerHello con certificado, Finished). En redes con latencia alta, como una conexión móvil a un servidor lejano, esas dos vueltas se notan en cada nueva pestaña que abrís.

QUIC junta el establecimiento del transporte y el cifrado en un solo intercambio. El primer paquete que manda el cliente ya lleva el ClientHello de TLS 1.3 embebido; el servidor responde con su ServerHello, el certificado y los parámetros de transporte en el mismo vuelo de paquetes. Con eso alcanza un handshake de 1-RTT en una conexión nueva, la mitad de vueltas que TCP más TLS por separado.

Si el cliente ya se conectó antes a ese servidor y guardó una sesión previa, puede mandar datos de aplicación en el primerísimo paquete, antes incluso de que el servidor confirme nada: eso es 0-RTT. El riesgo es que un atacante podría capturar y reenviar (replay) ese primer paquete, así que 0-RTT solo es seguro para peticiones idempotentes como un GET sin efectos secundarios, nunca para un POST que cobra algo o modifica estado.

sequenceDiagram
    participant C as Cliente
    participant S as Servidor
    Note over C,S: Primera conexion con QUIC, handshake de 1-RTT
    C->>S: Initial packet con ClientHello
    S-->>C: Initial mas Handshake con ServerHello y certificado
    C->>S: Handshake completo y datos de aplicacion HTTP/3
    Note over C,S: En una reconexion con 0-RTT, los datos van en el primer paquete
Enter fullscreen mode Exit fullscreen mode

Multiplexado sin bloqueo de cabeza de línea

El bloqueo de cabeza de línea (head-of-line blocking) es el motivo técnico más citado para justificar HTTP/3. En HTTP/2 sobre TCP, si se pierde un solo paquete que forma parte de un stream cualquiera, TCP retiene todos los bytes posteriores en el buffer del receptor hasta reenviar y confirmar ese paquete perdido, aunque esos bytes pertenezcan a un stream totalmente distinto que no tuvo ninguna pérdida.

QUIC resuelve esto porque cada stream tiene su propio espacio de números de secuencia dentro del protocolo. Un paquete perdido solo bloquea el stream al que pertenece; los demás streams de la misma conexión siguen entregando datos a la aplicación sin esperar ningún reenvío ajeno.

flowchart LR
    A["Una sola conexion QUIC"] --> B["Stream 1: imagen.jpg"]
    A --> C["Stream 2: estilos.css"]
    A --> D["Stream 3: api/pedidos"]
    B -.->|"paquete perdido"| E["Solo el stream 1 espera reenvio"]
    C --> F["Stream 2 sigue entregando datos"]
    D --> G["Stream 3 sigue entregando datos"]
Enter fullscreen mode Exit fullscreen mode

En una página con decenas de recursos (imágenes, hojas de estilo, llamadas a API), esto importa especialmente en redes con pérdida de paquetes frecuente, como el wifi saturado de un café o una red móvil con mala cobertura, donde una sola pérdida ya no arrastra a toda la página.

Ejemplos prácticos: comprobar y activar HTTP/3

Antes que nada conviene confirmar que tu versión de curl puede negociar el protocolo QUIC, porque no todas las builds lo incluyen:

curl -V | grep -i http3
Enter fullscreen mode Exit fullscreen mode

Este comando lista las capacidades de tu build de curl; si la línea de Features incluye HTTP3, podés forzar una petición sobre QUIC directamente.

💡 Tip: corré curl -V antes que nada. Si la línea de protocolos no incluye HTTP3, tu build de curl no puede negociarlo aunque el servidor lo soporte.

curl --http3 -o /dev/null -s -w "protocolo negociado: %{http_version}\n" https://cloudflare.com/
Enter fullscreen mode Exit fullscreen mode

Este comando descarta el cuerpo de la respuesta y solo imprime la versión de HTTP que terminó usando la conexión. Si el resultado muestra 3, el sitio respondió sobre QUIC en esa petición puntual.

Del lado del servidor, activar HTTP/3 en nginx (a partir de la rama 1.25, que incorpora el módulo ngx_http_v3_module) implica abrir un listener UDP para QUIC además del listener TCP habitual, y anunciarlo con la cabecera Alt-Svc:

server {
    listen 443 quic reuseport;
    listen 443 ssl;
    http2 on;

    ssl_certificate     /etc/ssl/certs/programacion.crt;
    ssl_certificate_key /etc/ssl/private/programacion.key;
    ssl_protocols TLSv1.3;

    add_header Alt-Svc 'h3=":443"; ma=86400';

    location / {
        root /var/www/programacion;
    }
}
Enter fullscreen mode Exit fullscreen mode

El flag reuseport le permite al kernel repartir los paquetes UDP entrantes entre varios procesos worker de nginx, algo necesario porque UDP no tiene el concepto de conexión aceptada que sí tiene TCP: sin reuseport, todos los paquetes QUIC terminarían apilados en un único worker. La cabecera Alt-Svc es lo que le dice al navegador que la próxima vez pruebe QUIC en ese mismo puerto; es necesaria porque la primerísima visita de un cliente siempre entra por TCP y TLS, QUIC recién se activa a partir de la segunda petición, una vez que el navegador vio ese header.

Cómo empezar paso a paso

Para llevar HTTP/3 a un servidor propio, en orden:

  • Confirmá que tu servidor web soporta QUIC: nginx 1.25 o superior, Caddy (lo trae activado por defecto desde su versión 2) o un proxy como HAProxy 2.6 o superior con el módulo QUIC.- Abrí el puerto UDP correspondiente, normalmente 443/udp, en el firewall del servidor y en cualquier grupo de seguridad de la nube que uses, además del 443/tcp que ya tenías configurado.- Configurá el certificado TLS 1.3 igual que lo harías para HTTPS normal: QUIC reutiliza exactamente el mismo certificado.- Agregá el listener quic y la cabecera Alt-Svc como en el ejemplo de arriba.- Recargá el servidor y verificá con curl --http3 o con la pestaña Network de las DevTools de Chrome, columna Protocol, buscando el valor h3. El header Alt-Svc es lo que le avisa al navegador que puede usar QUIC. Con Caddy el proceso es más corto porque HTTP/3 viene activado por defecto en cuanto el servidor obtiene un certificado válido vía ACME; no hace falta tocar ningún flag adicional para que empiece a anunciarlo.

Casos de uso reales

Cloudflare fue una de las primeras redes en habilitar HTTP/3 en producción para sus clientes, y documentó el proceso y la motivación en su blog técnico. Google lo usa en YouTube y en la mayoría de sus servicios desde antes incluso de la estandarización final del RFC, porque fue el origen del protocolo. Los navegadores Chrome, Firefox, Safari y Edge lo soportan de forma nativa desde hace varias versiones y lo negocian automáticamente cuando el servidor lo anuncia.

Fuera del navegador, el protocolo QUIC también sirve de base para DNS sobre QUIC (DoQ), pensado para resolver nombres con menos latencia y más privacidad que el DNS tradicional sobre UDP plano, y para protocolos de videollamada y streaming en tiempo real que necesitan baja latencia y tolerancia a pérdida de paquetes sin renunciar a cifrado moderno.

Errores comunes y gotchas

El gotcha más frecuente es asumir que activar QUIC en el servidor alcanza. Muchas redes corporativas y algunos proveedores móviles bloquean directamente el tráfico UDP saliente por el puerto 443, porque tradicionalmente ese puerto solo llevaba TCP. Cuando eso pasa, el navegador simplemente vuelve a TCP y TLS sin avisar al usuario ni al operador del sitio.

⚠️ Ojo: no asumas que HTTP/3 está funcionando solo porque lo configuraste en el servidor. Muchos firewalls corporativos bloquean UDP/443 por completo y el navegador cae de nuevo a TCP sin avisar; verificá siempre con curl o DevTools desde la red real del usuario final.

Otro error común es olvidar la cabecera Alt-Svc: sin ella, ningún navegador sabe que puede intentar QUIC, porque el descubrimiento depende de haberla visto en una respuesta anterior servida sobre TCP.

Un tercer problema aparece en infraestructuras con balanceadores de carga que hacen round robin de paquetes UDP sin entender QUIC: como cada conexión QUIC se identifica por un Connection ID y no por la tupla IP y puerto, un balanceador ingenuo puede mandar paquetes de la misma conexión a servidores backend distintos y romperla por completo. Los balanceadores modernos con soporte QUIC leen el Connection ID para mantener afinidad de sesión.

Comparativa con HTTP/1.1 y HTTP/2

OpciónCuándo usarlaVentajaLimitaciónHTTP/1.1Compatibilidad máxima con clientes muy viejos o dispositivos embebidosSimplicidad, texto plano fácil de depurarConexiones seriales por origen, sin multiplexado realHTTP/2Sitios con muchos recursos que ya corren sobre infraestructura TCP existenteMultiplexado de streams sobre una sola conexión TCP, server pushBloqueo de cabeza de línea a nivel de TCP si se pierde un paqueteHTTP/3Usuarios en redes móviles inestables o sitios con muchísimos recursos paralelosHandshake de 1-RTT o 0-RTT, streams verdaderamente independientes, migración de conexiónNecesita UDP/443 abierto y servidores o balanceadores con soporte QUIC

Profundizando: migración de conexión y control de congestión conectable

Una consecuencia poco conocida de identificar las conexiones por Connection ID en lugar de por IP y puerto es la migración de conexión. Cuando tu celular pasa de wifi a datos móviles, cambia de dirección IP, pero el Connection ID de QUIC sigue siendo el mismo. El servidor reconoce que es la misma sesión y no hace falta un handshake nuevo: la conexión simplemente continúa donde estaba.

stateDiagram-v2
    [*] --> ConexionEnWifi
    ConexionEnWifi --> Migrando: la red cambia de wifi a LTE
    Migrando --> ConexionEnLTE: mismo Connection ID, sin handshake nuevo
    ConexionEnLTE --> [*]
Enter fullscreen mode Exit fullscreen mode

Otra pieza técnica que cambia por completo es la compresión de cabeceras. HTTP/2 usa HPACK, un esquema que depende de que las cabeceras lleguen en un orden estricto para mantener sincronizada una tabla dinámica compartida entre cliente y servidor. Ese orden estricto es exactamente lo que QUIC rompe a propósito, porque los streams son independientes y pueden llegar en cualquier orden. Por eso HTTP/3 usa QPACK, un esquema de compresión rediseñado que separa la tabla dinámica de la entrega de cada stream, usando un stream de control aparte para sincronizar las actualizaciones sin bloquear el resto del tráfico.

Al vivir en espacio de usuario, el control de congestión de QUIC tampoco depende del kernel del sistema operativo. Cada implementación, la de Chrome, la de un servidor nginx, la de una librería como quiche, puede traer su propio algoritmo, sea CUBIC clásico o variantes más agresivas como BBR, y actualizarlo con cada release de la aplicación, sin esperar un parche de sistema operativo.

💭 Clave: QUIC no solo cambia el transporte, cambia quién controla el transporte: pasa de ser una decisión del sistema operativo a ser una decisión de la aplicación.

Esa misma flexibilidad tiene un costo real. Como cada implementación de QUIC hace su propio manejo de paquetes en espacio de usuario, el uso de CPU por conexión suele ser mayor que el de TCP, que delega buena parte del trabajo a rutas ya optimizadas del kernel. Es un trade-off honesto: menos rigidez y más control a cambio de más ciclos de CPU por byte transmitido, algo que conviene medir antes de migrar tráfico masivo.

📖 Resumen en Telegram: Ver resumen

Tu próximo paso: corré curl -V | grep http3 en tu terminal y, si tu build lo soporta, probá curl --http3 -o /dev/null -s -w "%{http_version}\n" https://cloudflare.com/ para ver el protocolo negociado en vivo.

Preguntas frecuentes

¿Qué diferencia hay entre QUIC y HTTP/3?

QUIC es el protocolo de transporte, equivalente a TCP. HTTP/3 es el protocolo de aplicación que corre encima de QUIC, equivalente a como HTTP/1.1 y HTTP/2 corren sobre TCP.

¿Necesito cambiar el código de mi aplicación para servir HTTP/3?

No. Generalmente es un cambio de configuración en el servidor web o en el CDN; el código de la aplicación sigue hablando HTTP normal sin enterarse de qué transporte se usó por debajo.

¿Todos los navegadores soportan HTTP/3?

Los navegadores modernos, como Chrome, Firefox, Safari y Edge, lo soportan y lo negocian automáticamente cuando el servidor lo anuncia con la cabecera Alt-Svc.

¿QUIC es más seguro que TCP más TLS por separado?

QUIC cifra más metadata de la conexión que TCP y TLS por separado, incluyendo los números de paquete, lo que reduce lo que un observador en la red puede inferir sobre el tráfico. El cifrado del contenido en sí sigue siendo TLS 1.3 en ambos casos.

¿Por qué algunas redes corporativas bloquean HTTP/3?

Porque corre sobre UDP y muchos firewalls solo permiten tráfico TCP saliente por el puerto 443. Cuando el navegador no logra negociar QUIC, cae automáticamente a TCP y TLS sin que el usuario lo note.

¿Dónde está la especificación oficial?

El transporte QUIC está definido en el RFC 9000 y el mapeo de HTTP/3 sobre QUIC en el RFC 9114, ambos publicados por la IETF.

Referencias

📱 ¿Te gusta este contenido? Únete a nuestro canal de Telegram @programacion donde publicamos a diario lo más relevante de tecnología, IA y desarrollo. Resúmenes rápidos, contenido fresco todos los días.

Top comments (0)