⏱️ Lectura: 13 min

Un navegador con HTTP/1.1 abre hasta seis conexiones TCP por dominio para cargar una sola página; con HTTP/2 abre una y manda cientos de peticiones por ella al mismo tiempo, sin bloquearse entre sí.

📑 En este artículo
  1. TL;DR
  2. Qué es HTTP/2 y por qué importa
    1. De SPDY a HTTP/2
  3. Cómo funciona la multiplexación en HTTP/2
    1. La estructura de un frame
  4. HPACK: cómo se comprimen las cabeceras
  5. Ejemplos prácticos
  6. Cómo habilitarlo paso a paso
  7. Casos de uso reales
  8. Errores comunes y buenas prácticas
  9. Comparativa con alternativas
  10. Profundizando: prioridades y control de flujo
  11. Preguntas frecuentes
    1. ¿HTTP/2 requiere HTTPS obligatoriamente?
    2. ¿HTTP/2 hace cualquier sitio más rápido automáticamente?
    3. ¿Qué diferencia hay entre HPACK y comprimir el body con gzip?
    4. ¿Por qué existe head-of-line blocking si HTTP/2 multiplexa las peticiones?
    5. ¿gRPC puede funcionar sin HTTP/2?
    6. ¿Cómo sé si un sitio de terceros usa HTTP/2 sin acceso a su servidor?
  12. Referencias

Publicado en 2015 como RFC 7540, HTTP/2 tomó el protocolo SPDY de Google y lo convirtió en estándar del IETF. Este artículo explica cómo funciona su capa binaria, cómo comprime cabeceras con HPACK y cómo verificar en tu propio servidor que la multiplexación HTTP/2 está realmente activa.

TL;DR

  • Vas a entender cómo HTTP/2 multiplexa decenas de peticiones sobre una sola conexión TCP.
  • Vas a poder leer una captura de frames HTTP/2 y distinguir HEADERS, DATA y SETTINGS.
  • Vas a saber configurar Nginx y Node.js para servir contenido por HTTP/2 con TLS.
  • Vas a comprender cómo HPACK comprime cabeceras repetidas con tablas estática y dinámica.
  • Vas a poder verificar con curl y openssl si un servidor realmente negoció HTTP/2.
  • Vas a identificar el head-of-line blocking a nivel TCP, el motivo detrás de HTTP/3.
  • Vas a saber cuándo migrar a HTTP/2 no cambia nada y cuándo sí importa el rendimiento.

Qué es HTTP/2 y por qué importa

HTTP/1.1 procesa una petición a la vez por conexión TCP. Si pedís diez archivos, el navegador necesita hasta diez conexiones simultáneas o hacer cola. HTTP/2 resuelve esto con una capa de framing binaria: en lugar de mandar texto plano línea por línea, divide cada mensaje en frames binarios etiquetados con un número de stream, y todos esos frames viajan intercalados por la misma conexión TCP.

Esto importa porque el cuello de botella de una web moderna casi nunca es el ancho de banda: es la cantidad de conexiones y el orden en que llegan los recursos. Un sitio con 80 archivos JS, CSS e imágenes se beneficia de mandar esas 80 peticiones en paralelo por un solo socket en vez de turnarse en seis colas.

De SPDY a HTTP/2

Google empezó a experimentar con SPDY en Chrome antes de que existiera un estándar. SPDY ya introducía multiplexación y compresión de cabeceras, pero usaba el algoritmo DEFLATE genérico, la misma familia de compresión que terminó habilitando el ataque CRIME contra sesiones TLS. El grupo de trabajo HTTPbis del IETF tomó SPDY como punto de partida, corrigió ese problema de diseño con HPACK y publicó el resultado como RFC 7540 en mayo de 2015. Chrome retiró el soporte de SPDY poco después, dejando a HTTP/2 como único sucesor.

Comparación de conexiones TCP entre HTTP/1.1 y HTTP/2
HTTP/1.1 abre varias conexiones; HTTP/2 multiplexa todo en una sola. Foto de Miguel Ángel Padriñán Alba en Unsplash

Cómo funciona la multiplexación en HTTP/2

Cada request/response en HTTP/2 vive dentro de un stream: un flujo lógico identificado con un número impar (si lo abre el cliente) o par (si lo abre el servidor mediante push). Un stream se compone de frames: HEADERS para las cabeceras, DATA para el cuerpo, y frames de control como SETTINGS, WINDOW_UPDATE o RST_STREAM.

