# Dominando QUIC y HTTP/3 con Nginx: La Guía Completa

> Una entrada de jmrp.io, publicada como documento propio. Índice: https://jmrp.io/llms-full.txt

Canonical: https://jmrp.io/es/blog/004-enabling-quic-http3-nginx/
Language: es
Alternate: https://jmrp.io/blog/004-enabling-quic-http3-nginx/index.md
License: https://creativecommons.org/licenses/by/4.0/
Type: TechArticle
Published: 2026-01-14
Updated: 2026-09-02
Instructions re-tested: 2026-08-01 · nginx 1.31.2
Author: José Manuel Requena Plens
Summary: Inmersión profunda en QUIC y HTTP/3 — arquitectura técnica, seguridad y configuración paso a paso de Nginx para despliegue en producción.
Tags: Nginx, Networking, Security
Topics: HTTP/3 (Q58797190), QUIC (Q7265601), Nginx (Q306144), Transport Layer Security (Q206494), User Datagram Protocol (Q11163), Transmission Control Protocol (Q8803)
Build-Date: 2026-09-06

Preguntas que responde:

**¿Qué necesito para habilitar HTTP/3 en Nginx?**

Necesitas Nginx 1.25.0 o superior (el soporte HTTP/3 se añadió a mediados de 2023) compilado con --with-http_v3_module, además de una biblioteca SSL con soporte QUIC como OpenSSL 3.5.1+, BoringSSL, QuicTLS o LibreSSL 3.6+. TLS 1.3 es obligatorio para QUIC.

**¿HTTP/3 usa TCP o UDP?**

HTTP/3 funciona sobre QUIC, que se construye sobre UDP en lugar de TCP. Debes abrir el puerto UDP 443 tanto en el firewall del servidor como en el del proveedor cloud, o QUIC fallará silenciosamente y los clientes retrocederán a HTTP/2.

**¿Cómo resuelve QUIC el head-of-line blocking de TCP?**

TCP ve todo como un único flujo ordenado de bytes, así que un solo paquete perdido bloquea todos los streams multiplexados de HTTP/2 hasta su retransmisión. QUIC implementa streams en la capa de transporte, dando entrega independiente a cada stream, por lo que un paquete perdido solo afecta a su propio stream.

**¿Por qué mi navegador sigue mostrando HTTP/2 en la primera visita?**

Ver HTTP/2 en la primera visita es lo esperado: un navegador que nunca ha conectado con el origen no tiene forma de saber que habla HTTP/3, así que abre primero una conexión TCP. Durante esa petición descubre HTTP/3 —por la cabecera de respuesta Alt-Svc o por un registro DNS HTTPS que anuncia alpn=h3— y cambia a HTTP/3 en las peticiones siguientes o tras recargar.

**¿Por qué es crítico el flag reuseport para QUIC en Nginx?**

UDP no tiene conexión, así que sin reuseport todos los paquetes QUIC los maneja un único worker de Nginx, creando un cuello de botella. La función reuseport del kernel distribuye los paquetes entre los workers, pero debe aparecer en exactamente una directiva listen por dirección:puerto a nivel global o Nginx no arrancará.

**¿Cómo verifico que HTTP/3 funciona?**

Usa curl moderno con curl -I --http3 https://tusitio, activa la columna Protocolo en las DevTools del navegador y busca h3, usa herramientas online como http3check.net o cloudflare-quic.com, o filtra el log de acceso de Nginx por quic="h3".


Pasos (Habilitar QUIC y HTTP/3 en Nginx):
1. Verificar o instalar Nginx con soporte HTTP/3
2. Añadir los listeners QUIC y TCP
3. Configurar TLS 1.3 y activar HTTP/3
4. Anunciar HTTP/3 con la cabecera Alt-Svc
5. Abrir el puerto UDP 443 en el firewall
6. Verificar que HTTP/3 funciona

---

La web ha dependido de **TCP** durante más de cuatro décadas. Pero las aplicaciones modernas —con sus exigencias de interactividad en tiempo real, conectividad móvil y cargas de página instantáneas— han expuesto las limitaciones fundamentales de TCP. Llegan **QUIC** y **HTTP/3**: una reinvención completa del transporte web que abandona TCP en favor de UDP para ofrecer un internet más rápido, seguro y resiliente.

En esta guía completa, exploraremos la historia y la arquitectura de QUIC, entenderemos por qué resuelve problemas que TCP no puede, y recorreremos una configuración completa de Nginx lista para producción.

## TL;DR — QUIC y HTTP/3 en Nginx

- **QUIC funciona sobre UDP, no TCP**: HTTP/3 mapea la semántica HTTP sobre QUIC, que se construye sobre UDP y exige TLS 1.3.
- **Resuelve el HOL blocking**: los streams de QUIC son independientes en la capa de transporte, así que un paquete perdido solo bloquea su propio stream, no todos los streams multiplexados como en HTTP/2 sobre TCP.
- **Handshakes más rápidos y migración de conexión**: handshake combinado de transporte+criptografía (1-RTT, o 0-RTT al reanudar) más Connection IDs que sobreviven a los cambios de red (Wi-Fi → datos móviles).
- **Nginx necesita 1.25.0+ con `--with-http_v3_module`** y una biblioteca SSL con soporte QUIC (OpenSSL 3.5.1+, BoringSSL, QuicTLS o LibreSSL 3.6+).
- **Configuración base**: añade `listen 443 quic reuseport;`, activa `http3 on`, TLS 1.3 y la cabecera `Alt-Svc`; `reuseport` debe aparecer en exactamente un listener por dirección:puerto.
- **Abre UDP/443 en el firewall** (servidor y proveedor cloud) o QUIC fallará silenciosamente y los clientes retrocederán a HTTP/2; verifica con `curl --http3`, las DevTools o `quic="h3"` en el log de acceso.

---

## Cómo lo ejecuto en mi propia infraestructura

jmrp.io sirve HTTP/3 en sí mismo: los dos server blocks en producción llevan `listen 443 quic;` junto a los listeners TCP habituales, de modo que el sitio acepta conexiones QUIC y TLS-sobre-TCP en paralelo, según lo que soporte realmente el cliente — y lo que haya entre el cliente y el servidor.

