4dim / Notas

HTTP/2 en nginx: el TTFB de 1,4 a 0,34 segundos

El servidor renderizaba la página en 12 a 25 milisegundos; el resto, hasta 1,4 segundos, era el viaje. Una palabra en nginx, dos fuentes adelantadas con el atributo que casi nadie pone, y dos trampas de configuración que costaron una tarde.

El servidor no era el problema

Si tu web tarda en aparecer, esta nota cuenta dónde suele estar el tiempo perdido. Casi nunca está en el programa: está en el viaje. Y lo primero es medir, para no equivocarse de culpable.

Nuestra página de acceso tardaba más de un segundo en empezar a pintarse, y la primera sospecha fue la aplicación. Medimos. El servidor armaba esa página en 12 a 25 milisegundos, y 40 pasando por nginx en la misma máquina.

Todo lo demás, hasta 1,4 segundos, era el viaje: la forma en que el navegador iba descargando las piezas que la página necesita.

Es la lección que se repite en casi todos los sitios pequeños que hemos mirado. El código no es lento; la entrega lo es. Y la entrega se arregla en la configuración del servidor web, no en la aplicación.

Diez recursos, seis conexiones, seis apretones de manos

El sitio se servía con HTTP/1.1, la versión antigua del idioma con el que hablan navegador y servidor. Esa versión no sabe mezclar varias descargas por un mismo canal. Así que el navegador abre varias conexiones a la vez, seis en la práctica, y reparte los recursos entre ellas.

Cada conexión paga su propio saludo de apertura y su propio saludo de cifrado antes de pedir nada. Diez recursos, seis conexiones, seis veces el tiempo de ida y vuelta multiplicado por los pasos del cifrado. Con un servidor al otro lado del Atlántico, eso son cientos de milisegundos que no hacen nada.

HTTP/2 usa una sola conexión y mete todos los recursos por ella, en paralelo. Un saludo de apertura, un saludo de cifrado, y luego todo.

La medición

QuéHTTP/1.1HTTP/2
Tiempo hasta el primer byte de /login1,0 a 1,4 s0,34 a 0,58 s
Cada recurso individualAproximadamente la mitad
Renderizado en el servidor12 a 25 ms12 a 25 ms

El cambio en nginx es una palabra: listen 443 ssl http2;. El resto de la mejora salió de mirar qué pedía el navegador y en qué orden.

Medir antes de optimizar no es un consejo de manual. Es lo que evita pasar una semana acelerando un servidor que ya respondía en menos de 25 milisegundos.

Adelantar las fuentes, y la palabra que falta

La cadena de descarga era esta: primero el HTML, luego la hoja de estilos de 12 kB, y solo al terminar de leerla el navegador descubría que existen fuentes de letra y las pedía. Tres viajes en fila, uno detrás de otro.

Con <link rel="preload"> el HTML anuncia las fuentes desde el principio y se descargan a la vez que el CSS. Adelantamos solo dos, las que se leen arriba del todo. Adelantarlas todas las pondría a competir por el ancho de banda del primer segundo.

Queda el detalle que casi nadie sabe. El atributo crossoriginen ese <link> no es opcional, aunque la fuente esté en tu propio dominio. Las fuentes se piden siempre en modo CORS, el modo pensado para traer recursos de otros dominios.

Un preload sin crossorigin no coincide con la petición real, y el navegador termina descargando la fuente dos veces. Lo avisa en la consola, en amarillo, y nadie lo lee.

Dos trampas de nginx que costaron una tarde

  • El archivo que no era un enlace. Por convención, sites-enabled/ guarda enlaces que apuntan a los archivos de sites-available/. El nuestro era una copia. Editábamos el de sites-available, corríamos nginx -t para validar, salía verde y no cambiaba nada. nginx -t estaba revisando el otro archivo, el que sí estaba activo. Hoy el script de instalación comprueba que sea un enlace de verdad.
  • La directiva duplicada. Certbot, el programa que gestiona los certificados, ya declara la reanudación de sesión cifrada en su archivo de opciones. Declararla otra vez a mano, en el archivo del sitio, la rompía por duplicada. Lo que gestiona Certbot, se le deja a Certbot.

Qué hacer entonces

  • Medir desde fuera el tiempo hasta el primer byte (con curl -w o la pestaña de red) y compararlo con lo que tarda el servidor en armar la página. La diferencia es el viaje.
  • Comprobar que el sitio sirve HTTP/2. Si nginx dice listen 443 ssl; sin http2, ahí está el segundo perdido.
  • Adelantar las dos o tres fuentes que se leen primero, con crossorigin.
  • Verificar que sites-enabled tiene enlaces, no copias.
  • No repetir a mano lo que Certbot ya configura.

Esta nota sale de nuestro trabajo en Desarrollo web.

← todas las notas