El protocolo define diez tipos de frame en total: DATA, HEADERS, PRIORITY, RST_STREAM, SETTINGS, PUSH_PROMISE, PING, GOAWAY, WINDOW_UPDATE y CONTINUATION. Cada uno lleva un identificador de stream que le permite a cualquier endpoint reensamblar los mensajes aunque lleguen entreverados.

La estructura de un frame

Cada frame arranca con un header fijo de 9 bytes: 3 bytes para la longitud del payload (hasta 16.777.215 bytes), 1 byte para el tipo de frame, 1 byte de flags y 4 bytes para el identificador de stream. Ese formato fijo permite a un parser leer el flujo de bytes sin buscar delimitadores de texto como los saltos de línea de HTTP/1.1, y es una de las razones por las que decodificar HTTP/2 es más barato en CPU que parsear texto plano.

sequenceDiagram
    participant N as Navegador
    participant S as Servidor
    N->>S: HEADERS stream 1 (index.html)
    N->>S: HEADERS stream 3 (estilo.css)
    N->>S: HEADERS stream 5 (app.js)
    S-->>N: DATA stream 3 (estilo.css)
    S-->>N: DATA stream 1 (index.html)
    S-->>N: DATA stream 5 (app.js)
    Note over N,S: los streams se intercalan en la misma conexion

Cada stream tiene su propio control de flujo mediante frames WINDOW_UPDATE, así un recurso pesado no acapara todo el ancho de banda de la conexión. El orden de entrega original se recupera del lado del receptor usando el identificador de stream de cada frame, no el orden de llegada en el cable.

HPACK: cómo se comprimen las cabeceras

Las cabeceras HTTP se repiten muchísimo entre peticiones: user-agent, accept-encoding, cookie. Mandarlas completas en cada request desperdicia bytes. HPACK (RFC 7541) resuelve esto con dos tablas: una tabla estática de 61 entradas predefinidas por el estándar y una tabla dinámica que cliente y servidor van llenando con las cabeceras que ya se enviaron antes en esa misma conexión.

Algunas entradas fijas de la tabla estática: el índice 2 representa :method: GET, el índice 4 representa :path: / y el índice 8 representa :status: 200. Si tu petición usa exactamente esos valores, HPACK manda un solo byte por cabecera en lugar de la cadena de texto completa. Si una cabecera es nueva, se codifica con Huffman y se agrega a la tabla dinámica para reutilizarla en la siguiente petición.

flowchart LR
    A["Cabecera: method GET"] --> B{"Existe en tabla estatica?"}
    B -->|"si"| C["Enviar indice de 1 byte"]
    B -->|"no"| D{"Existe en tabla dinamica?"}
    D -->|"si"| E["Enviar indice dinamico"]
    D -->|"no"| F["Codificar con Huffman y agregar a tabla dinamica"]
📌 Nota: HPACK evita a propósito el algoritmo DEFLATE genérico que usaba SPDY, porque combinar compresión con datos secretos (como cookies de sesión) en el mismo stream permitió el ataque CRIME contra TLS. HPACK separa índices de literales justamente para no reabrir ese problema.

Ejemplos prácticos

Lo más simple para confirmar que un servidor habla HTTP/2 es pedirle la respuesta con curl indicando el protocolo:

curl -I --http2 -s https://www.google.com | head -n 5

Si la negociación ALPN funcionó, la primera línea de la respuesta dice HTTP/2 200 en lugar de HTTP/1.1 200 OK. Si tu servidor no soporta HTTP/2 todavía, curl hace fallback silencioso a HTTP/1.1 sin mostrar ningún error.

Para levantar un servidor HTTP/2 real, Node.js trae soporte nativo en el módulo http2. Este ejemplo responde con una cabecera de contenido exacta y cierra el stream:

const http2 = require('node:http2');
const fs = require('node:fs');

const server = http2.createSecureServer({
  key: fs.readFileSync('server-key.pem'),
  cert: fs.readFileSync('server-cert.pem'),
});

server.on('stream', (stream, headers) => {
  console.log('Peticion a:', headers[':path']);
  stream.respond({
    ':status': 200,
    'content-type': 'text/plain; charset=utf-8',
  });
  stream.end('Hola desde HTTP/2');
});

server.listen(8443);

El objeto headers usa pseudo-cabeceras con dos puntos (:path, :method, :status) porque HTTP/2 separa la línea de estado tradicional en campos individuales dentro del mismo bloque HEADERS comprimido por HPACK.

Del lado cliente, la librería Python httpx negocia HTTP/2 automáticamente si el servidor lo soporta:

import httpx

