WebSocket / socket.io 403 + xhr poll error behind Cloudflare (Cluster scaling+ Redis)
-
Hi everyone, @baris
I’m currently facing persistent
xhr poll errorissues with NodeBB behind Cloudflare (Free plan, proxied / orange cloud). I’ve been debugging this for quite a while and would really appreciate some expert input.
The ProblemIn the browser console I consistently get:
[socket.io] Connection error: xhr poll errorWith error i nnodebb :

Network tab shows:/socket.io/?_csrf=...&EIO=4&transport=polling → 403
So the failure happens during the polling transport phase, before WebSocket upgrade. I guess
Login works.
Sessions work.
Forum loads.
But sockets keep failing with 403 with error connexion in nodebb interface
🧭 Infrastructure Overview
Server
- 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
- Same ports open in the server with firewalld/virtualmin
- Managed via Virtualmin
- Nginx reverse proxy
- Let’s Encrypt SSL
- Ubuntu Server
NodeBB Setup
- Latest stable NodeBB 4.9.1
- Node.js LTS 18
- MongoDB
- Redis enabled
- Cluster mode enabled scaling
Here my config.json:``` { "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 } } ```Here my vhost nginx :
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 }Nginx has been reloaded.
NodeBB restarted multiple times without "origin": "*" or "cors": {
️ Cloudflare ConfigurationPlan: Free
Status: Proxied (orange cloud)SSL/TLS
- Mode: Full (Strict) with Let's encrypt SSL on the web servers with certbot
- WebSockets: ON
Cache Rules
-
Rules created:
- If URI Path contains
/socket.io/ - Then: Bypass cache
- If URI Path contains
WAF
- Custom ignore rule for
/socket.io/* - Custom ignore rule for
/api/*
No rate limiting.
--> Issue persists.
🧪 What Has Been Tested
- Verified Redis connectivity
- Verified cluster processes running
- Verified cluster port in netstats
- Confirmed
trust proxy: true - Confirmed
X-Forwarded-Protois set - Cleared all caches
- Restarted everything multiple times
- test without "origin": "*" or "cors": {
🧠 Observations
The failing request includes
_csrf, for example:/socket.io/?_csrf=...&EIO=4&transport=pollingThis suggests either:
- CSRF validation failing
- Session cookie mismatch
- Header mismatch
- Cloudflare altering something in polling requests
But:
- Login works
- Normal requests work
- Only socket polling fails
Questions- Has anyone experienced 403 specifically on
transport=pollingbehind Cloudflare? - Is there anything specific in NodeBB cluster mode that could cause this?
- Could Cloudflare be interfering with long-polling specifically (even with WebSockets enabled)?
- Is there a recommended minimal known-good config for NodeBB + Cloudflare (Free) + cluster?
At this point I’m unsure whether:
- This is CSRF related
- This is Cloudflare related
- This is a subtle proxy/session issue
- Or something specific to polling transport
Any guidance or expert would be greatly appreciated.
Thanks in advance

-
Hi everyone, @baris
I’m currently facing persistent
xhr poll errorissues with NodeBB behind Cloudflare (Free plan, proxied / orange cloud). I’ve been debugging this for quite a while and would really appreciate some expert input.
The ProblemIn the browser console I consistently get:
[socket.io] Connection error: xhr poll errorWith error i nnodebb :

Network tab shows:/socket.io/?_csrf=...&EIO=4&transport=polling → 403
So the failure happens during the polling transport phase, before WebSocket upgrade. I guess
Login works.
Sessions work.
Forum loads.
But sockets keep failing with 403 with error connexion in nodebb interface
🧭 Infrastructure Overview
Server
- 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
- Same ports open in the server with firewalld/virtualmin
- Managed via Virtualmin
- Nginx reverse proxy
- Let’s Encrypt SSL
- Ubuntu Server
NodeBB Setup
- Latest stable NodeBB 4.9.1
- Node.js LTS 18
- MongoDB
- Redis enabled
- Cluster mode enabled scaling
Here my config.json:``` { "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 } } ```Here my vhost nginx :
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 }Nginx has been reloaded.
NodeBB restarted multiple times without "origin": "*" or "cors": {
️ Cloudflare ConfigurationPlan: Free
Status: Proxied (orange cloud)SSL/TLS
- Mode: Full (Strict) with Let's encrypt SSL on the web servers with certbot
- WebSockets: ON
Cache Rules
-
Rules created:
- If URI Path contains
/socket.io/ - Then: Bypass cache
- If URI Path contains
WAF
- Custom ignore rule for
/socket.io/* - Custom ignore rule for
/api/*
No rate limiting.
--> Issue persists.
🧪 What Has Been Tested
- Verified Redis connectivity
- Verified cluster processes running
- Verified cluster port in netstats
- Confirmed
trust proxy: true - Confirmed
X-Forwarded-Protois set - Cleared all caches
- Restarted everything multiple times
- test without "origin": "*" or "cors": {
🧠 Observations
The failing request includes
_csrf, for example:/socket.io/?_csrf=...&EIO=4&transport=pollingThis suggests either:
- CSRF validation failing
- Session cookie mismatch
- Header mismatch
- Cloudflare altering something in polling requests
But:
- Login works
- Normal requests work
- Only socket polling fails
Questions- Has anyone experienced 403 specifically on
transport=pollingbehind Cloudflare? - Is there anything specific in NodeBB cluster mode that could cause this?
- Could Cloudflare be interfering with long-polling specifically (even with WebSockets enabled)?
- Is there a recommended minimal known-good config for NodeBB + Cloudflare (Free) + cluster?
At this point I’m unsure whether:
- This is CSRF related
- This is Cloudflare related
- This is a subtle proxy/session issue
- Or something specific to polling transport
Any guidance or expert would be greatly appreciated.
Thanks in advance

One small issue I see is the name of the socket.io:origins config. It is
originsnotorigin. So in config.json it should be lie below. With that said you probably don't want to set it to*but just yourhttps://example.com:*your url in config.json."socket.io": { "origins": "*" }Did you try disabling cloudflare, does polling work when you do that?
-
Hi everyone, @baris
I’m currently facing persistent
xhr poll errorissues with NodeBB behind Cloudflare (Free plan, proxied / orange cloud). I’ve been debugging this for quite a while and would really appreciate some expert input.
The ProblemIn the browser console I consistently get:
[socket.io] Connection error: xhr poll errorWith error i nnodebb :

Network tab shows:/socket.io/?_csrf=...&EIO=4&transport=polling → 403
So the failure happens during the polling transport phase, before WebSocket upgrade. I guess
Login works.
Sessions work.
Forum loads.
But sockets keep failing with 403 with error connexion in nodebb interface
🧭 Infrastructure Overview
Server
- 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
- Same ports open in the server with firewalld/virtualmin
- Managed via Virtualmin
- Nginx reverse proxy
- Let’s Encrypt SSL
- Ubuntu Server
NodeBB Setup
- Latest stable NodeBB 4.9.1
- Node.js LTS 18
- MongoDB
- Redis enabled
- Cluster mode enabled scaling
Here my config.json:``` { "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 } } ```Here my vhost nginx :
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 }Nginx has been reloaded.
NodeBB restarted multiple times without "origin": "*" or "cors": {
️ Cloudflare ConfigurationPlan: Free
Status: Proxied (orange cloud)SSL/TLS
- Mode: Full (Strict) with Let's encrypt SSL on the web servers with certbot
- WebSockets: ON
Cache Rules
-
Rules created:
- If URI Path contains
/socket.io/ - Then: Bypass cache
- If URI Path contains
WAF
- Custom ignore rule for
/socket.io/* - Custom ignore rule for
/api/*
No rate limiting.
--> Issue persists.
🧪 What Has Been Tested
- Verified Redis connectivity
- Verified cluster processes running
- Verified cluster port in netstats
- Confirmed
trust proxy: true - Confirmed
X-Forwarded-Protois set - Cleared all caches
- Restarted everything multiple times
- test without "origin": "*" or "cors": {
🧠 Observations
The failing request includes
_csrf, for example:/socket.io/?_csrf=...&EIO=4&transport=pollingThis suggests either:
- CSRF validation failing
- Session cookie mismatch
- Header mismatch
- Cloudflare altering something in polling requests
But:
- Login works
- Normal requests work
- Only socket polling fails
Questions- Has anyone experienced 403 specifically on
transport=pollingbehind Cloudflare? - Is there anything specific in NodeBB cluster mode that could cause this?
- Could Cloudflare be interfering with long-polling specifically (even with WebSockets enabled)?
- Is there a recommended minimal known-good config for NodeBB + Cloudflare (Free) + cluster?
At this point I’m unsure whether:
- This is CSRF related
- This is Cloudflare related
- This is a subtle proxy/session issue
- Or something specific to polling transport
Any guidance or expert would be greatly appreciated.
Thanks in advance

-
Hi everyone, @baris
I’m currently facing persistent
xhr poll errorissues with NodeBB behind Cloudflare (Free plan, proxied / orange cloud). I’ve been debugging this for quite a while and would really appreciate some expert input.
The ProblemIn the browser console I consistently get:
[socket.io] Connection error: xhr poll errorWith error i nnodebb :

Network tab shows:/socket.io/?_csrf=...&EIO=4&transport=polling → 403
So the failure happens during the polling transport phase, before WebSocket upgrade. I guess
Login works.
Sessions work.
Forum loads.
But sockets keep failing with 403 with error connexion in nodebb interface
🧭 Infrastructure Overview
Server
- 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
- Same ports open in the server with firewalld/virtualmin
- Managed via Virtualmin
- Nginx reverse proxy
- Let’s Encrypt SSL
- Ubuntu Server
NodeBB Setup
- Latest stable NodeBB 4.9.1
- Node.js LTS 18
- MongoDB
- Redis enabled
- Cluster mode enabled scaling
Here my config.json:``` { "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 } } ```Here my vhost nginx :
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 }Nginx has been reloaded.
NodeBB restarted multiple times without "origin": "*" or "cors": {
️ Cloudflare ConfigurationPlan: Free
Status: Proxied (orange cloud)SSL/TLS
- Mode: Full (Strict) with Let's encrypt SSL on the web servers with certbot
- WebSockets: ON
Cache Rules
-
Rules created:
- If URI Path contains
/socket.io/ - Then: Bypass cache
- If URI Path contains
WAF
- Custom ignore rule for
/socket.io/* - Custom ignore rule for
/api/*
No rate limiting.
--> Issue persists.
🧪 What Has Been Tested
- Verified Redis connectivity
- Verified cluster processes running
- Verified cluster port in netstats
- Confirmed
trust proxy: true - Confirmed
X-Forwarded-Protois set - Cleared all caches
- Restarted everything multiple times
- test without "origin": "*" or "cors": {
🧠 Observations
The failing request includes
_csrf, for example:/socket.io/?_csrf=...&EIO=4&transport=pollingThis suggests either:
- CSRF validation failing
- Session cookie mismatch
- Header mismatch
- Cloudflare altering something in polling requests
But:
- Login works
- Normal requests work
- Only socket polling fails
Questions- Has anyone experienced 403 specifically on
transport=pollingbehind Cloudflare? - Is there anything specific in NodeBB cluster mode that could cause this?
- Could Cloudflare be interfering with long-polling specifically (even with WebSockets enabled)?
- Is there a recommended minimal known-good config for NodeBB + Cloudflare (Free) + cluster?
At this point I’m unsure whether:
- This is CSRF related
- This is Cloudflare related
- This is a subtle proxy/session issue
- Or something specific to polling transport
Any guidance or expert would be greatly appreciated.
Thanks in advance

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
"url": "https://myforum.comit will be set tohttps://myforum.com:* -
Hi everyone, @baris
I’m currently facing persistent
xhr poll errorissues with NodeBB behind Cloudflare (Free plan, proxied / orange cloud). I’ve been debugging this for quite a while and would really appreciate some expert input.
The ProblemIn the browser console I consistently get:
[socket.io] Connection error: xhr poll errorWith error i nnodebb :

Network tab shows:/socket.io/?_csrf=...&EIO=4&transport=polling → 403
So the failure happens during the polling transport phase, before WebSocket upgrade. I guess
Login works.
Sessions work.
Forum loads.
But sockets keep failing with 403 with error connexion in nodebb interface
🧭 Infrastructure Overview
Server
- 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
- Same ports open in the server with firewalld/virtualmin
- Managed via Virtualmin
- Nginx reverse proxy
- Let’s Encrypt SSL
- Ubuntu Server
NodeBB Setup
- Latest stable NodeBB 4.9.1
- Node.js LTS 18
- MongoDB
- Redis enabled
- Cluster mode enabled scaling
Here my config.json:``` { "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 } } ```Here my vhost nginx :
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 }Nginx has been reloaded.
NodeBB restarted multiple times without "origin": "*" or "cors": {
️ Cloudflare ConfigurationPlan: Free
Status: Proxied (orange cloud)SSL/TLS
- Mode: Full (Strict) with Let's encrypt SSL on the web servers with certbot
- WebSockets: ON
Cache Rules
-
Rules created:
- If URI Path contains
/socket.io/ - Then: Bypass cache
- If URI Path contains
WAF
- Custom ignore rule for
/socket.io/* - Custom ignore rule for
/api/*
No rate limiting.
--> Issue persists.
🧪 What Has Been Tested
- Verified Redis connectivity
- Verified cluster processes running
- Verified cluster port in netstats
- Confirmed
trust proxy: true - Confirmed
X-Forwarded-Protois set - Cleared all caches
- Restarted everything multiple times
- test without "origin": "*" or "cors": {
🧠 Observations
The failing request includes
_csrf, for example:/socket.io/?_csrf=...&EIO=4&transport=pollingThis suggests either:
- CSRF validation failing
- Session cookie mismatch
- Header mismatch
- Cloudflare altering something in polling requests
But:
- Login works
- Normal requests work
- Only socket polling fails
Questions- Has anyone experienced 403 specifically on
transport=pollingbehind Cloudflare? - Is there anything specific in NodeBB cluster mode that could cause this?
- Could Cloudflare be interfering with long-polling specifically (even with WebSockets enabled)?
- Is there a recommended minimal known-good config for NodeBB + Cloudflare (Free) + cluster?
At this point I’m unsure whether:
- This is CSRF related
- This is Cloudflare related
- This is a subtle proxy/session issue
- Or something specific to polling transport
Any guidance or expert would be greatly appreciated.
Thanks in advance

IMPORTANT: seems to be good, but must have WAF rules and page rules on Cloud Flare for websocket.io
--> I can share if someone is interrested.
-
Hi everyone, @baris
I’m currently facing persistent
xhr poll errorissues with NodeBB behind Cloudflare (Free plan, proxied / orange cloud). I’ve been debugging this for quite a while and would really appreciate some expert input.
The ProblemIn the browser console I consistently get:
[socket.io] Connection error: xhr poll errorWith error i nnodebb :

Network tab shows:/socket.io/?_csrf=...&EIO=4&transport=polling → 403
So the failure happens during the polling transport phase, before WebSocket upgrade. I guess
Login works.
Sessions work.
Forum loads.
But sockets keep failing with 403 with error connexion in nodebb interface
🧭 Infrastructure Overview
Server
- 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
- Same ports open in the server with firewalld/virtualmin
- Managed via Virtualmin
- Nginx reverse proxy
- Let’s Encrypt SSL
- Ubuntu Server
NodeBB Setup
- Latest stable NodeBB 4.9.1
- Node.js LTS 18
- MongoDB
- Redis enabled
- Cluster mode enabled scaling
Here my config.json:``` { "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 } } ```Here my vhost nginx :
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 }Nginx has been reloaded.
NodeBB restarted multiple times without "origin": "*" or "cors": {
️ Cloudflare ConfigurationPlan: Free
Status: Proxied (orange cloud)SSL/TLS
- Mode: Full (Strict) with Let's encrypt SSL on the web servers with certbot
- WebSockets: ON
Cache Rules
-
Rules created:
- If URI Path contains
/socket.io/ - Then: Bypass cache
- If URI Path contains
WAF
- Custom ignore rule for
/socket.io/* - Custom ignore rule for
/api/*
No rate limiting.
--> Issue persists.
🧪 What Has Been Tested
- Verified Redis connectivity
- Verified cluster processes running
- Verified cluster port in netstats
- Confirmed
trust proxy: true - Confirmed
X-Forwarded-Protois set - Cleared all caches
- Restarted everything multiple times
- test without "origin": "*" or "cors": {
🧠 Observations
The failing request includes
_csrf, for example:/socket.io/?_csrf=...&EIO=4&transport=pollingThis suggests either:
- CSRF validation failing
- Session cookie mismatch
- Header mismatch
- Cloudflare altering something in polling requests
But:
- Login works
- Normal requests work
- Only socket polling fails
Questions- Has anyone experienced 403 specifically on
transport=pollingbehind Cloudflare? - Is there anything specific in NodeBB cluster mode that could cause this?
- Could Cloudflare be interfering with long-polling specifically (even with WebSockets enabled)?
- Is there a recommended minimal known-good config for NodeBB + Cloudflare (Free) + cluster?
At this point I’m unsure whether:
- This is CSRF related
- This is Cloudflare related
- This is a subtle proxy/session issue
- Or something specific to polling transport
Any guidance or expert would be greatly appreciated.
Thanks in advance

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 socket.io connections even though everything was OK on Cloudflare's end: WAF rules & Cache rules to ignore socket.io, visible in the browser console:
GET https://xxxxxx/socket.io/?_csrf=&EIO=4&transport=websocket&sid=xxx HTTP/1.1 400 POST https://xxxxxxx/socket.io/?_csrf=&EIO=4&transport=polling&sid=xxx HTTP/1.1 400The forum was still accessible and functional, but real-time features were degraded and the forum felt noticeably slower.
After investigation, I found the root cause: my nginx upstream was configured with
ip_hash(as recommended in the NodeBB documentation), but this does not work correctly when Cloudflare is sitting in front of the server.The issue is that Cloudflare uses multiple outgoing IPs for the same end user. This means that the initial socket.io 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.
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.The fix was to replace
ip_hashwith a hash based on the real client IP passed by Cloudflare in the X-Forwarded-For header: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; }After applying this change and reloading nginx, the 400 errors dropped significantly.
My questions are:
- Is this the recommended approach for NodeBB clusters running behind Cloudflare?
- Are there any known caveats with using $http_x_forwarded_for consistent instead of ip_hash in this context?
- Should the NodeBB nginx documentation mention this as an alternative for Cloudflare users?
Thanks in advance.
-
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 socket.io connections even though everything was OK on Cloudflare's end: WAF rules & Cache rules to ignore socket.io, visible in the browser console:
GET https://xxxxxx/socket.io/?_csrf=&EIO=4&transport=websocket&sid=xxx HTTP/1.1 400 POST https://xxxxxxx/socket.io/?_csrf=&EIO=4&transport=polling&sid=xxx HTTP/1.1 400The forum was still accessible and functional, but real-time features were degraded and the forum felt noticeably slower.
After investigation, I found the root cause: my nginx upstream was configured with
ip_hash(as recommended in the NodeBB documentation), but this does not work correctly when Cloudflare is sitting in front of the server.The issue is that Cloudflare uses multiple outgoing IPs for the same end user. This means that the initial socket.io 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.
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.The fix was to replace
ip_hashwith a hash based on the real client IP passed by Cloudflare in the X-Forwarded-For header: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; }After applying this change and reloading nginx, the 400 errors dropped significantly.
My questions are:
- Is this the recommended approach for NodeBB clusters running behind Cloudflare?
- Are there any known caveats with using $http_x_forwarded_for consistent instead of ip_hash in this context?
- Should the NodeBB nginx documentation mention this as an alternative for Cloudflare users?
Thanks in advance.
- No objections to this approach
- No, no known contraindications
- Yes, probably

Hello! It looks like you're interested in this conversation, but you don't have an account yet.
Getting fed up of having to scroll through the same posts each visit? When you register for an account, you'll always come back to exactly where you were before, and choose to be notified of new replies (either via email, or push notification). You'll also be able to save bookmarks and upvote posts to show your appreciation to other community members.
With your input, this post could be even better 💗
Register Login