Carga interminable detrás de Cloudflare

Mudamos nuestro servidor de un proveedor de VPS a otro y también actualicé la instancia a través de launcher rebuild a la última versión, de 3.5.0.beta3 a 3.5.0.beta4.

La instancia siempre funcionó bien detrás de Cloudflare, pero ahora intentar acceder a ella lleva a una animación de carga infinita de 5 puntos.

Tengo una entrada en el archivo hosts en mi sistema local para omitir Cloudflare, ya que mi ISP (Deutsche Telekom AG) tiene políticas de peering deficientes, por lo que el acceso a través de Cloudflare es muy bajo a veces. Así que al principio no reconocí el problema, ya que el acceso sin Cloudflare funciona bien. Así que actualicé la instancia y, por lo tanto, ahora no estoy seguro de si el VPS cambiado o la actualización de Discourse fue el cambio relevante. Me aseguré a través de VPN y red móvil de que el problema es realmente Cloudflare en sí mismo ahora, no el mal peering de mi ISP, y otros usuarios también enfrentan el mismo problema. El VPS antiguo y el nuevo tienen IPv6 disponible, y todo el sistema es exactamente el mismo, transferido como un archivo de imagen raw.

No hay ningún mensaje de error, ni en el navegador (consola), ni por el proxy del sistema host, ni por Nginx dentro del contenedor, ni por Rails ni en ningún otro lugar. Los documentos HTML y varios scripts se cargan bien, y al compararlos con los que se sirven al omitir Cloudflare, se ve que todo (lo que comprobé) es idéntico. Los encabezados de respuesta también se ven en su mayoría iguales, aparte de algunos específicos de Cloudflare, por supuesto. Lo último que veo cargándose es el mini profiler:

Por supuesto, borrar la caché del navegador, usar ventanas privadas, etc., no cambiaron nada. Tampoco ayuda borrar/desactivar la caché de Cloudflare, por lo que la caché no es el problema. Desactivé temporalmente la caché de CF por completo para todo el foro.

Cabe destacar que el foro se ejecuta en una subruta detrás de un proxy Apache en el host, siguiendo estas instrucciones: Serve Discourse from a subfolder (path prefix) instead of a subdomain
Anteriormente, creamos solo un enlace simbólico ln -s . forum en lugar de los enlaces simbólicos de uploads/backups y duplicamos las reescrituras de las instrucciones, lo que funcionó bien durante años (y también ahora sin Cloudflare), pero como parte de mis esfuerzos de depuración, cambié a esas instrucciones para asegurar que el proxy interno aplique todas las reglas según lo previsto. El encabezado de confianza es CF-Connecting-IP, aunque también habilité cloudflare.template.yml, incluso si duplica un poco las cosas. Y también intenté cambiar de un lado a otro varias partes de estas plantillas y las instrucciones anteriores, también en un intento de verificar si los encabezados IP del proxy marcan alguna diferencia, ya que la falta de CF-Connecting-IP es una cosa al omitir Cloudflare.

En este punto, estoy completamente sin ideas, no tengo ni una sola pista de dónde podría venir el problema, ni un solo registro/salida relacionado en ninguna parte. A través de Cloudflare, Discourse simplemente se queda colgado en la animación de carga sin más rastro.

Espero que alguien tenga una idea de cómo depurar esto, o si hubo algún cambio entre 3.5.0.beta3 y 3.5.0.beta4 que pudiera estar relacionado. ¿Supongo que una reversión es problemática?

Esta es la instancia: https://dietpi.com/forum/
EDIT: He deshabilitado Cloudflare por ahora. Pero hay un CNAME que todavía se pasa a través de Cloudflare, por lo que estos dos se pueden comparar: https://www.dietpi.com/forum/

Problema interesante.

Simplemente https://www.dietpi.com/forum/ se está colgando para siempre.

