Carregamento interminável atrás do Cloudflare

Movemos nosso servidor de um provedor de VPS para outro e também atualizei a instância via launcher rebuild para a versão mais recente, de 3.5.0.beta3 para 3.5.0.beta4.

A instância sempre funcionou bem atrás do Cloudflare, mas agora tentar acessá-la leva a uma animação de carregamento infinita de 5 pontos.

Tenho uma entrada no arquivo hosts no meu sistema local para contornar o Cloudflare, já que meu ISP (Deutsche Telekom AG) tem políticas de peering ruins, de modo que o acesso via Cloudflare é muito baixo às vezes. Então, inicialmente, não percebi o problema, pois o acesso sem o Cloudflare funciona bem. Então atualizei a instância e, portanto, agora não tenho certeza se a mudança de VPS ou a atualização do Discourse foi a mudança relevante. Assegurei via VPN e rede móvel que o problema é realmente o próprio Cloudflare agora, não o mau peering do meu ISP, e outros usuários também enfrentam o mesmo problema. O VPS antigo e o novo têm IPv6 disponível, e todo o sistema é exatamente o mesmo, transferido como um arquivo de imagem bruta.

Não há nenhuma mensagem de erro, nem no navegador (console), nem pelo proxy do sistema host, nem pelo Nginx dentro do contêiner, nem pelo Rails ou em qualquer outro lugar. Os documentos HTML e vários scripts carregam bem, e compará-los com os servidos ao contornar o Cloudflare mostra que tudo (que verifiquei) é idêntico. Os cabeçalhos de resposta também parecem em sua maioria os mesmos, exceto por alguns específicos do Cloudflare, é claro. As últimas coisas que vejo sendo carregadas são o mini profiler:

Claro que limpar o cache do navegador, usar janelas privadas etc. não mudaram nada. Limpar/desabilitar o cache do Cloudflare também não ajuda, então o cache não é o problema. Desabilitei temporariamente o cache do CF completamente para todo o fórum.

Notável dizer que o fórum roda em um subdiretório atrás de um proxy Apache no host, seguindo estas instruções: Serve Discourse from a subfolder (path prefix) instead of a subdomain
Anteriormente, criamos apenas um link simbólico ln -s . forum em vez dos links simbólicos de uploads/backups e dobramos as reescritas das instruções, o que funcionou bem por anos (e também agora sem o Cloudflare), mas como parte dos meus esforços de depuração, mudei para essas instruções para garantir que o proxy interno aplique todas as regras como pretendido. O cabeçalho confiável é CF-Connecting-IP, embora eu também tenha habilitado cloudflare.template.yml, mesmo que isso duplique as coisas um pouco. E também tentei mudar para frente e para trás várias partes desses templates e das instruções acima, também na tentativa de verificar se os cabeçalhos de IP do proxy fazem alguma diferença, já que a falta de CF-Connecting-IP é uma coisa ao contornar o Cloudflare.

Neste ponto, estou completamente sem ideias, não tenho um único rastro de onde o problema pode estar vindo, nenhum log/saída relacionado em lugar nenhum. Através do Cloudflare, o Discourse apenas fica travado na animação de carregamento sem mais rastros.

Espero que alguém tenha uma ideia de como depurar isso, ou se houve alguma mudança entre 3.5.0.beta3 e 3.5.0.beta4 que possa estar relacionada. Acho que um downgrade é problemático?

Esta é a instância: https://dietpi.com/forum/
EDIT: Desabilitei o Cloudflare por enquanto. Mas há um CNAME que ainda está passando pelo Cloudflare, então esses dois podem ser comparados: https://www.dietpi.com/forum/

Problema interessante.

É simplesmente \u003chttps://www.dietpi.com/forum/\u003e que fica travado para sempre.