with httpx.Client(http2=True) as client:
    response = client.get('https://programacion.example/api/status')
    print(response.http_version)

Ese ejemplo requiere instalar el extra h2 con pip install httpx[http2]. Si el servidor no soporta HTTP/2, httpx cae a HTTP/1.1 sin lanzar excepción.

Cómo habilitarlo paso a paso

En Nginx, la forma moderna (1.25.1 en adelante) separa la directiva http2 del listen:

server {
    listen 443 ssl;
    http2 on;
    ssl_certificate     /etc/nginx/certs/programacion.crt;
    ssl_certificate_key /etc/nginx/certs/programacion.key;
    server_name programacion.example;
}

En versiones anteriores de Nginx la sintaxis era listen 443 ssl http2; en una sola línea. Tras recargar con nginx -s reload, confirmá que el binario tiene el módulo compilado:

nginx -V 2>&1 | grep -o http_v2_module

Y confirmá la negociación ALPN real contra el servidor en producción:

openssl s_client -alpn h2 -connect programacion.example:443 </dev/null 2>/dev/null | grep ALPN

Si la salida muestra ALPN protocol: h2, el navegador va a usar HTTP/2 en esa conexión. En Chrome DevTools, la pestaña Network tiene una columna “Protocol” que muestra h2 por request cuando está activo.

Servidor procesando múltiples streams HTTP/2 en paralelo
Cada stream mantiene su propio control de flujo dentro de la conexión. Foto de Ludo Poiré en Unsplash

Casos de uso reales

gRPC, el framework RPC de Google, depende nativamente de HTTP/2: usa sus streams bidireccionales para soportar streaming de datos en ambas direcciones sobre una sola conexión, algo que HTTP/1.1 no puede hacer sin websockets. APIs con muchas respuestas JSON pequeñas y sitios con decenas de assets estáticos son los que más notan la diferencia frente a HTTP/1.1.

Los CDN y API gateways modernos sirven HTTP/2 por defecto desde hace años, así que la mayor parte del tráfico web ya viaja multiplexado sin que el desarrollador configure nada del lado del cliente. Donde sí importa la configuración es en el origin server: si tu backend habla HTTP/1.1 puro detrás del CDN, perdés el beneficio de multiplexación en el tramo final hacia el servidor de aplicación, aunque el usuario final sí se beneficie en el tramo hacia el CDN.

En cambio, una API que sirve una sola respuesta grande por conexión, o un servicio interno con muy pocos clientes concurrentes, gana poco al migrar: el beneficio de HTTP/2 es proporcional a cuántos recursos pequeños y paralelos hay que traer.

Errores comunes y buenas prácticas

  • Server Push mal usado: HTTP/2 permitía que el servidor empujara recursos sin que el cliente los pidiera (frames PUSH_PROMISE). En la práctica, adivinar mal qué recursos empujar desperdiciaba ancho de banda, y los navegadores fueron retirando el soporte con el tiempo. No lo diseñes como pieza central de tu arquitectura.
  • Head-of-line blocking a nivel TCP: aunque HTTP/2 multiplexa a nivel de aplicación, sigue viajando sobre una sola conexión TCP. Si se pierde un paquete, TCP bloquea la entrega de todos los streams hasta retransmitirlo, aunque los datos de otros streams ya hayan llegado. Este es exactamente el problema que HTTP/3 sobre QUIC resuelve dándole su propio control de pérdida a cada stream.
  • Límite de streams concurrentes ignorado: el frame SETTINGS negocia un máximo de streams simultáneos por conexión. Si tu cliente abre más peticiones de las que el servidor permite, quedan en cola esperando aunque la conexión esté técnicamente multiplexando: revisá el valor de SETTINGS_MAX_CONCURRENT_STREAMS si notás cuellos de botella inesperados.
  • Concatenar y hacer sprite de assets sigue siendo útil, pero menos crítico: con HTTP/1.1 era casi obligatorio combinar archivos para ahorrar conexiones; con HTTP/2 multiplexar hace razonable servir muchos archivos chicos, aunque el overhead de cada request individual no desaparece del todo.

Comparativa con alternativas

ProtocoloTransporteMultiplexaciónCompresión de cabecerasCuándo usarlo
HTTP/1.1TCPNo (una petición por conexión)NingunaCompatibilidad con clientes muy viejos
HTTP/2TCP + TLS (h2)Sí, a nivel de aplicaciónHPACKEstándar por defecto hoy en la mayoría de sitios
HTTP/3QUIC sobre UDPSí, sin head-of-line blocking entre streamsQPACKRedes móviles o con pérdida de paquetes frecuente