No me fié de la palabra de la cabecera `Alt-Svc`. Ejecutar `curl --http3` directamente contra el sitio en vivo devuelve `HTTP/3 200`: un handshake QUIC completado, no una inferencia sacada de una cabecera de respuesta que un servidor mal configurado podría seguir enviando mientras cae en silencio a HTTP/2 por debajo. Esa distinción importa — muchas listas de comprobación de "HTTP/3 activado" se quedan en la cabecera y nunca confirman que el protocolo se negoció de verdad. Es un detalle pequeño de verificar, y también es la única forma de saber que un despliegue QUIC es real y no aspiracional: la cabecera no cuesta nada añadir, pero solo un handshake desde el lado del cliente demuestra que UDP/443 está realmente abierto y negociando de extremo a extremo.

Servir HTTP/3 y que se use son dos preguntas distintas, y solo la segunda necesita datos. jmrp.io está detrás de Cloudflare, que termina la conexión del cliente, así que el protocolo que negocia de verdad un visitante aparece en la analítica de zona de Cloudflare y no en mi propio log de acceso. En ocho días —del 19 al 27 de agosto de 2026, filtrando por el host `jmrp.io` para que el resto de la zona no lo tape— **HTTP/3 se llevó el 33,5 % de las peticiones que negociaron HTTP/2 o HTTP/3**: 11.208 de 33.411.

Ese denominador pide dos frases de defensa, porque la versión honesta de esta medición va sobre todo de lo que hay que tirar. Excluí HTTP/1.1 por completo, y la exclusión no es cosmética: los navegadores actuales acuerdan h2 por ALPN contra Cloudflare, así que una petición que dice ser un navegador y aun así llega por HTTP/1.1 es casi siempre un scraper con el user-agent falseado. Y había unos cuantos: un cubo de «Chrome de escritorio» salía con un 45,8 % de HTTP/1.1, que es algo que Chrome de escritorio no hace. Quitar HTTP/1.1 elimina las falsificaciones evidentes, pero no garantiza que el resto esté limpio: el plan Free no expone puntuación de bot, y la dimensión del navegador se deduce del propio user-agent, de modo que no queda ningún campo independiente por el que filtrar. Por eso tampoco hay aquí una tabla de adopción por navegador: no voy a publicar porcentajes por familia construidos sobre un denominador que no puedo auditar. La ventana son ocho consultas encadenadas de 24 horas, el rango más largo que devuelve ese plan, con lo que arrastra ocho costuras de dos minutos: unos 16 minutos ausentes en total.

Dos cosas sobre esa cifra. La primera es que es una instantánea de esa ventana, no una propiedad estable del sitio. La misma consulta sobre los siete días UTC completos del 26 de agosto al 1 de septiembre de 2026 da un **20,1 %** —10.508 de 52.341— y oscila de un día a otro:

**Cuota de HTTP/3 por día — host jmrp.io, del 26 de agosto al 1 de septiembre de 2026**

| Día (UTC) | HTTP/3 | HTTP/2 + HTTP/3 | Cuota de HTTP/3 |
| --- | --- | --- | --- |
| 2026-08-26 | 2.013 | 9.046 | 22,3 % |
| 2026-08-27 | 1.765 | 10.457 | 16,9 % |
| 2026-08-28 | 1.630 | 7.142 | 22,8 % |
| 2026-08-29 | 882 | 6.902 | 12,8 % |
| 2026-08-30 | 1.544 | 6.423 | 24,0 % |
| 2026-08-31 | 1.272 | 6.778 | 18,8 % |
| 2026-09-01 | 1.402 | 5.593 | 25,1 % |
| **Total** | **10.508** | **52.341** | **20,1 %** |

La segunda es que ninguna de las dos lecturas se puede recalcular después. El plan Free retiene `httpRequestsAdaptiveGroups` ocho días en ventana deslizante y rechaza cualquier consulta más antigua con `cannot request data older than 1w1d`, así que ambas cifras son observaciones fechadas que la propia fuente no volverá a servir. La tabla está aquí por eso, y publica los dos recuentos y no solo el porcentaje diario, porque los porcentajes por sí solos no bastarían: el día con más tráfico casi duplica al más tranquilo, así que las siete cuotas redondeadas no permiten reconstruir el 20,1 % ponderado —su media sin ponderar es del 20,4 %—. Con cada numerador y cada denominador en la página, el total es aritmética que cualquiera puede comprobar.

Un detalle medido que no esperaba. Mi origen sí envía la cabecera que esta guía te dice que añadas —`add_header Alt-Svc 'h3=":443"; ma=86400' always;` está en los dos server blocks TLS, y una petición directa al origen vuelve con ella—. A través de Cloudflare, esa cabecera sencillamente no está:

**Salida — Alt-Svc en el origen frente al edge**

```text
# En la propia máquina de origen, saltándose el CDN por completo:

$ curl -sS -kD- -o /dev/null -H 'Host: jmrp.io' https://127.0.0.1/
alt-svc: h3=":443"; ma=86400
$ curl -sS -D- -o /dev/null --resolve jmrp.io:443:104.21.40.251 https://jmrp.io/
server: cloudflare
(sin cabecera alt-svc en la respuesta)
$ dig +short HTTPS jmrp.io @1.1.1.1
1 . alpn="h3,h2" ipv4hint=104.21.40.251,172.67.158.146 ech=...
```

QUIC funciona igualmente, como ya demostraba el handshake de antes, porque el anuncio vive en otro sitio: el registro DNS HTTPS del dominio lleva `alpn="h3,h2"`, y los navegadores que resuelven ese registro se enteran de HTTP/3 antes de abrir una sola conexión. Esto es comportamiento de Cloudflare en toda su plataforma, no una particularidad de mi configuración — el edge termina la conexión y decide por su cuenta qué anuncia, y nada de mi configuración de Nginx puede cambiarlo. La lección práctica es estrecha pero vale: si tienes un CDN delante del origen que configuraste para HTTP/3, la cabecera que estás comprobando puede no ser la que ven tus visitantes. Mira también el registro DNS.

---

## ¿Por qué QUIC sustituyó a TCP en HTTP/3?

El protocolo QUIC tiene una evolución interesante desde un experimento propietario de Google hasta un estándar IETF completo.

**Cronología de QUIC**