$ wget https://www.dietpi.com/forum/
--2025-05-03 10:52:18--  https://www.dietpi.com/forum/
Resolving www.dietpi.com (www.dietpi.com)... 104.21.12.65, 172.67.193.183, 2606:4700:3035::6815:c41, ...
Connecting to www.dietpi.com (www.dietpi.com)|104.21.12.65|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: unspecified [text/html]
Saving to: âindex.html.1â

    [<=>                                

Lo interesante es que las llamadas como https://www.dietpi.com/forum/site.json sí tienen éxito.

https://www.dietpi.com/forum/t/why-there-are-two-kernals-in-my-raspberry-pi4/23355 no funciona y se cuelga para siempre, pero
https://www.dietpi.com/forum/t/why-there-are-two-kernals-in-my-raspberry-pi4/23355.json sí lo hace.

1 me gusta

Interesante, en efecto. Me doy cuenta ahora de que los documentos HTML no se cargan completamente, sino que se cuelgan en algún punto. Comparé /forum/ en ambos casos y pensé que eran idénticos, pero probablemente me estaba centrando demasiado en la cabecera, mientras que partes del cuerpo en la parte inferior faltan.

Esa última línea al cargar a través de Cloudflare:

      <discourse-assets-json>
        <div class="hidden" id="data-preloaded" data-preloaded="{&quot;topic_list&quot;:&quot;{&quot;users&quot;...&quot;:false,&quot;allowed_iframes&quot;:&quot;https://dietpi.com/forum/discobot/certificate.svg

Tuve que truncarla ya que excede con creces el límite de caracteres para una publicación. El documento normalmente continúa así:

      <discourse-assets-json>
        <div class="hidden" id="data-preloaded" data-preloaded="{&quot;topic_list&quot;:&quot;{&quot;users&quot;...&quot;:false,&quot;allowed_iframes&quot;:&quot;https://dietpi.com/forum/discobot/certificate.svg&quot;,&quot;can_permanently_delete...

Las otras páginas se cuelgan en el mismo punto. Creo que estamos cerca de algo.

EDITAR: Ah, espera, comprobé mal, otras páginas se cuelgan en otro lugar. Así que no es este elemento/atributo HTML en particular.

Sí, cada página/documento HTML se cuelga en el mismo carácter cuando se carga a través del navegador una y otra vez, en ventana privada, etc. Pero una página diferente se cuelga en un punto diferente. Y al cargar esas a través de curl, también se cuelgan siempre en el mismo punto, pero en uno diferente, y wget de nuevo siempre se cuelga en un punto, pero uno ligeramente diferente. Muy extraño.

¿Tienes alguna optimización habilitada?

No, ninguna optimización de (contenido). Tenía habilitada la función de 103 sugerencias tempranas, pero ya la deshabilité como intento de solucionar las cosas. Probé lo mismo con la configuración del protocolo, pero tampoco cambió nada:

Por cierto, no hay una cabecera de respuesta content-length, ¿podría eso causar un problema? Quiero decir, tampoco existe cuando se omite Cloudflare, pero ¿probablemente Cloudflare tenga algún problema? EDITAR: No, parece normal para páginas dinámicas, lo mismo ocurre con nuestras páginas de Wordpress y Matomo que, sin embargo, no causan ningún problema.

Y otro hallazgo al jugar con curl. Imprimir en STDOUT da como resultado la visualización del documento HTML completo, pero aún así se cuelga y al final:

  <p class='powered-by-link'>Powered by <a href="https://www.discourse.org">Discourse</a>, best viewed with JavaScript enabled</p>
</footer>


  </body>

</html>

Pero al intentar guardarlo con -o o redirección simple, o incluso simplemente canalizándolo a grep, se cuelga en un lugar diferente:

            <div class="link-bottom-line">
                <a href='/forum/c/general-discussion/7' class='badge-wrapper bullet'>
                  <span class='badge-category-bg' style='background-color: #F7941D'></span>
                  <span class='badge-category clear-badge'>
                    <span class='category-name'>General Discussion</span>
                  </span>
                </a>

Y puedo replicar al 100% estos mismos 73728 bytes al acceder a https://www.dietpi.com/forum/ con curl sin simplemente imprimirlo en la consola de inmediato. Esto es tan extraño :face_with_monocle:.


Entonces:

  • Todos los clientes se cuelgan al cargar cualquier documento HTML de nuestra instancia de Discourse.
  • Cada cliente se cuelga en el mismo byte al cargar la misma página.
  • Los diferentes clientes se cuelgan en puntos diferentes, pero en el mismo byte al repetir con el mismo cliente.
  • Cada página se cuelga en un punto diferente del documento y con un tamaño descargado diferente.
  • La misma herramienta curl se cuelga en puntos diferentes cuando solo imprime en STDOUT en comparación con canalizar o almacenar el documento en algún lugar.
  • wget puede descargar el documento completo (al menos https://www.dietpi.com/forum/) a un archivo, pero aún se cuelga al final, lo mismo ocurre cuando curl https://www.dietpi.com/forum/ imprime el documento completo en la consola, pero se cuelga al final.

Creo que podría ser un búfer. Pero al investigar, noté algo más.

wget -O - https://www.dietpi.com/forum/latest

Termina con

  </body>
</html>

Pero la conexión nunca se cierra.

Teoría: hay un problema de configuración en algún lugar donde hay una discrepancia en las versiones HTTP o en las cabeceras (como la conexión keep alive) y esto solo se convierte en un problema cuando un documento es mayor que X (sospecho que 64 KB).

Sí, wget siempre descarga el documento completo, y curl lo hace al imprimir directamente en la consola, pero la conexión no se cierra. Lo mismo ocurre con documentos mucho más pequeños, como probé uno de 14k sobre un tema con solo 2 publicaciones. Pero incluso los más pequeños generalmente no son descargados completamente por curl al usar tuberías o al guardar en un archivo, ni en el navegador.

Ambas herramientas siempre muestran HTTP/2, y en Cloudflare tengo habilitadas las solicitudes de origen HTTP/2. Pero vale la pena probar explícitamente con otras versiones de HTTP. Ayer deshabilité todas las configuraciones de protocolo en Cloudflare que se ven en la captura de pantalla anterior, y no ayudó. Pero lo intentaré de nuevo. También puedo habilitar los registros de acceso en el servidor para ver la solicitud entrante real de Cloudflare.

Probé todas las combinaciones de versiones HTTP (1.1-3) y TLS (1.2-1.3) compatibles, pero eso no marca la diferencia. También deshabilité el soporte HTTP3, las solicitudes de origen HTTP2 reanudan esta conexión 0-RTT nuevamente. No hay diferencia, curl sigue colgado exactamente en los mismos 73.728 bytes de https://www.dietpi.com/forum/.

Con respecto a la teoría de tamaños de documento demasiado grandes, https://www.dietpi.com/dietpi-software.html tiene 199.475 bytes y se carga perfectamente. Debo mencionar que el servidor (el mismo servidor web) aloja un sitio web estático, una instancia de MkDocs, Wordpress, Matomo, que funcionan perfectamente. También hay una instancia de Grafana donde el servidor web frontal actúa como proxy a través de un socket UNIX.

Pero estoy de acuerdo en que parece estar relacionado con búferes o tamaños de fragmentos o algo así. Es simplemente extraño que el tamaño descargado hasta el bloqueo varíe tanto entre clientes y páginas, mientras que permanece exactamente igual a pesar de cambiar las versiones del protocolo, y que la conexión ni siquiera se cierre cuando el documento se ha descargado por completo. Como si faltara la señal de parada, aunque en este punto me faltan conocimientos sobre HTTP. Por lo tanto, pensé en la cabecera content-length, pero obviamente esa no es obligatoria.

El servidor web también actúa como proxy para el contenedor de Discourse a través de un socket UNIX. Podría habilitar el oyente TCP para que la instancia de Discourse esté adicionalmente disponible sin el proxy (dejando el Nginx dentro del contenedor, por supuesto).

¿Podrías probar KeepAlive Off en Apache?

Supongo que eso al menos descartaría el servidor web, así que valdría la pena intentarlo.

1 me gusta

Sin cambios. También de la documentación de Apache:

Además, una conexión Keep-Alive con un cliente HTTP/1.0 solo se puede usar cuando la longitud del contenido se conoce de antemano.

Por lo tanto, dado que falta content-length, probablemente tenga sentido que de todos modos no se use para esta solicitud.

Dado que requiere una reconstrucción, lo haré un poco más tarde, cuando la actividad común de nuestro sitio web sea mínima. Um, estoy pensando en HTTPS… parece que necesito hacer algunos ajustes personalizados en la configuración interna de Nginx para mantener el socket UNIX funcional, así como las conexiones HTTP simples, mientras escucho en un puerto adicional para HTTPS con los certificados TLS del host, pero sin redirección/aplicación de HTTPS. … y un puerto TCP HTTP simple adicional también sería interesante, para clientes que pueden ignorar HSTS.

¿Por casualidad estás usando RocketLoader en CloudFlare? Sé que con otros scripts causa problemas.
¿También borraste la caché de CF?
¿Estás usando reglas de entrada en CF que podrían haber estado vinculadas a tu antigua IP de VPS y no se actualizaron a la nueva?

1 me gusta

Sin RocketLoader: Tenga en cuenta que, según las pruebas anteriores con curl y wget, que no interpretan ninguna sintaxis, por lo que no cargan ningún JavaScript, estilos ni nada más, el problema es que la descarga del documento HTML sin procesar siempre se cuelga.

La caché de Cloudflare no está activa para el foro, los documentos HTML sin procesar nunca se almacenaron en caché de todos modos.

No hay reglas específicas de VPS. En general, no hay reglas para el foro, aparte de omitir la caché. El problema aparece en ambos casos, por lo que la caché tampoco es el problema.

1 me gusta

Mientras probaba para eludir el proxy Apache2 en el host del contenedor Discourse, y deshabilitando las redirecciones HTTPS forzadas en Cloudflare para probar conexiones HTTP simples a través de curl también, finalmente encontré el culpable en Cloudflare:

No estoy seguro de qué cambió con nuestro cambio de VPS y/o la actualización de Discourse de 3.5.0.beta3 a 3.5.0.beta4 y/o coincidentemente en Discourse al mismo tiempo, pero parece que algo en los documentos HTML, CSS o JavaScript de Discourse hace que la reescritura HTTPS de Cloudflare de las URL incrustadas falle. Parece que las solicitudes curl parciales y colgadas no estaban realmente relacionadas, o tal vez lo están. Es extraño que en la pestaña de red del navegador se pueda ver el contenido parcial del documento HTML, como si la función de reescritura HTTPS lo hiciera mientras se transmite a través del documento.

¿Quizás alguien más tiene una instancia y una cuenta de Cloudflare para probar esto, ya sea un problema general o relacionado con nuestra instancia/configuración particular?

Por cierto, para probar la elusión del proxy, así como HTTP, manteniendo la conexión a través del proxy activa, ajustar manualmente la configuración de Nginx dentro del contenedor de esta manera funciona perfectamente:

root@dietpi-discourse:/var/www/discourse# cat /etc/nginx/conf.d/outlets/server/10-http.conf
listen unix:/shared/nginx.http.sock;
set_real_ip_from unix:;
listen 8080;
listen [::]:8080;
listen 8443 ssl;
listen [::]:8443 ssl;
http2 on;

ssl_protocols TLSv1.2 TLSv1.3;
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:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;

ssl_certificate /shared/fullchain.cer;
ssl_certificate_key /shared/dietpi.com.key;

ssl_session_tickets off;
ssl_session_timeout 1d;
ssl_session_cache shared:SSL:1m;

Es importante eliminar las redirecciones HTTPS y la cabecera HSTS, por supuesto, y exponer los puertos añadidos.

Y otro hallazgo: Usamos mod_sed para añadir nuestro código de seguimiento de Matomo a todas las respuestas text/html, justo antes de la etiqueta de cierre </head>. Desactivarlo para Discourse (o evitar el proxy de Apache2) también soluciona las cosas, a pesar de que Cloudflare Automatic HTTPS Rewrites está activo. Desactivar cualquiera de los dos soluciona las cosas. En todas las demás páginas la combinación funciona bien, incluso en páginas muy grandes que tenemos, más grandes que las páginas del foro que fallan. Así que tal vez los dos filtros, primero mod_set en nuestro proxy y luego las reescrituras de URL incrustadas por Cloudflare causan que algo se rompa, relacionado con los tamaños de documento o fragmentos o lo que sea.

Incrustamos el rastreador a través de la edición del tema de Discourse ahora, y además deshabilité las reescrituras automáticas de HTTPS de Cloudflare. No hay contenido mixto en todo nuestro sitio web. Y si lo hay, es bueno verlo y arreglarlo, en lugar de que Cloudflare lo oculte para siempre.

Estoy bastante seguro de que eso no puede funcionar.

No estoy muy seguro de qué problema intentas resolver, pero probablemente necesites habilitar force_https en tu app.yml.

4 Me gusta

Imagino que solo por el nombre “Cloudflare Automatic HTTPS Rewrites” se pueda malinterpretar. Cloudflare tiene 2 funciones:

  • “Always Use HTTPS” redirige todas las solicitudes HTTP simples a HTTPS, al igual que force_https en Discourse. Ambas estaban habilitadas previamente, y deshabilité ambas para probar si HTTPS tiene algo que ver con el problema o las páginas de Discourse de carga infinita y las solicitudes curl que se cuelgan. Esto funcionó perfectamente, incluso resolviendo todo el problema para las solicitudes HTTPS también, pero solo porque deshabilité “Cloudflare Automatic HTTPS Rewrites” en el mismo momento.
  • “Cloudflare Automatic HTTPS Rewrites” altera documentos HTML, CSS y JavaScript para reemplazar todas las URL incrustadas HTTP simples con variantes HTTPS, donde Cloudflare cree que el host es accesible a través de HTTPS (basado en la lista de precarga HSTS y similares). Esto es para evitar advertencias de contenido mixto.

Forzar o no forzar HTTPS en Cloudflare, en el proxy del host o en Discourse no importa. Lo que causó el problema fue la combinación del filtro mod_sed en el proxy del host y las ediciones HTTP simples incrustadas por Cloudflare. Por lo tanto, dos etapas en las que el contenido de los documentos pasó a través de un filtro. El problema no fue que hubiera ningún cambio real en el contenido (no hay contenido mixto en nuestro sitio, por lo que “Cloudflare Automatic HTTPS Rewrites” en realidad no cambia el cuerpo del documento), sino que probablemente estaba relacionado con fragmentos, búfer o tiempos.

1 me gusta

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.