$ 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â

    [<=>                                

O interessante é que chamadas como \u003chttps://www.dietpi.com/forum/site.json\u003e são bem-sucedidas.

\u003chttps://www.dietpi.com/forum/t/why-there-are-two-kernals-in-my-raspberry-pi4/23355\u003e não funciona e trava para sempre, mas \u003chttps://www.dietpi.com/forum/t/why-there-are-two-kernals-in-my-raspberry-pi4/23355.json\u003e funciona.

1 curtida

Interessante, de fato. Percebo agora que os documentos HTML não estão carregando completamente, mas param em algum ponto. Comparei /forum/ em ambos os casos e pensei que fossem idênticos, mas provavelmente eu estava focando demais no cabeçalho, enquanto partes do corpo na parte inferior estão faltando.

Essa última linha ao carregar via 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&quot;

Precisei truncá-la, pois excede em muito o limite de caracteres para uma postagem. O documento geralmente continua assim:

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

As outras páginas param exatamente no mesmo ponto. Acho que estamos chegando a algo aqui.

EDIT: Ah, espere, verifiquei errado, outras páginas param em outro lugar. Então não é este elemento/atributo HTML em particular.

Sim, cada página/documento HTML para no mesmo caractere quando carregado via navegador repetidamente, em janela privada, etc. Mas uma página diferente para em um ponto diferente. E ao carregar essas via curl, elas também param sempre no mesmo ponto, mas em um diferente, e wget novamente sempre para em um ponto, mas um ligeiramente diferente. Muito estranho.

Você tem alguma otimização habilitada?

Não, nenhuma otimização de (conteúdo). Eu tinha o recurso de 103 Early Hints ativado, mas já o desativei como tentativa de resolver as coisas. Tentei o mesmo com as configurações de protocolo, mas isso também não mudou nada:

A propósito, não há cabeçalho de resposta content-length, isso poderia causar um problema? Quero dizer, ele também não existe ao contornar o Cloudflare, mas provavelmente o Cloudflare então tem algum problema? EDIT: Não, parece normal para páginas dinâmicas, o mesmo com nossas páginas Wordpress e Matomo que, no entanto, não causam problemas.

E outra descoberta ao brincar com curl. Imprimir para STDOUT resulta no documento HTML completo sendo exibido, mas ainda assim trava e no 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>

Mas ao tentar salvá-lo via -o ou redirecionamento simples, ou mesmo apenas canalizando para grep, ele trava em um ponto 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>

E posso replicar 100% esses mesmos 73728 bytes ao acessar https://www.dietpi.com/forum/ com curl sem apenas imprimi-lo no console imediatamente. Isso é tão estranho :face_with_monocle:.


Então:

  • Todos os clientes travam ao carregar qualquer documento HTML de nossa instância do Discourse.
  • Cada cliente trava no mesmo byte ao carregar a mesma página.
  • Clientes diferentes travam em pontos diferentes, mas no mesmo byte ao repetir com o mesmo cliente.
  • Cada página trava em um ponto diferente do documento e em um tamanho de download diferente.
  • A mesma ferramenta curl trava em pontos diferentes ao apenas imprimir para STDOUT em vez de canalizar ou armazenar o documento em algum lugar.
  • wget é capaz de baixar o documento completo (pelo menos https://www.dietpi.com/forum/) para um arquivo, mas ainda trava no final, o mesmo quando curl https://www.dietpi.com/forum/ imprime o documento completo no console, mas trava no final.

Acho que isso pode ser buffering. Mas ao investigar, notei outra coisa.

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

Termina com

  </body>
</html>

Mas a conexão nunca é fechada.

Teoria: há um problema de configuração em algum lugar onde há uma incompatibilidade nas versões HTTP ou nos cabeçalhos (como conexão keep alive) e isso só se torna um problema quando um documento é maior que X (suspeito que 64KB).

Sim, wget sempre baixa o documento inteiro, e curl faz isso ao imprimir diretamente no console, mas a conexão não é fechada. O mesmo acontece com documentos muito menores, como testei um de 14k sobre um tópico com apenas 2 posts. Mas mesmo os menores geralmente não são totalmente baixados por curl ao usar pipe ou salvar em arquivo, nem no navegador.

Ambas as ferramentas sempre mostram HTTP/2, e no Cloudflare tenho requisições de origem HTTP/2 habilitadas. Mas vale a pena testar usando outras versões HTTP explicitamente. Ontem desabilitei todas as configurações de protocolo no Cloudflare vistas na captura de tela acima, e isso não ajudou. Mas vou tentar novamente. Também posso habilitar logs de acesso no servidor para ver a requisição real vinda do Cloudflare.

Tentei todas as combinações de versões HTTP (1.1-3) e TLS (1.2-1.3) suportadas, mas isso não faz diferença. Também desabilitei o suporte HTTP3, requisições de origem HTTP2, esta retomada de conexão 0-RTT novamente. Nenhuma diferença, curl continua travando exatamente nos mesmos 73.728 bytes de https://www.dietpi.com/forum/.

Em relação à teoria de tamanhos de documento muito grandes, https://www.dietpi.com/dietpi-software.html tem 199.475 bytes e carrega perfeitamente. Devo mencionar que o servidor (o mesmo servidor web) hospeda um site estático, instância MkDocs, Wordpress, Matomo, que funcionam perfeitamente. Há também uma instância Grafana onde o servidor web frontal atua como proxy via socket UNIX.

Mas concordo que parece estar relacionado a buffers ou tamanhos de chunk ou algo assim. É apenas estranho que o tamanho baixado até o travamento varie tanto entre clientes e páginas, enquanto permanece exatamente o mesmo apesar de mudar as versões do protocolo, e que a conexão nem sequer é fechada quando o documento foi totalmente baixado. Como se o sinal de parada estivesse faltando, embora eu esteja sem insights sobre HTTP neste ponto. Daí pensei sobre o cabeçalho content-length, mas obviamente ele não é obrigatório.

O servidor web também atua como proxy para o container Discourse via socket UNIX. Eu poderia habilitar o listener TCP para tornar a instância Discourse adicionalmente disponível sem o proxy (deixando o Nginx dentro do container, é claro).

Você poderia tentar KeepAlive Off no Apache?

Eu acho que isso pelo menos possivelmente eliminaria o servidor web, então valeria a pena tentar.

1 curtida

Nenhuma alteração. Também da documentação do Apache:

Além disso, uma conexão Keep-Alive com um cliente HTTP/1.0 só pode ser usada quando o comprimento do conteúdo é conhecido antecipadamente.

Portanto, como falta content-length, provavelmente faz sentido que ele não seja usado de qualquer maneira para esta solicitação.

Como requer uma reconstrução, farei isso um pouco mais tarde, quando a atividade comum do nosso site for mínima. Hum, estou apenas pensando em HTTPS… parece que preciso fazer alguns ajustes personalizados na configuração interna do Nginx para manter o socket UNIX funcional, bem como conexões HTTP simples, enquanto escuto em uma porta adicional para HTTPS com os certificados TLS do host, mas sem redirecionamento/imposição de HTTPS. … e uma porta TCP HTTP simples adicional também seria interessante, para clientes que podem ignorar HSTS.

Você por acaso está usando o RocketLoader no CloudFlare? Sei que com alguns outros scripts isso causa problemas.
Além disso, você limpou o cache do CF?
Você está usando regras de entrada no CF que podem ter sido vinculadas ao seu antigo IP de VPS e não atualizadas para o novo?

1 curtida

Sem RocketLoader: Observe que, a partir dos testes acima com curl e wget, que não interpretam nenhuma sintaxe, portanto não carregam nenhum JavaScript, estilos ou qualquer outra coisa, o problema é que o download do documento HTML bruto sempre trava.

O cache do Cloudflare não está ativo para o fórum, os documentos HTML brutos nunca foram cacheados de qualquer forma.

Sem regras específicas de VPS. Geralmente não há regras para o fórum, além de contornar o cache. O problema aparece em ambos os casos, então o cache também não é o problema.

1 curtida

Ao testar para contornar o proxy Apache2 no host do contêiner Discourse e desabilitar os redirecionamentos forçados de HTTPS no Cloudflare para testar conexões HTTP simples via curl, finalmente encontrei o culpado no Cloudflare:

Não tenho certeza do que mudou com nossa troca de VPS e/ou a atualização do Discourse de 3.5.0.beta3 para 3.5.0.beta4 e/ou coincidentemente no Discourse ao mesmo tempo, mas parece que algo nos documentos HTML, CSS ou JavaScript do Discourse faz com que a reescrita HTTPS do Cloudflare de URLs incorporadas falhe. Parece que as requisições curl parciais e pendentes não estavam realmente relacionadas, ou talvez estejam. É estranho que na aba de rede do navegador se possa ver o conteúdo parcial do documento HTML, como se o recurso de reescrita HTTPS o fizesse enquanto o transmite pelo documento.

Talvez alguém mais tenha uma instância e uma conta Cloudflare para testar isso, seja um problema geral ou relacionado à nossa instância/configuração específica?

A propósito, para testar o contorno do proxy, bem como o HTTP, mantendo a conexão via proxy ativa, ajustar manualmente a configuração do Nginx dentro do contêiner desta forma funciona perfeitamente:

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;

É importante remover os redirecionamentos HTTPS e o cabeçalho HSTS, é claro, e expor as portas adicionadas.

E outra descoberta: Usamos o mod_sed para adicionar nosso código de rastreamento Matomo a todas as respostas text/html, logo antes da tag de fechamento </head>. Desativá-lo para o Discourse (ou contornar o proxy Apache2) também resolve as coisas, apesar de o Cloudflare Automatic HTTPS Rewrites estar ativo. Desativar um ou ambos resolve as coisas. Em todas as outras páginas, a combinação funciona bem, inclusive em páginas muito grandes que temos, maiores que as páginas do fórum que falham. Portanto, talvez os dois filtros, primeiro o mod_set em nosso proxy e depois as reescritas de URL incorporadas pelo Cloudflare causem algo a quebrar, relacionado a tamanhos de documento ou chunk, ou qualquer outra coisa.

Incorporamos o rastreador por meio da edição de tema do Discourse agora, e desativei adicionalmente o Cloudflare Automatic HTTPS Rewrites. Não há conteúdo misto em todo o nosso site. E se houver, é bom ver e corrigir, em vez de ter o Cloudflare mascarando-o para sempre.

[quote=“MichaIng, post:14, topic:364596”]para testar conexões HTTP simples via curl também,
[/quote]

Tenho certeza que isso não pode funcionar.

Não tenho certeza de qual problema você está tentando resolver, mas provavelmente você precisa habilitar force_https em seu app.yml.

4 curtidas

Imagino que apenas pelo nome "Cloudflare Automatic HTTPS Rewrites" possa haver um mal-entendido. A Cloudflare tem 2 recursos:

  • "Always Use HTTPS" redireciona todas as requisições HTTP simples para HTTPS, assim como o force_https no Discourse faz. Ambos estavam previamente ativados, e desativei ambos para testar se o HTTPS tem algo a ver com o problema ou com as páginas do Discourse em carregamento infinito e requisições curl travadas. Isso funcionou perfeitamente, até resolvendo todo o problema para requisições HTTPS também, mas apenas porque desativei o "Cloudflare Automatic HTTPS Rewrites" na mesma etapa.
  • "Cloudflare Automatic HTTPS Rewrites" altera documentos HTML, CSS e JavaScript para substituir todas as URLs HTTP simples incorporadas por variantes HTTPS, onde a Cloudflare acha que o host é alcançável via HTTPS (com base na lista de pré-carregamento HSTS e similares). Isso é para evitar avisos de conteúdo misto.

Forçar ou não o HTTPS na Cloudflare, no proxy do host ou no Discourse não importa. O que causou o problema foi a combinação do filtro mod_sed no proxy do host e as edições HTTP simples incorporadas pela Cloudflare. Portanto, dois estágios em que o conteúdo dos documentos passou por um filtro. O problema não foi que houve qualquer alteração de conteúdo real (não há conteúdo misto em nosso site, portanto, "Cloudflare Automatic HTTPS Rewrites" não altera realmente o corpo do documento), mas provavelmente relacionado a chunks, buffer ou tempo.

1 curtida

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