| Año | Hito | Importancia |
| --- | --- | --- |
| **2012** | Google comienza el desarrollo de QUIC | Proyecto interno para reducir la latencia web |
| **2013** | Primer tráfico QUIC en Chrome | Primeros experimentos con servicios de Google |
| **2014** | Despliegue a gran escala de gQUIC | Chrome de escritorio usa QUIC para las propiedades de Google |
| **2016** | Se forma el Grupo de Trabajo QUIC del IETF | Comienza el proceso de estandarización formal |
| **2017** | IETF QUIC diverge de gQUIC | Integración de TLS 1.3, transporte de propósito general |
| **2021** | Se publica el RFC 9000 | Protocolo de transporte QUIC estandarizado |
| **2022** | Se publica el RFC 9114 | HTTP/3 estandarizado |

**Información — Google QUIC vs IETF QUIC**

El "gQUIC" (Google QUIC) original usaba criptografía propietaria. La versión IETF exige la integración de **TLS 1.3**, convirtiéndolo en un protocolo de transporte de propósito general adecuado para cualquier aplicación, no solo HTTP.

### La familia de RFCs de QUIC

La especificación completa de QUIC abarca múltiples RFCs:

**Documentos estándar de QUIC**

| RFC | Título | Descripción |
| --- | --- | --- |
| **[RFC 9000](https://datatracker.ietf.org/doc/html/rfc9000)** | QUIC Transport | Protocolo base: paquetes, frames, streams, gestión de conexiones |
| **[RFC 9001](https://datatracker.ietf.org/doc/html/rfc9001)** | Using TLS to Secure QUIC | Integración de TLS 1.3, derivación de claves, niveles de cifrado |
| **[RFC 9002](https://datatracker.ietf.org/doc/html/rfc9002)** | Loss Detection and Congestion Control | Recuperación de paquetes perdidos, estimación de RTT, algoritmos de congestión |
| **[RFC 8999](https://datatracker.ietf.org/doc/html/rfc8999)** | Version-Independent Properties | Comportamientos comunes a todas las versiones de QUIC |
| **[RFC 9114](https://datatracker.ietf.org/doc/html/rfc9114)** | HTTP/3 | Semántica HTTP sobre QUIC |

---

## ¿Por qué QUIC? Entendiendo las limitaciones de TCP

Para apreciar QUIC, debemos entender por qué TCP —la columna vertebral de internet desde 1974— tiene dificultades con las demandas modernas de la web.

### ¿Qué es el head-of-line (HOL) blocking?

Este es el problema fundamental que QUIC resuelve. Imagina una autopista con un solo carril (conexión TCP). Si un coche se avería (pérdida de paquete), **todos los que van detrás deben parar y esperar**, aunque vayan a destinos diferentes.

**Head-of-Line Blocking: TCP vs QUIC**

```mermaid
sequenceDiagram
    participant S as Servidor
    participant N as Red
    participant C as Cliente
    
    rect rgba(255, 0, 0, 0.1)
        note over S,C: **HTTP/2 sobre TCP: HOL Blocking**
        S->>N: Stream A: Paquete 1
        S->>N: Stream B: Paquete 1
        S->>N: Stream A: Paquete 2 (PERDIDO)
        S->>N: Stream C: Paquete 1
        N->>C: Stream A: Paquete 1
        N->>C: Stream B: Paquete 1 (¡BLOQUEADO!)
        N->>C: (Esperando Stream A: Paquete 2...)
        note right of C: TODOS los streams bloqueados<br/>esperando retransmisión
    end
    
    rect rgba(0, 255, 0, 0.1)
        note over S,C: **HTTP/3 sobre QUIC: Streams Independientes**
        S->>N: Stream A: Paquete 1
        S->>N: Stream B: Paquete 1
        S->>N: Stream A: Paquete 2 (PERDIDO)
        S->>N: Stream C: Paquete 1
        N->>C: Stream A: Paquete 1
        N->>C: Stream B: Paquete 1 (¡Entregado!)
        N->>C: Stream C: Paquete 1 (¡Entregado!)
        note right of C: Solo Stream A espera<br/>¡B y C continúan!
    end
```

**Idea clave — HOL Blocking en TCP**

**La idea clave**

HTTP/2 multiplexa varios streams sobre una **única conexión TCP**. Pero TCP ve todo como un único flujo ordenado de bytes — no conoce los streams de HTTP. Cuando se pierde el paquete 2 del Stream A, TCP bloquea **todos** los datos hasta que ese paquete sea retransmitido.

QUIC implementa streams **en la capa de transporte**, de modo que cada stream tiene entrega independiente. Los paquetes perdidos solo afectan a su propio stream.

### El problema de la latencia del handshake

Establecer una conexión TCP segura requiere **múltiples viajes de ida y vuelta**:

**Comparativa de establecimiento de conexión**

| Protocolo | Conexión nueva | Conexión reanudada | RTTs totales |
| --- | --- | --- | --- |
| **TCP + TLS 1.2** | TCP handshake + TLS handshake | TCP + TLS abreviado | 3 RTT → 2 RTT |
| **TCP + TLS 1.3** | TCP handshake + TLS 1.3 | TCP + TLS 0-RTT | 2 RTT → 1 RTT |
| **QUIC** | Handshake combinado | 0-RTT early data | 1 RTT → 0 RTT |

### El problema de la migración de conexión

Las conexiones TCP se identifican por una 4-tupla: `(IP origen, puerto origen, IP destino, puerto destino)`. Cuando cambias de Wi-Fi a datos móviles, tu IP cambia — y tu conexión TCP se rompe.

**Correcto — Connection IDs de QUIC**

**La solución de QUIC: Connection IDs**

QUIC utiliza **Connection IDs** criptográficamente seguros en lugar de direcciones IP. Cuando tu teléfono cambia de red, el Connection ID permanece igual. Tus descargas, videollamadas y sesiones de juego continúan sin interrupción.

---

## Arquitectura de QUIC en profundidad

### Transporte sobre UDP

QUIC se construye sobre **UDP** en lugar de crear un nuevo protocolo IP. Fue una elección pragmática:

- **No requiere cambios en el kernel**: UDP tiene soporte universal
- **Implementación en espacio de usuario**: Iteración y despliegue más rápidos
- **Traversal de middleboxes**: UDP atraviesa la mayoría de NATs y firewalls

**Advertencia — UDP vs TCP para QUIC**

**QUIC NO es "UDP con funciones extra"**

QUIC implementa su propia fiabilidad, ordenación y control de flujo. UDP es simplemente el mecanismo de entrega. Piensa en UDP como el "sobre" y en QUIC como el sistema postal completo dentro.

### Estructura de paquetes

QUIC utiliza dos tipos de paquetes:

**Tipos de paquetes QUIC**

| Tipo | Cabecera | Uso | Cifrado |
| --- | --- | --- | --- |
| **Long Header** | Información completa de conexión | Initial, Handshake, 0-RTT, Retry | Claves específicas por nivel |
| **Short Header** | Mínima (post-handshake) | Datos de aplicación (1-RTT) | Claves de aplicación |

### Streams y control de flujo

Los streams QUIC son canales ligeros y multiplexados dentro de una conexión:

**Lógica de procesamiento de streams QUIC**

```mermaid
flowchart TD
    Packet[Frame de Stream entrante]
    
    %% Control de flujo a nivel de conexión
    CheckConn{¿Límite MAX_DATA de conexión OK?}
    Packet --> CheckConn

    CheckConn -- No --> BlockConn[Conexión bloqueada]
    CheckConn -- Sí --> DecodeID[Decodificar Stream ID]

    %% Lógica de Stream ID
    DecodeID --> CheckInitiator{¿Último bit: Iniciador?}
    
    CheckInitiator -- "0 (Par)" --> Client[Iniciado por cliente]
    CheckInitiator -- "1 (Impar)" --> Server[Iniciado por servidor]

    Client --> CheckDirC{¿Penúltimo bit: Dirección?}
    Server --> CheckDirS{¿Penúltimo bit: Dirección?}

    CheckDirC -- "0 (Bidi)" --> Stream0[Tipo 0x0: Cliente Bidi]
    CheckDirC -- "1 (Uni)" --> Stream2[Tipo 0x2: Cliente Uni]

    CheckDirS -- "0 (Bidi)" --> Stream1[Tipo 0x1: Servidor Bidi]
    CheckDirS -- "1 (Uni)" --> Stream3[Tipo 0x3: Servidor Uni]

    %% Control de flujo a nivel de stream
    Stream0 --> CheckStreamFC{¿Límite MAX_STREAM_DATA OK?}
    Stream1 --> CheckStreamFC
    Stream2 --> CheckStreamFC
    Stream3 --> CheckStreamFC

    CheckStreamFC -- No --> BlockStream[Stream bloqueado]
    CheckStreamFC -- Sí --> Buffer[Buffer de recepción]
```

**Decodificación del Stream ID:**
El Stream ID es un entero de 62 bits donde los **2 bits menos significativos** funcionan como cabecera de tipo:

- **Bit 0 (Iniciador)**: `0` = Cliente, `1` = Servidor
- **Bit 1 (Dirección)**: `0` = Bidireccional, `1` = Unidireccional

Esto crea 4 espacios de direcciones distintos:

- **...00**: Petición/Respuesta del cliente
- **...01**: Server Push (obsoleto)
- **...10**: Stream de control del cliente
- **...11**: Stream de control del servidor (QPACK)

**Control de flujo de doble capa:**
Para que un paquete sea procesado, debe pasar **dos** comprobaciones independientes:

1. **Nivel de conexión**: ¿Hay crédito `MAX_DATA` para toda la conexión?
2. **Nivel de stream**: ¿Hay crédito `MAX_STREAM_DATA` para este stream específico?
Si *cualquiera* se agota, la transmisión se bloquea hasta que llegue un frame `WINDOW_UPDATE`.

---

## El handshake de QUIC: velocidad y seguridad

QUIC combina los handshakes de transporte y criptográficos en un único intercambio, reduciendo drásticamente la latencia.

### Handshake 1-RTT (primera conexión)

**Handshake 1-RTT de QUIC**

```mermaid
sequenceDiagram
    participant C as Cliente
    participant S as Servidor
    
    rect rgba(0, 100, 255, 0.05)
        note over C,S: **Handshake 1-RTT de QUIC**
        C->>S: Paquete Initial<br/>ClientHello + Parámetros de transporte QUIC
        Note right of C: Contiene SCID, DCID, versión
        S->>C: Paquetes Initial + Handshake<br/>ServerHello + Cert + Finished
        Note left of S: El servidor envía las claves de datos de aplicación
        C->>S: Handshake completo + Primera petición
        Note right of C: ¡Puede enviar petición HTTP inmediatamente!
        S->>C: Datos de respuesta
        note over C,S: Total: 1 viaje de ida y vuelta hasta los primeros datos
    end
```

### Handshake 0-RTT (conexión reanudada)

Para clientes que se han conectado previamente, QUIC permite enviar datos cifrados **en el primer paquete**:

**Reanudación 0-RTT de QUIC**

```mermaid
sequenceDiagram
    participant C as Cliente
    participant S as Servidor
    
    rect rgba(0, 255, 0, 0.1)
        note over C,S: **QUIC 0-RTT (Conexión reanudada)**
        C->>S: Paquetes Initial + 0-RTT<br/>ClientHello + Early Data (HTTP GET)
        Note right of C: ¡Datos enviados antes de completar el handshake!
        S->>C: Initial + Handshake + 1-RTT<br/>ServerHello + Cert + Datos de respuesta
        note over C,S: Total: 0 viajes de ida y vuelta para enviar la petición
    end
```

**Advertencia — Riesgos del Early Data**

**Consideraciones de seguridad del 0-RTT**

El early data (0-RTT) es vulnerable a **ataques de repetición** (replay attacks). Un atacante podría capturar y reenviar el paquete inicial. Mitigaciones:

- Usar 0-RTT solo para **peticiones idempotentes** (GET, HEAD)
- Los servidores deben rechazar datos 0-RTT no idempotentes
- Usar `ssl_early_data on;` en Nginx entendiendo los riesgos
- La cabecera `Early-Data` avisa a los backends sobre peticiones repetidas

---

## Arquitectura de seguridad

La seguridad de QUIC no es opcional —está integrada en el protocolo desde su diseño.

### Cuatro niveles de cifrado

**Niveles de cifrado de QUIC**

| Nivel | Claves derivadas de | Uso | Forward Secrecy |
| --- | --- | --- | --- |
| **Initial** | Destination Connection ID | Primeros paquetes, negociación de versión | No [mal] |
| **0-RTT** | Clave precompartida (PSK) | Datos de aplicación tempranos | No [mal] |
| **Handshake** | Secretos del handshake TLS | Completar el handshake | Parcial [cuidado] |
| **Application (1-RTT)** | Secretos de tráfico TLS | Todos los datos post-handshake | Completo (ECDHE) [bien] |

### Protección de paquetes

Cada paquete QUIC (excepto Initial) está protegido con **cifrado AEAD**:

**Estructura y protección de paquetes QUIC**

| Sección del paquete | Componente | Nivel de protección |
| --- | --- | --- |
| **Cabecera** | Flags | Protegido (Header Protection) [cuidado] |
| **Cabecera** | Connection ID | Texto claro (para enrutamiento) [mal] |
| **Cabecera** | Número de paquete | Cifrado (Header Protection) [bien] |
| **Carga útil** | Datos de aplicación | Totalmente cifrado (AEAD) [bien] |
| **Pie** | Etiqueta de autenticación | MAC de integridad de 16 bytes [bien] |

**Información — Mecanismos de cifrado**

**Detalles de la protección:**

- **Derivación de claves**: HKDF a partir de los secretos de tráfico de TLS 1.3.
- **Nonce**: IV con XOR del número de paquete para prevenir la reutilización de nonces en la conexión.
- **Header Protection**: Cifrado independiente de flags y números de paquete para enmascarar patrones de tráfico de red.

### Connection ID y privacidad

**Idea clave**

**Diseño anti-rastreo**

Los Connection IDs de QUIC pueden **rotarse** durante una conexión. El servidor proporciona nuevos Connection IDs mediante frames `NEW_CONNECTION_ID`. Esto impide que los observadores de red rastreen conexiones a través de cambios de ruta.

Combinado con números de paquete y cabeceras cifradas, QUIC mejora significativamente la privacidad en comparación con TCP.

### Mecanismos de protección contra DoS

QUIC incluye varias medidas anti-amplificación y anti-suplantación:

1. **Validación de dirección**: Los servidores envían paquetes `RETRY` para validar las direcciones de los clientes
2. **Anti-amplificación**: Los servidores limitan los datos enviados antes de la validación de dirección (3x los datos del cliente)
3. **Stateless Reset**: Terminación limpia de conexión sin estado

---

## HTTP/3: HTTP sobre QUIC

HTTP/3 es el mapeo de la semántica HTTP sobre el transporte QUIC. Reemplaza el framing binario de HTTP/2 con streams QUIC.

**HTTP/2 vs HTTP/3**

| Característica | HTTP/2 | HTTP/3 |
| --- | --- | --- |
| **Transporte** | TCP + TLS | QUIC (UDP + TLS 1.3) |
| **Multiplexing** | Frames de stream en una única conexión | Streams nativos de QUIC |
| **HOL Blocking** | Sí (en la capa TCP) | No (streams independientes) |
| **Compresión de cabeceras** | HPACK | QPACK (maneja el desorden) |
| **Server Push** | Soportado | Obsoleto (poco usado) |
| **Migración de conexión** | No [mal] | Sí [bien] |

### ¿Quién usa HTTP/3 hoy?

A fecha de 2026, la adopción de HTTP/3 es significativa y creciente:

**Estadísticas de adopción de HTTP/3**

| Métrica | Valor | Fuente |
| --- | --- | --- |
| **Sitios web usando HTTP/3** | 36,6% | [W3Techs (2026)](https://w3techs.com/technologies/details/ce-http3) |
| **Uso de QUIC en Chrome (subsiguiente)** | 40% | [APNIC Labs](https://blog.apnic.net/2023/10/09/measuring-http-3-real-world-performance/) |
| **Mejora en carga de página (global)** | 12,4% | [Cloudflare Benchmarks](https://blog.cloudflare.com/http-3-vs-http-2/) |
| **Mejora en regiones de alta latencia** | 13,8% | [DebugBear](https://www.debugbear.com/blog/http3-vs-http2-performance) |

---

## Configuración de Nginx: guía completa

Ahora vamos a implementar HTTP/3 en Nginx. Esta sección cubre todo, desde los prerrequisitos hasta el despliegue en producción.

### Prerrequisitos

**Advertencia — Requisitos de HTTP/3**

**Requisitos de versión**

- **Nginx**: Versión 1.25.0+ (soporte HTTP/3 añadido a mediados de 2023)
- **Biblioteca SSL**: Una de las siguientes con soporte QUIC:
  - OpenSSL 3.5.1+ (recomendado para 0-RTT)
  - BoringSSL
  - QuicTLS (fork de OpenSSL, archivado en 2025)
  - LibreSSL 3.6+

Verifica tu instalación de Nginx:

**Comando para verificar la versión y módulos de Nginx**

```bash
nginx -V 2>&1 | grep -E 'version|http_v3_module|OpenSSL|BoringSSL'
```

**Salida de la versión de Nginx**

```text
nginx version: nginx/1.28.0
built with OpenSSL 3.5.0 8 Apr 2025 (running with OpenSSL 3.5.4 30 Sep 2025)
configure arguments: ... --with-http_v3_module ...
```

**Advertencia — El repositorio Nginx de Ondřej Surý ya no existe**

Las guías escritas antes de 2026 —incluidas versiones anteriores de esta— te
mandan a `https://packages.sury.org/nginx/README.txt` en Debian y a
`ppa:ondrej/nginx-mainline` en Ubuntu. Los dos canales se han retirado: a 27 de
agosto de 2026 la ruta `/nginx/` de ese host devuelve **404** mientras sus
repositorios de PHP y Apache siguen respondiendo, y los dos PPA de Launchpad han
desaparecido. Usa el repositorio oficial de Nginx.

**Opción 1/2 — Repositorio oficial de Nginx (Recomendado)**

Los paquetes que nginx.org publica para Debian y Ubuntu vienen compilados
`--with-http_v3_module` contra OpenSSL 3.5, así que HTTP/3 no exige compilar
nada a mano. Los mismos comandos valen para las dos distribuciones:
`/etc/os-release` aporta el fabricante y el nombre en clave.

```bash
sudo apt install curl gnupg2 ca-certificates
DISTRO=$(. /etc/os-release && echo "$ID")
CODENAME=$(. /etc/os-release && echo "$VERSION_CODENAME")

# Clave de firma de Nginx en su propio llavero
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://nginx.org/keys/nginx_signing.key -o /tmp/nginx.key

# Contrasta las huellas con la lista que publica nginx en
# https://nginx.org/en/pgp_keys.html ANTES de confiar en la clave. El
# paquete lleva varias claves y rotan, así que compara la salida y no un
# número copiado de un artículo.
gpg --show-keys --with-fingerprint /tmp/nginx.key

sudo gpg --dearmor -o /etc/apt/keyrings/nginx.gpg /tmp/nginx.key

# Repositorio mainline, restringido a ese llavero
echo "deb [signed-by=/etc/apt/keyrings/nginx.gpg] https://nginx.org/packages/mainline/$DISTRO $CODENAME nginx" \
    | sudo tee /etc/apt/sources.list.d/nginx.list

sudo apt update && sudo apt install nginx

# Comprobar la compilación que acabas de instalar
nginx -V 2>&1 | tr ' ' '\n' | grep -E 'http_v3|OpenSSL'
```

**Opción 2/2 — Compilar desde fuente**

Solo hace falta si quieres un módulo que los paquetes no traen. Nginx compila
HTTP/3 contra cualquier biblioteca SSL que exponga una API QUIC: OpenSSL 3.5+,
BoringSSL o LibreSSL 3.6+. En una distribución actual basta con el OpenSSL del
sistema — el propio paquete oficial de Debian 13 está compilado contra OpenSSL
3.5.6.

```bash
openssl version   # 3.5.0 o superior

wget https://nginx.org/download/nginx-1.31.4.tar.gz
tar xzf nginx-1.31.4.tar.gz
cd nginx-1.31.4

./configure \
    --with-http_v3_module \
    --with-http_ssl_module \
    --with-http_v2_module

make -j"$(nproc)"
sudo make install
```

### Configuración básica

Esta es la configuración mínima para habilitar HTTP/3:

**Fichero: `/etc/nginx/sites-available/example.com.conf`**

```nginx
server {
    server_name example.com;
    root /var/www/example.com;

    # ==========================================
    # LISTENERS: TCP (HTTP/1.1, HTTP/2) + UDP (HTTP/3)
    # ==========================================
    
    # Standard HTTPS over TCP
    listen 443 ssl;
    listen [::]:443 ssl;
    http2 on;
    
    # QUIC/HTTP/3 over UDP
    # 'reuseport' is critical for multi-worker performance
    listen 443 quic reuseport;
    listen [::]:443 quic reuseport;

    # ==========================================
    # SSL/TLS CONFIGURATION
    # ==========================================
    
    # TLS 1.3 is REQUIRED for QUIC
    ssl_protocols TLSv1.3 TLSv1.2;
    ssl_prefer_server_ciphers off;
    
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    # ==========================================
    # HTTP/3 CONFIGURATION
    # ==========================================
    
    http3 on;
    
    # Advertise HTTP/3 support to browsers
    # ma=86400 means "cache this info for 24 hours"
    add_header Alt-Svc 'h3=":443"; ma=86400' always;

    location / {
        try_files $uri $uri/ =404;
    }
}
```

**Información — Por qué reuseport es crítico**

**¿Por qué `reuseport`?**

UDP no tiene conexión — sin `reuseport`, todos los paquetes QUIC serían manejados por un único worker de Nginx, creando un cuello de botella. La función `reuseport` del kernel distribuye los paquetes entrantes entre todos los workers.

**⚠️ Importante:** El flag `reuseport` debe aparecer en **exactamente una** directiva `listen` por dirección:puerto a nivel global. Si tienes múltiples server blocks escuchando en el puerto 443 QUIC, solo el primero debe tener `reuseport`. Usarlo varias veces hará que Nginx falle al iniciar con errores de "address already in use".

### Configuración completa de producción

Aquí tienes una configuración exhaustiva con todas las optimizaciones. La configuración se divide en dos archivos: el `nginx.conf` principal para ajustes globales y un archivo de configuración específico del sitio.

**Fichero: `/etc/nginx/nginx.conf` — Configuración principal de Nginx**

```nginx
# Main context
worker_processes auto;
error_log /var/log/nginx/error.log warn;

events {
    worker_connections 4096;
    use epoll;
    multi_accept on;
}

http {
    # ==========================================
    # HTTP/3 GLOBAL SETTINGS
    # ==========================================
    # Explicit for clarity (http3 defaults to 'on' in nginx 1.25.0+)
    http3 on;
    
    # Custom log format to track HTTP/3 connections
    log_format quic '$remote_addr - $remote_user [$time_local] '
                    '"$request" $status $body_bytes_sent '
                    '"$http_referer" "$http_user_agent" '
                    'proto="$server_protocol" quic="$http3"';

    # MIME types
    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    # Performance optimizations
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    
    # Gzip compression
    gzip on;
    gzip_vary on;
    gzip_min_length 1024;
    gzip_types text/plain text/css application/json application/javascript 
               text/xml application/xml application/xml+rss text/javascript;

    # Include site configurations
    include /etc/nginx/sites-enabled/*;
}
```

La configuración específica del sitio incluye todas las directivas para QUIC, TLS, cabeceras y logging:

**Fichero: `/etc/nginx/sites-available/example.com.conf` — Configuración específica del sitio Nginx**

```nginx
server {
    server_name example.com www.example.com;
    root /var/www/example.com;

    # ==========================================
    # DUAL-STACK LISTENERS
    # ==========================================
    
    # TCP: HTTP/1.1 and HTTP/2 fallback
    listen 443 ssl;
    listen [::]:443 ssl;
    http2 on;
    
    # UDP: QUIC/HTTP/3
    listen 443 quic reuseport;
    listen [::]:443 quic reuseport;

    # ==========================================
    # TLS CONFIGURATION (Required for QUIC)
    # ==========================================
    
    ssl_protocols TLSv1.3 TLSv1.2;
    # TLS 1.3 ciphersuites (handled separately for better OpenSSL compatibility)
    ssl_conf_command Ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256;
    # TLS 1.2 ciphers only (ECDHE for forward secrecy)
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305;
    ssl_prefer_server_ciphers off;
    ssl_ecdh_curve X25519:P-256:P-384;
    
    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    
    # Session resumption for performance
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 1d;
    ssl_session_tickets off;  # Better security
    
    # OCSP Stapling
    ssl_stapling on;
    ssl_stapling_verify on;

    # ==========================================
    # QUIC-SPECIFIC SETTINGS
    # ==========================================
    
    # Enable 0-RTT early data (with security considerations)
    ssl_early_data on;
    
    # DoS protection: require address validation
    quic_retry on;
    
    # Performance: Generic Segmentation Offload (Linux 4.18+)
    quic_gso on;

    # ==========================================
    # HEADERS
    # ==========================================
    
    # Advertise HTTP/3 availability
    add_header Alt-Svc 'h3=":443"; ma=86400' always;
    
    # Warn backends about 0-RTT replay risk
    add_header Early-Data $ssl_early_data always;
    
    # Security headers
    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-Frame-Options "DENY" always;

    # ==========================================
    # LOGGING
    # ==========================================
    
    access_log /var/log/nginx/example.com.access.log quic;
    error_log /var/log/nginx/example.com.error.log;

    # ==========================================
    # LOCATIONS
    # ==========================================
    
    location / {
        try_files $uri $uri/ =404;
    }
    
    # Static assets with long cache
    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2)$ {
        expires 1y;
        add_header Cache-Control "public, immutable" always;
        add_header Alt-Svc 'h3=":443"; ma=86400' always;
        
        # Security headers (inherited from server block may not apply with add_header in location)
        add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
        add_header X-Content-Type-Options "nosniff" always;
        add_header X-Frame-Options "DENY" always;
    }
}

# HTTP to HTTPS redirect
server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;
    return 301 https://$host$request_uri;
}
```

### Referencia de todas las directivas QUIC

**Directivas QUIC de Nginx**

| Directiva | Por defecto | Contexto | Descripción |
| --- | --- | --- | --- |
| [`http3`](https://nginx.org/en/docs/http/ngx_http_v3_module.html#http3) | `on` | http, server | Habilita la negociación del protocolo HTTP/3 |
| [`http3_hq`](https://nginx.org/en/docs/http/ngx_http_v3_module.html#http3_hq) | `off` | http, server | Habilita HTTP/0.9 sobre QUIC (solo para pruebas) |
| [`http3_max_concurrent_streams`](https://nginx.org/en/docs/http/ngx_http_v3_module.html#http3_max_concurrent_streams) | `128` | http, server | Máximo de streams concurrentes por conexión |
| [`http3_stream_buffer_size`](https://nginx.org/en/docs/http/ngx_http_v3_module.html#http3_stream_buffer_size) | `64k` | http, server | Tamaño del buffer para lectura/escritura de streams |
| [`quic_active_connection_id_limit`](https://nginx.org/en/docs/http/ngx_http_v3_module.html#quic_active_connection_id_limit) | `2` | http, server | Máximo de Connection IDs almacenados por conexión |
| [`quic_bpf`](https://nginx.org/en/docs/http/ngx_http_v3_module.html#quic_bpf) | `off` | main | Enrutamiento eBPF para migración de conexión (Linux 5.7+) |
| [`quic_gso`](https://nginx.org/en/docs/http/ngx_http_v3_module.html#quic_gso) | `off` | http, server | Generic Segmentation Offload (Linux 4.18+) |
| [`quic_host_key`](https://nginx.org/en/docs/http/ngx_http_v3_module.html#quic_host_key) | - | http, server | Archivo con clave secreta para tokens de validación de dirección |
| [`quic_retry`](https://nginx.org/en/docs/http/ngx_http_v3_module.html#quic_retry) | `off` | http, server | Habilita la validación de dirección mediante paquetes Retry |

---

## Configuración del firewall

**Crítico**: HTTP/3 usa **UDP** en el puerto 443, no TCP. Si tu firewall solo permite TCP/443, QUIC fallará silenciosamente y los clientes retrocedarán a HTTP/2.

**Opción 1/4 — UFW (Ubuntu/Debian)**

```bash
# Check current rules
sudo ufw status

# Allow UDP on port 443
sudo ufw allow 443/udp comment 'QUIC/HTTP3'

# Verify
sudo ufw status verbose
```

**Opción 2/4 — iptables**

```bash
# Allow incoming UDP 443
sudo iptables -A INPUT -p udp --dport 443 -j ACCEPT

# Save rules (Debian/Ubuntu)
sudo iptables-save | sudo tee /etc/iptables/rules.v4

# For IPv6
sudo ip6tables -A INPUT -p udp --dport 443 -j ACCEPT
sudo ip6tables-save | sudo tee /etc/iptables/rules.v6
```

**Opción 3/4 — firewalld (RHEL/CentOS)**

```bash
# Add UDP 443 to default zone
sudo firewall-cmd --permanent --add-port=443/udp

# Reload
sudo firewall-cmd --reload

# Verify
sudo firewall-cmd --list-ports
```

**Opción 4/4 — AWS Security Groups**

Añade una regla de entrada:

- **Tipo**: Custom UDP
- **Rango de puertos**: 443
- **Origen**: 0.0.0.0/0 (o tu CIDR)
- **Descripción**: QUIC/HTTP3

### Ajuste del kernel para alto tráfico

Para servidores con alto tráfico, aumenta los tamaños de buffer UDP:

**Fichero: `/etc/sysctl.d/99-quic.conf`**

```bash
# Increase UDP buffer sizes for QUIC
net.core.rmem_max = 16777216
net.core.wmem_max = 16777216
net.core.rmem_default = 1048576
net.core.wmem_default = 1048576

# UDP memory limits
net.ipv4.udp_mem = 65536 131072 262144
net.ipv4.udp_rmem_min = 8192
net.ipv4.udp_wmem_min = 8192

# Allow more local ports for connections
net.ipv4.ip_local_port_range = 1024 65535
```

Aplica los ajustes:

**Comando para aplicar los ajustes de kernel para UDP**

```bash
sudo sysctl --system
```

---

## Verificación y pruebas

Tras la configuración, verifica que HTTP/3 funciona correctamente.

### Método 1: curl

Las versiones modernas de curl (7.66+ con soporte HTTP/3) permiten probar directamente:

**Comandos curl para probar la conexión HTTP/3**

```bash
# Test HTTP/3 specifically
curl -I --http3-only https://jmrp.io

# Or allow fallback
curl -I --http3 https://jmrp.io
```

**Salida de la respuesta HTTP/3 con curl**

```text
HTTP/3 200 
server: jmrp.io
date: Wed, 14 Jan 2026 20:02:32 GMT
content-type: text/html; charset=utf-8
alt-svc: h3=":443"; ma=86400
strict-transport-security: max-age=63072000; includeSubDomains; preload
```

**Consejo — Instalar curl con soporte HTTP/3**

**Instalar curl con soporte HTTP/3**

En macOS: `brew install curl`
En Ubuntu: Compilar desde fuente con `--with-ngtcp2` o usar el paquete snap

### Método 2: DevTools del navegador

1. Abre Chrome o Firefox
2. Navega a tu sitio
3. Abre DevTools (F12) → pestaña **Red** (Network)
4. Haz clic derecho en las cabeceras de columna → Activa la columna **Protocolo**
5. Recarga la página
6. Busca `h3` en la columna Protocolo

**Información — Comportamiento en la primera visita**

**Comportamiento en la primera visita**

En la primera visita, los navegadores usan HTTP/2 para descubrir la cabecera `Alt-Svc`. En peticiones posteriores (o tras recargar), cambian a HTTP/3. Este es un comportamiento normal.

### Método 3: herramientas online

**Herramientas de prueba HTTP/3**

| Herramienta | URL | Características |
| --- | --- | --- |
| **HTTP/3 Check** | [http3check.net](https://http3check.net/) | Test rápido de aprobado/suspenso con detalles |
| **Cloudflare HTTP/3 Test** | [cloudflare-quic.com](https://cloudflare-quic.com/) | Prueba de conexión en vivo |
| **Qualys SSL Labs** | [ssllabs.com](https://www.ssllabs.com/ssltest/) | Análisis exhaustivo de TLS |

### Método 4: comprobar los logs de Nginx

Usa el formato de log personalizado para verificar el tráfico HTTP/3:

**Comando grep para comprobar los logs de acceso de Nginx**

```bash
# Check for HTTP/3 connections
grep 'quic="h3"' /var/log/nginx/access.log | tail -5
```

**Salida del log mostrando tráfico HTTP/3**

```text
192.168.1.100 - - [14/Jan/2026:20:02:32 +0000] "GET / HTTP/3" 200 15234 "-" "Mozilla/5.0..." proto="HTTP/3" quic="h3"
```

---

## Guía de resolución de problemas

**Problemas comunes de HTTP/3 y soluciones**

| Síntoma | Causa probable | Solución |
| --- | --- | --- |
| **El protocolo se queda en HTTP/2** | Firewall bloqueando UDP/443 | Abre el puerto UDP 443 en el servidor y en el proveedor cloud |
| **El protocolo se queda en HTTP/2** | Falta la cabecera Alt-Svc | Añade `add_header Alt-Svc 'h3=":443"; ma=86400' always;` |
| **Errores de conexión** | TLS 1.3 no habilitado | Habilita TLSv1.3 para QUIC (obligatorio); TLSv1.2 puede seguir habilitado como fallback para HTTP/2 |
| **Los navegadores se niegan a usar QUIC** | Certificado autofirmado | Usa un certificado válido (Let's Encrypt) |
| **Nginx no arranca** | Falta `--with-http_v3_module` | Recompila Nginx con el módulo HTTP/3 |
| **Uso alto de CPU** | GSO no soportado por el kernel | Desactiva `quic_gso` o actualiza el kernel |
| **0-RTT no funciona** | Versión de OpenSSL demasiado antigua | Usa OpenSSL 3.5.1+, QuicTLS o BoringSSL |

### Modo depuración

Activa el logging de depuración temporalmente:

**Activar el logging de depuración de Nginx**

```nginx
error_log /var/log/nginx/error.log debug;
```

Comprueba los mensajes específicos de QUIC:

```bash
grep -i quic /var/log/nginx/error.log | tail -20
```

**Salida — Log de depuración de Nginx (fallo simulado)**

```text
2026/01/14 10:42:15 [debug] 12345#0: *1 quic handle packet: fd:16, addr:192.0.2.1:54321
2026/01/14 10:42:15 [debug] 12345#0: *1 quic packet rx dcid len:8 87654321
2026/01/14 10:42:15 [debug] 12345#0: *1 quic packet rx scid len:8 12345678
2026/01/14 10:42:15 [info] 12345#0: *1 quic SSL_do_handshake() failed: SSL_ERROR_SSL: error:14094416:SSL routines:ssl3_read_bytes:sslv3 alert certificate unknown
2026/01/14 10:42:15 [debug] 12345#0: *1 quic close connection: 0:
```

---

## Lista de verificación para producción

Antes de desplegar HTTP/3 en producción, verifica:

**Lista de verificación para HTTP/3 en producción**

| Categoría | Elemento | Verificación |
| --- | --- | --- |
| **Build** | Nginx compilado con `--with-http_v3_module` | `nginx -V 2>&1 \| grep http_v3` |
| **Build** | Biblioteca SSL compatible (QuicTLS/BoringSSL/OpenSSL 3.5.1+) | `nginx -V 2>&1 \| grep -i ssl` |
| **Red** | UDP/443 abierto en el firewall del servidor | `sudo ss -ulnp \| grep 443` |
| **Red** | UDP/443 abierto en el proveedor cloud (AWS/GCP/Azure) | Comprueba las reglas del security group/firewall |
| **Config** | `listen 443 quic reuseport;` presente | `nginx -T \| grep quic` |
| **Config** | TLS 1.3 habilitado | `nginx -T \| grep ssl_protocols` |
| **Config** | Cabecera Alt-Svc configurada | `curl -I https://example.com \| grep alt-svc` |
| **Certificado** | Certificado válido (no autofirmado) | `openssl s_client -connect site:443` |
| **Prueba** | HTTP/3 confirmado y funcionando | `curl --http3 https://example.com` |
| **Monitorización** | Formato de log incluye `$http3` | Comprueba la configuración del formato de log |

---

## ¿Cuándo NO deberías usar QUIC?

Aunque QUIC ofrece beneficios significativos, hay escenarios donde TCP puede ser preferible:

**Advertencia — Consideraciones sobre el fallback de protocolo**

**Considera mantener TCP/HTTP/2 cuando:**

- **Los firewalls corporativos bloquean UDP**: Muchas redes empresariales bloquean o limitan el tráfico UDP
- **La CPU del servidor es limitada**: QUIC tiene mayor consumo de CPU por el cifrado
- **Tus usuarios están en redes estables**: Entornos con baja pérdida de paquetes ven menos beneficio
- **Infraestructura de proxy heredada**: Los load balancers pueden no soportar QUIC passthrough
- **La depuración es crítica**: TCP tiene herramientas de monitorización más maduras