Profundizando: prioridades y control de flujo

La especificación original de HTTP/2 incluía un árbol de dependencias entre streams para indicar prioridad (frames PRIORITY), pero en la práctica los navegadores lo implementaron de formas inconsistentes. El IETF lo reemplazó con un esquema más simple en RFC 9218, que expresa la prioridad con una cabecera HTTP legible (priority: u=1, i) en lugar de un árbol binario complejo.

El control de flujo funciona en dos niveles: por stream y por conexión completa. Cada lado anuncia cuántos bytes está dispuesto a recibir con un frame WINDOW_UPDATE, y el emisor no puede mandar más DATA de la que la ventana permite hasta que el receptor la actualice. Esto evita que un solo stream lento sature el buffer y bloquee a los demás.

flowchart TD
    subgraph HTTP1["HTTP 1.1"]
    A1["Navegador"] --> B1["Conexion TCP 1"]
    A1 --> B2["Conexion TCP 2"]
    A1 --> B3["Conexion TCP 3"]
    B1 --> C1["Servidor"]
    B2 --> C1
    B3 --> C1
    end
    subgraph HTTP2["HTTP 2"]
    A2["Navegador"] --> B4["Conexion TCP unica"]
    B4 --> C2["Servidor"]
    end
💡 Tip: si querés inspeccionar frames HTTP/2 crudos, Wireshark los decodifica automáticamente cuando tiene la clave de sesión TLS (exportá SSLKEYLOGFILE antes de correr el navegador).

📖 Resumen en Telegram: Ver resumen

Tu próximo paso: corré openssl s_client -alpn h2 -connect tu-dominio:443 </dev/null 2>/dev/null | grep ALPN contra tu propio sitio y confirmá si ya está sirviendo HTTP/2.

Preguntas frecuentes

¿HTTP/2 requiere HTTPS obligatoriamente?

El estándar define una variante en texto plano llamada h2c, pero en la práctica ningún navegador mayor la implementa. Todos exigen TLS (h2), así que si querés HTTP/2 en el navegador necesitás certificado.

¿HTTP/2 hace cualquier sitio más rápido automáticamente?

No. El beneficio depende de cuántos recursos pequeños y paralelos carga la página. Un sitio con pocos assets grandes gana poco frente a HTTP/1.1 bien configurado.

¿Qué diferencia hay entre HPACK y comprimir el body con gzip?

Son cosas distintas: gzip o brotli comprimen el cuerpo de la respuesta (HTML, JSON, imágenes). HPACK comprime únicamente las cabeceras, usando tablas de referencia en vez de compresión genérica, precisamente para evitar la vulnerabilidad que afectó a SPDY.

¿Por qué existe head-of-line blocking si HTTP/2 multiplexa las peticiones?

Porque la multiplexación ocurre en la capa de aplicación, pero todo sigue viajando por una única conexión TCP. Si TCP pierde un paquete, retiene la entrega de todos los streams hasta retransmitirlo, sin importar que otros streams no dependan de ese paquete.

¿gRPC puede funcionar sin HTTP/2?

No de forma estándar: gRPC está diseñado sobre las capacidades de streaming bidireccional de HTTP/2 y las usa como parte central de su protocolo.

¿Cómo sé si un sitio de terceros usa HTTP/2 sin acceso a su servidor?

Abrí Chrome DevTools, pestaña Network, activá la columna “Protocol” (clic derecho en el encabezado de columnas) y recargá la página: vas a ver h2 o http/1.1 por cada request.

Referencias

  • RFC 7540: especificación oficial del protocolo HTTP/2.
  • RFC 7541: especificación de HPACK, la compresión de cabeceras de HTTP/2.
  • MDN: HTTP: documentación general sobre el protocolo HTTP y su evolución.
  • Wikipedia: CRIME: el ataque que motivó el diseño de HPACK en lugar de DEFLATE genérico.
  • RFC 9218: esquema extensible de prioridades que reemplazó al árbol de dependencias original de HTTP/2.

📱 ¿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.

Imagen destacada: Foto de Ivan N en Unsplash


Andrés Morales

Desarrollador e investigador en inteligencia artificial. Escribe sobre modelos de lenguaje, frameworks, herramientas para devs y lanzamientos open source. Cubre papers de ML, ecosistema de startups tech y tendencias de programación.

0 Comentarios

Deja un comentario

Marcador de posición del avatar

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Este sitio usa Akismet para reducir el spam. Aprende cómo se procesan los datos de tus comentarios.