<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[WebSocket &#x2F; socket.io 403 + xhr poll error behind Cloudflare (Cluster scaling+ Redis)]]></title><description><![CDATA[<p dir="auto">Hi everyone, <a href="https://community.nodebb.org/user/baris" rel="nofollow ugc">@baris</a></p>
<p dir="auto">I’m currently facing persistent <code>xhr poll error</code> issues with NodeBB behind <strong>Cloudflare</strong> (Free plan, proxied / orange cloud). I’ve been debugging this for quite a while and would really appreciate some expert input.</p>
<hr />
<h1><img src="https://host1.bosio.info/assets/plugins/nodebb-plugin-emoji/emoji/android/1f50e.png?v=5a1f7d4f361" class="not-responsive emoji emoji-android emoji--mag_right" style="height:23px;width:auto;vertical-align:middle" title="🔎" alt="🔎" /> The Problem</h1>
<p dir="auto">In the browser console I consistently get:</p>
<pre><code>[socket.io] Connection error: xhr poll error
</code></pre>
<p dir="auto">With error i nnodebb :</p>
<p dir="auto"><img src="https://community.nodebb.org/assets/uploads/files/1772441871370-ae8a3eb4-96d2-4fee-903f-4147c3522059-image.jpeg" alt="ae8a3eb4-96d2-4fee-903f-4147c3522059-image.jpeg" class=" img-fluid img-markdown" /><br />
Network tab shows:</p>
<pre><code>/socket.io/?_csrf=...&amp;amp;EIO=4&amp;amp;transport=polling → 403
</code></pre>
<p dir="auto"><img src="https://community.nodebb.org/assets/uploads/files/1772441925180-570a8724-b086-44a1-bf3e-51d4e0935fb6-image.jpeg" alt="570a8724-b086-44a1-bf3e-51d4e0935fb6-image.jpeg" class=" img-fluid img-markdown" /></p>
<p dir="auto">So the failure happens during the <strong>polling transport phase</strong>, before WebSocket upgrade. I guess</p>
<p dir="auto">Login works.<br />
Sessions work.<br />
Forum loads.<br />
But sockets keep failing with 403 with error connexion in nodebb interface</p>
<hr />
<h1>🧭 Infrastructure Overview</h1>
<h2>Server</h2>
<ul>
<li>VPS wHetzner with public IP and firewall Hetzner with open port : 80, 443, Virtualmin CF Proxied 8443, nodebb 4567, redis 6379, clustering 4567, 4568, 4569</li>
<li>Same ports open in the server with firewalld/virtualmin</li>
<li>Managed via Virtualmin</li>
<li>Nginx reverse proxy</li>
<li>Let’s Encrypt SSL</li>
<li>Ubuntu Server</li>
</ul>
<h2>NodeBB Setup</h2>
<ul>
<li>Latest stable NodeBB 4.9.1</li>
<li>Node.js LTS 18</li>
<li>MongoDB</li>
<li>Redis enabled</li>
<li>Cluster mode enabled scaling</li>
</ul>
<p dir="auto"><code>Here my config.json</code>:</p>
<pre><code>```

{

    "url": "https://xxx-xxx.net",

    "socket.io": {

     "cors": {

      "origin": "*"

     }

    },

    "trust proxy": true,

    "secret": "xxxx-xxxx-4c42-xxxxx-xxxxxxxx",

    "database": "mongo",

    "mongo": {

        "host": "127.0.0.1",

        "port": "27017",

        "username": "nodebb",

        "password": "xxxxxxxxxxxxxxxxx",

        "database": "nodebb",

        "uri": ""

    },

    "port": [4567, 4568,4569],

        "redis": {

        "host":"127.0.0.1",

        "port":"6379",

        "database": 5

    }

}

```
</code></pre>
<p dir="auto">Here my vhost nginx :</p>
<pre><code>    

        upstream io_nodes {
    
            ip_hash;
            server 127.0.0.1:4567;
            server 127.0.0.1:4568;
            server 127.0.0.1:4569;
        }

        server {

        	server_name xx-xx.net www.xx-xxx.net mail.xx-xx.net webmail.xx-xx.net admin.xx-xx.net;

        	root /home/xxx-xxx/nodebb; #dossier root nodebb

        	index index.php index.htm index.html;

        	access_log /var/log/virtualmin/xxx-xxx.net_access_log;

        	error_log /var/log/virtualmin/xx-xx.net_error_log;

        	client_max_body_size 20M;

        	fastcgi_param GATEWAY_INTERFACE CGI/1.1;
    
        	fastcgi_param SERVER_SOFTWARE nginx;

        	fastcgi_param QUERY_STRING $query_string;

        	fastcgi_param REQUEST_METHOD $request_method;
    
        	fastcgi_param CONTENT_TYPE $content_type;

        	fastcgi_param CONTENT_LENGTH $content_length;

        	fastcgi_param SCRIPT_FILENAME "/home/xx-xx/public_html$fastcgi_script_name";

        	fastcgi_param SCRIPT_NAME $fastcgi_script_name;
    
        	fastcgi_param REQUEST_URI $request_uri;
    
        	fastcgi_param DOCUMENT_URI $document_uri;
    
        	fastcgi_param DOCUMENT_ROOT /home/xx-xxx/public_html;

        	fastcgi_param SERVER_PROTOCOL $server_protocol;

        	fastcgi_param REMOTE_ADDR $remote_addr;
    
               	fastcgi_param REMOTE_PORT $remote_port;
    
        	fastcgi_param SERVER_ADDR $server_addr;
    
        	fastcgi_param SERVER_PORT $server_port;
    
           	fastcgi_param SERVER_NAME $server_name;

        	fastcgi_param PATH_INFO $fastcgi_path_info;
    
        	fastcgi_param HTTPS $https;
    

        	location /.well-known {
        	}
    
        
    
        	location ^~ /.well-known/acme-challenge/ {
            	try_files $uri /;
            	allow all;
        	}
    

            # Ajout du Reverse Proxy :
            location / {

                proxy_set_header X-Real-IP $remote_addr;
                proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                proxy_set_header X-Forwarded-Proto $scheme;

                #proxy_set_header Host $http_host;

                proxy_set_header X-NginX-Proxy true;
	        proxy_set_header Host $host;
                proxy_pass http://io_nodes; 
                proxy_redirect off;

                # Socket.IO Support

                proxy_http_version 1.1;
                proxy_set_header Upgrade $http_upgrade;
                proxy_set_header Connection "upgrade";
            }
    

        	# serve static assets

        	# Ajouter le bloc ci-dessous qui forcera tout le trafic dans le cluster nodebb - redis lorsqu'il est référencé avec "[@nodebb](https://community.nodebb.org/user/nodebb)"

            # (A désactiver si pas de cluster nodebb - redis ou http://127.0.0.1:4567 pour serve static assets )
    

        	location [@nodebb](https://community.nodebb.org/user/nodebb) {

            	# proxy_pass http://127.0.0.1:4567;

        	proxy_pass http://io_nodes;

        	}
    

        	location ~ ^/assets/(.*) {

            	root /home/xxx-xxx/nodebb/;
            	try_files /build/public/$1 /public/$1 [@nodebb](https://community.nodebb.org/user/nodebb);
        	}
    
        
    
        	# serve static assets compressed
    
        	gzip            on;
        	gzip_min_length 1000;
        	gzip_proxied    off;
        	gzip_types      text/plain application/xml text/javascript application/javascript application/x-javascript text/css application/json;
    

        	location ~ "\.php(/|$)" {

        		try_files $uri $fastcgi_script_name =404;
        		default_type application/x-httpd-php;
        		fastcgi_pass unix:/run/php/173162234249002.sock;
        	}
    
        
    
        	fastcgi_split_path_info "^(.+\.php)(/.+)$";

        	if ($host = webmail.xxx-xxxx.net) {
    
        		rewrite "^/(.*)$" "https://xxx-xxx.net:20000/$1" redirect;
        	}
    
        
    
        	if ($host = admin.planete-warez.net) {

            		rewrite "^/(.*)$" "https://planete-warez.net:10000/$1" redirect;

        	}
    
        
    
        	listen 65.21.3.134:443 ssl http2;
        	listen [2a01:4f9:c010:db20::1]:443 ssl http2;

            ssl_certificate /etc/letsencrypt/live/xx-xx.net/fullchain.pem; # managed by Certbot
            ssl_certificate_key /etc/letsencrypt/live/xx-xx.net/privkey.pem; # managed by Certbot
    
        
    
        	rewrite /awstats/awstats.pl /cgi-bin/awstats.pl;
    
        }
    
        server {

            if ($host = xx-xx.net) {
                return 301 https://$host$request_uri;
            } # managed by Certbot
    

        	server_name xx-xx.net www.xx-xx.net mail.xx-xx.net webmail.xx-xx.net admin.xx-xx.net;

        	listen 65.21.3.134;
        	listen [2a01:4f9:c010:db20::1];

            return 404; # managed by Certbot

        }
</code></pre>
<p dir="auto">Nginx has been reloaded.<br />
NodeBB restarted multiple times without  "origin": "*"  or "cors": {</p>
<hr />
<h1><img src="https://host1.bosio.info/assets/plugins/nodebb-plugin-emoji/emoji/android/2601.png?v=5a1f7d4f361" class="not-responsive emoji emoji-android emoji--cloud" style="height:23px;width:auto;vertical-align:middle" title="☁" alt="☁" />️ Cloudflare Configuration</h1>
<p dir="auto">Plan: Free<br />
Status: Proxied (orange cloud)</p>
<h3>SSL/TLS</h3>
<ul>
<li>Mode: Full (Strict) with Let's encrypt SSL on the web servers with certbot</li>
<li>WebSockets: ON</li>
</ul>
<h3>Cache Rules</h3>
<ul>
<li>
<p dir="auto">Rules created:</p>
<ul>
<li>If URI Path contains <code>/socket.io/</code></li>
<li>Then: Bypass cache</li>
</ul>
</li>
</ul>
<h3>WAF</h3>
<ul>
<li>Custom ignore rule for <code>/socket.io/*</code></li>
<li>Custom ignore rule for <code>/api/*</code></li>
</ul>
<p dir="auto">No rate limiting.</p>
<p dir="auto">--&gt; Issue persists.</p>
<hr />
<h1>🧪 What Has Been Tested</h1>
<ul>
<li>Verified Redis connectivity</li>
<li>Verified cluster processes running</li>
<li>Verified cluster port in netstats</li>
<li>Confirmed <code>trust proxy: true</code></li>
<li>Confirmed <code>X-Forwarded-Proto</code> is set</li>
<li>Cleared all caches</li>
<li>Restarted everything multiple times</li>
<li>test without  "origin": "*"  or "cors": {</li>
</ul>
<hr />
<h1>🧠 Observations</h1>
<p dir="auto">The failing request includes <code>_csrf</code>, for example:</p>
<pre><code>/socket.io/?_csrf=...&amp;amp;EIO=4&amp;amp;transport=polling
</code></pre>
<p dir="auto">This suggests either:</p>
<ul>
<li>CSRF validation failing</li>
<li>Session cookie mismatch</li>
<li>Header mismatch</li>
<li>Cloudflare altering something in polling requests</li>
</ul>
<p dir="auto">But:</p>
<ul>
<li>Login works</li>
<li>Normal requests work</li>
<li>Only socket polling fails</li>
</ul>
<hr />
<h1><img src="https://host1.bosio.info/assets/plugins/nodebb-plugin-emoji/emoji/android/2753.png?v=5a1f7d4f361" class="not-responsive emoji emoji-android emoji--question" style="height:23px;width:auto;vertical-align:middle" title="❓" alt="❓" /> Questions</h1>
<ol>
<li>Has anyone experienced 403 specifically on <code>transport=polling</code> behind Cloudflare?</li>
<li>Is there anything specific in NodeBB cluster mode that could cause this?</li>
<li>Could Cloudflare be interfering with long-polling specifically (even with WebSockets enabled)?</li>
<li>Is there a recommended minimal known-good config for NodeBB + Cloudflare (Free) + cluster?</li>
</ol>
<hr />
<p dir="auto">At this point I’m unsure whether:</p>
<ul>
<li>This is CSRF related</li>
<li>This is Cloudflare related</li>
<li>This is a subtle proxy/session issue</li>
<li>Or something specific to polling transport</li>
</ul>
<p dir="auto">Any guidance or expert would be greatly appreciated.</p>
<p dir="auto">Thanks in advance <img src="https://host1.bosio.info/assets/plugins/nodebb-plugin-emoji/emoji/android/1f64f.png?v=5a1f7d4f361" class="not-responsive emoji emoji-android emoji--pray" style="height:23px;width:auto;vertical-align:middle" title="🙏" alt="🙏" /></p>
]]></description><link>https://host1.bosio.info/topic/37262142-0346-48f4-bf48-c4072fbf796a/websocket-socket.io-403-xhr-poll-error-behind-cloudflare-cluster-scaling-redis</link><generator>RSS for Node</generator><lastBuildDate>Tue, 05 May 2026 16:31:28 GMT</lastBuildDate><atom:link href="https://host1.bosio.info/topic/37262142-0346-48f4-bf48-c4072fbf796a.rss" rel="self" type="application/rss+xml"/><pubDate>Mon, 02 Mar 2026 09:24:17 GMT</pubDate><ttl>60</ttl><item><title><![CDATA[Reply to WebSocket &#x2F; socket.io 403 + xhr poll error behind Cloudflare (Cluster scaling+ Redis) on Sat, 18 Apr 2026 12:54:56 GMT]]></title><description><![CDATA[<p dir="auto"><a href="https://community.nodebb.org/user/downpw" rel="nofollow ugc">@DownPW</a></p>
<ol>
<li>No objections to this approach</li>
<li>No, no known contraindications</li>
<li>Yes, probably <img src="https://host1.bosio.info/assets/plugins/nodebb-plugin-emoji/emoji/android/1f642.png?v=5a1f7d4f361" class="not-responsive emoji emoji-android emoji--slightly_smiling_face" style="height:23px;width:auto;vertical-align:middle" title="🙂" alt="🙂" /></li>
</ol>
]]></description><link>https://host1.bosio.info/post/https://community.nodebb.org/post/107009</link><guid isPermaLink="true">https://host1.bosio.info/post/https://community.nodebb.org/post/107009</guid><dc:creator><![CDATA[julian@community.nodebb.org]]></dc:creator><pubDate>Sat, 18 Apr 2026 12:54:56 GMT</pubDate></item><item><title><![CDATA[Reply to WebSocket &#x2F; socket.io 403 + xhr poll error behind Cloudflare (Cluster scaling+ Redis) on Fri, 17 Apr 2026 22:13:02 GMT]]></title><description><![CDATA[<p dir="auto">Hi <a href="https://community.nodebb.org/user/baris" rel="nofollow ugc">@baris</a> <a href="https://community.nodebb.org/user/julian" rel="nofollow ugc">@julian</a> ,</p>
<p dir="auto">Like you know, I'm running a NodeBB forum behind Free Cloudflare Plan with a 3-node cluster setup on nginx. I was experiencing frequent 400 (Bad Request) errors on <a href="http://socket.io" rel="nofollow ugc">socket.io</a> connections even though everything was OK on Cloudflare's end: WAF rules &amp; Cache rules to ignore <a href="http://socket.io" rel="nofollow ugc">socket.io</a>, visible in the browser console:</p>
<pre><code>GET https://xxxxxx/socket.io/?_csrf=&amp;amp;EIO=4&amp;amp;transport=websocket&amp;amp;sid=xxx HTTP/1.1 400
POST https://xxxxxxx/socket.io/?_csrf=&amp;amp;EIO=4&amp;amp;transport=polling&amp;amp;sid=xxx HTTP/1.1 400
</code></pre>
<p dir="auto">The forum was still accessible and functional, but real-time features were degraded and the forum felt noticeably slower.</p>
<p dir="auto">After investigation, I found the root cause: my nginx upstream was configured with <code>ip_hash</code> (as recommended in the NodeBB documentation), but this does not work correctly when Cloudflare is sitting in front of the server.</p>
<p dir="auto">The issue is that Cloudflare uses multiple outgoing IPs for the same end user. This means that the initial <a href="http://socket.io" rel="nofollow ugc">socket.io</a> polling request and the subsequent WebSocket upgrade request can arrive from two different Cloudflare IPs, causing ip_hash to route them to different nodes in the cluster.<br />
Since the session is tied to a specific node, the second request gets a 400 response because that node has no knowledge of the session.</p>
<p dir="auto">The fix was to replace <code>ip_hash</code> with a hash based on the real client IP passed by Cloudflare in the X-Forwarded-For header:</p>
<pre><code>upstream io_nodes {
    hash $http_x_forwarded_for consistent;
    server 127.0.0.1:4567;
    server 127.0.0.1:4568;
    server 127.0.0.1:4569;
}
</code></pre>
<p dir="auto">After applying this change and reloading nginx, the 400 errors dropped significantly.</p>
<p dir="auto">My questions are:</p>
<ol>
<li>Is this the recommended approach for NodeBB clusters running behind Cloudflare?</li>
<li>Are there any known caveats with using $http_x_forwarded_for consistent instead of ip_hash in this context?</li>
<li>Should the NodeBB nginx documentation mention this as an alternative for Cloudflare users?</li>
</ol>
<p dir="auto">Thanks in advance.</p>
]]></description><link>https://host1.bosio.info/post/https://community.nodebb.org/post/107008</link><guid isPermaLink="true">https://host1.bosio.info/post/https://community.nodebb.org/post/107008</guid><dc:creator><![CDATA[downpw@community.nodebb.org]]></dc:creator><pubDate>Fri, 17 Apr 2026 22:13:02 GMT</pubDate></item><item><title><![CDATA[Reply to WebSocket &#x2F; socket.io 403 + xhr poll error behind Cloudflare (Cluster scaling+ Redis) on Wed, 04 Mar 2026 21:01:06 GMT]]></title><description><![CDATA[<p dir="auto">IMPORTANT: seems to be good, but must have <strong>WAF rules</strong> and <strong>page rules</strong> on Cloud Flare for <strong><a href="http://websocket.io" rel="nofollow ugc">websocket.io</a></strong></p>
<p dir="auto">--&gt; I can share if someone is interrested.</p>
]]></description><link>https://host1.bosio.info/post/https://community.nodebb.org/post/106737</link><guid isPermaLink="true">https://host1.bosio.info/post/https://community.nodebb.org/post/106737</guid><dc:creator><![CDATA[downpw@community.nodebb.org]]></dc:creator><pubDate>Wed, 04 Mar 2026 21:01:06 GMT</pubDate></item><item><title><![CDATA[Reply to WebSocket &#x2F; socket.io 403 + xhr poll error behind Cloudflare (Cluster scaling+ Redis) on Mon, 02 Mar 2026 17:44:08 GMT]]></title><description><![CDATA[<p dir="auto">That's correct, you can also try without putting it in config.json. Nodebb automatically parses it from config.json url property. If you forum url is <code>"url": "https://myforum.com</code> it will be set to <code>https://myforum.com:*</code></p>
]]></description><link>https://host1.bosio.info/post/https://community.nodebb.org/post/106707</link><guid isPermaLink="true">https://host1.bosio.info/post/https://community.nodebb.org/post/106707</guid><dc:creator><![CDATA[baris@community.nodebb.org]]></dc:creator><pubDate>Mon, 02 Mar 2026 17:44:08 GMT</pubDate></item><item><title><![CDATA[Reply to WebSocket &#x2F; socket.io 403 + xhr poll error behind Cloudflare (Cluster scaling+ Redis) on Mon, 02 Mar 2026 15:34:39 GMT]]></title><description><![CDATA[<p dir="auto">you said, like this ? :</p>
<pre><code>"socket.io": {
   "origins": "https://example.com"
}
</code></pre>
<p dir="auto">&gt; <a href="https://community.nodebb.org/user/baris" rel="nofollow ugc">@baris</a> <a href="https://community.nodebb.org/post/106702" rel="nofollow ugc">said</a>:<br />
&gt;<br />
&gt; Did you try disabling cloudflare, does polling work when you do that?</p>
<p dir="auto">I will test tonight</p>
]]></description><link>https://host1.bosio.info/post/https://community.nodebb.org/post/106705</link><guid isPermaLink="true">https://host1.bosio.info/post/https://community.nodebb.org/post/106705</guid><dc:creator><![CDATA[downpw@community.nodebb.org]]></dc:creator><pubDate>Mon, 02 Mar 2026 15:34:39 GMT</pubDate></item><item><title><![CDATA[Reply to WebSocket &#x2F; socket.io 403 + xhr poll error behind Cloudflare (Cluster scaling+ Redis) on Mon, 02 Mar 2026 13:11:16 GMT]]></title><description><![CDATA[<p dir="auto">One small issue I see is the name of the <a href="http://socket.io" rel="nofollow ugc">socket.io</a>:origins config. It is <code>origins</code> not <code>origin</code>. So in config.json it should be lie below. With that said you probably don't want to set it to <code>*</code> but just your <code>https://example.com:*</code> your url in config.json.</p>
<pre><code>"socket.io": {
   "origins": "*"
}
</code></pre>
<p dir="auto">Did you try disabling cloudflare, does polling work when you do that?</p>
]]></description><link>https://host1.bosio.info/post/https://community.nodebb.org/post/106702</link><guid isPermaLink="true">https://host1.bosio.info/post/https://community.nodebb.org/post/106702</guid><dc:creator><![CDATA[baris@community.nodebb.org]]></dc:creator><pubDate>Mon, 02 Mar 2026 13:11:16 GMT</pubDate></item></channel></rss>