<?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[Tutorial : NodeBB Scaling-clustering Redis + Proxied Cloudflare Free plan : integration guide]]></title><description><![CDATA[<p dir="auto">Also posted here : <a href="https://community.nodebb.org/topic/19292/nodebb-scaling-clustering-redis-proxied-cloudflare-free-plan-integration-guide" target="_blank" rel="noopener noreferrer nofollow ugc">https://community.nodebb.org/topic/19292/nodebb-scaling-clustering-redis-proxied-cloudflare-free-plan-integration-guide</a></p>
<hr />
<p dir="auto">Hello,</p>
<p dir="auto">This guide covers a modern setup (2026+) running NodeBB in cluster mode with Redis behind nginx and Cloudflare’s free plan in proxied mode. <a href="https://community.nodebb.org/topic/7930/using-cloudflare-with-nodebb" target="_blank" rel="noopener noreferrer nofollow ugc">The documentation here</a> is over 10 years old and don’t account for cluster mode, Redis pub/sub, or how Cloudflare’s proxied mode interacts with <code>socket.io</code> session stickiness. This guide fills that gap.</p>
<p dir="auto">Also, the official NodeBB nginx documentation will cause silent <code>socket.io</code> failures without the fixes described here.<br />
– See here : <a href="https://community.nodebb.org/topic/19225/websocket-socket.io-403-xhr-poll-error-behind-cloudflare-cluster-scaling-redis/5" target="_blank" rel="noopener noreferrer nofollow ugc">https://community.nodebb.org/topic/19225/websocket-socket.io-403-xhr-poll-error-behind-cloudflare-cluster-scaling-redis/5</a></p>
<blockquote>
<p dir="auto"><strong>Prerequisites:</strong></p>
</blockquote>
<ul>
<li>NodeBB multi-process cluster with Redis pub/sub running, nginx configured as reverse proxy.<br />
– See official documentation here : <a href="https://docs.nodebb.org/configuring/scaling/" target="_blank" rel="noopener noreferrer nofollow ugc">https://docs.nodebb.org/configuring/scaling/</a></li>
<li>Cloudflare in <strong>proxied mode</strong> (orange cloud) pointing to your server IP.</li>
<li>A valid SSL certificate installed on your server (covered in step 1 below)</li>
</ul>
<hr />
<h2>Why the official NodeBB nginx doc breaks with Cloudflare</h2>
<p dir="auto">The official docs recommend <code>ip_hash</code> for session stickiness. This silently breaks behind Cloudflare because Cloudflare uses <strong>multiple outgoing IPs for the same end user</strong>. The <code>socket.io</code> polling request and the WebSocket upgrade can arrive from two different CF IPs, routing them to different cluster nodes. The second node has no session → <code>400 Bad Request</code>.</p>
<hr />
<h2>Step 1 : SSL: Let’s Encrypt certificate + Cloudflare Full (Strict) mode</h2>
<h3>Why Full (Strict) and not just Full or Flexible?</h3>
<p dir="auto">Cloudflare offers four SSL modes:</p>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>Mode</th>
<th>Visitor → CF</th>
<th>CF → your server</th>
<th>Risk</th>
</tr>
</thead>
<tbody>
<tr>
<td>Off</td>
<td>HTTP</td>
<td>HTTP</td>
<td>Everything in plaintext</td>
</tr>
<tr>
<td>Flexible</td>
<td>HTTPS</td>
<td>HTTP</td>
<td>CF to server is unencrypted — <strong>never use this</strong></td>
</tr>
<tr>
<td>Full</td>
<td>HTTPS</td>
<td>HTTPS</td>
<td>Accepts any cert, including self-signed — <strong>not validated</strong></td>
</tr>
<tr>
<td><strong>Full (Strict)</strong></td>
<td><strong>HTTPS</strong></td>
<td><strong>HTTPS</strong></td>
<td><strong>Requires a valid, trusted cert — recommended</strong></td>
</tr>
</tbody>
</table>
<p dir="auto"><strong>Full (Strict)</strong> is the only mode that validates the certificate on your server. Without it, Cloudflare will happily connect to your server over HTTPS with an expired or self-signed cert, which provides no real security for the CF → origin leg.</p>
<h3>Install Certbot and obtain a certificate</h3>
<pre><code class="bash"># Install Certbot with the nginx plugin
sudo apt install certbot python3-certbot-nginx -y

# Obtain a certificate for your domain
# (nginx must already be running and port 80 reachable from the internet)
sudo certbot --nginx -d your-domain.com -d www.your-domain.com
</code></pre>
<p dir="auto">Certbot will automatically edit your nginx vhost to add the SSL configuration and set up an HTTP → HTTPS redirect.</p>
<blockquote>
<p dir="auto">If Cloudflare is already in proxied mode (orange cloud), Certbot’s HTTP-01 challenge still works because Cloudflare forwards HTTP traffic to your server. No need to temporarily disable the proxy.</p>
</blockquote>
<h3>Verify auto-renewal</h3>
<pre><code class="bash"># Test the renewal process (dry run — no cert is actually renewed)
sudo certbot renew --dry-run
</code></pre>
<p dir="auto">Certbot installs a systemd timer automatically. Certificates are renewed 30 days before expiry.</p>
<h3>Configure Cloudflare SSL mode</h3>
<p dir="auto">In your Cloudflare dashboard, go to <strong>SSL/TLS → Overview</strong> and select <strong>Full (Strict)</strong>.</p>
<p dir="auto">Also enable these recommended options under <strong>SSL/TLS → Edge Certificates</strong>:</p>
<ul>
<li><strong>Always Use HTTPS</strong> → On</li>
<li><strong>Minimum TLS Version</strong> → TLS 1.2</li>
<li><strong>Opportunistic Encryption</strong> → On</li>
<li><strong>TLS 1.3</strong> → On</li>
<li><strong>Automatic HTTPS rewrites</strong> → On</li>
</ul>
<h3>Update your nginx vhost to listen on 443</h3>
<p dir="auto">After Certbot runs, your vhost should contain (Certbot adds this automatically):</p>
<pre><code class="nginx">server {
    listen 443 ssl;
    server_name your-domain.com;

    ssl_certificate     /etc/letsencrypt/live/your-domain.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/your-domain.com/privkey.pem;
    include             /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam         /etc/letsencrypt/ssl-dhparams.pem;

    # ... rest of your NodeBB proxy config
}

server {
    listen 80;
    server_name your-domain.com;
    return 301 https://$host$request_uri;
}
</code></pre>
<blockquote>
<p dir="auto">With Cloudflare in proxied mode, the redirect from port 80 to 443 in nginx is technically handled by Cloudflare before it reaches your server (via the “Always Use HTTPS” rule above). Keep it in nginx anyway as a safety net for direct server access.</p>
</blockquote>
<hr />
<h2>Step 2 : Nginx: trust Cloudflare’s real IP headers</h2>
<p dir="auto">By default nginx sees Cloudflare’s edge IPs, not your visitors’ IPs. You need to tell nginx which IP ranges to trust as a proxy, so <code>$http_x_forwarded_for</code> resolves correctly for the upstream hash in step 3.</p>
<p dir="auto">Add this in you nodebb nginx virtualhost configuration :</p>
<pre><code class="nginx"># Cloudflare IPv4 ranges (update periodically from https://www.cloudflare.com/ips-v4)
set_real_ip_from 173.245.48.0/20;
set_real_ip_from 103.21.244.0/22;
set_real_ip_from 103.22.200.0/22;
set_real_ip_from 103.31.4.0/22;
set_real_ip_from 141.101.64.0/18;
set_real_ip_from 108.162.192.0/18;
set_real_ip_from 190.93.240.0/20;
set_real_ip_from 188.114.96.0/20;
set_real_ip_from 197.234.240.0/22;
set_real_ip_from 198.41.128.0/17;
set_real_ip_from 162.158.0.0/15;
set_real_ip_from 104.16.0.0/13;
set_real_ip_from 104.24.0.0/14;
set_real_ip_from 172.64.0.0/13;
set_real_ip_from 131.0.72.0/22;

# Cloudflare IPv6 ranges
set_real_ip_from 2400:cb00::/32;
set_real_ip_from 2606:4700::/32;
set_real_ip_from 2803:f800::/32;
set_real_ip_from 2405:b500::/32;
set_real_ip_from 2405:8100::/32;
set_real_ip_from 2a06:98c0::/29;
set_real_ip_from 2c0f:f248::/32;

real_ip_header CF-Connecting-IP;
</code></pre>
<blockquote>
<p dir="auto">Using <code>CF-Connecting-IP</code> instead of <code>X-Forwarded-For</code> is safer - Cloudflare guarantees this header contains exactly the real visitor IP with no spoofing risk.</p>
</blockquote>
<hr />
<h2>Step 3 : Nginx: fix upstream session stickiness</h2>
<p dir="auto"><strong>The problem:</strong></p>
<pre><code>User → CF IP 1 → ip_hash → :4567  ✓ (session created)
User → CF IP 2 → ip_hash → :4568  ✗ (400 — no session!)
</code></pre>
<p dir="auto"><strong>The fix:</strong></p>
<pre><code>User real IP → consistent hash → always :4567  ✓
</code></pre>
<p dir="auto">In your NodeBB nginx vhost, update the upstream block like this :</p>
<pre><code class="nginx">upstream io_nodes {
    # BEFORE (breaks with Cloudflare):
    # ip_hash;

    # AFTER — hash on the real visitor IP forwarded by Cloudflare
    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">Make sure WebSocket proxying is present in your <code>location /socket.io/</code> block:</p>
<pre><code class="nginx">location /socket.io/ {
    proxy_pass            http://io_nodes/socket.io/;
    proxy_redirect        off;
    proxy_http_version    1.1;
    proxy_set_header      Upgrade            $http_upgrade;
    proxy_set_header      Connection         "upgrade";
    proxy_set_header      Host               $host;
    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;
}
</code></pre>
<hr />
<h2>Step 4 : NodeBB config.json: origins &amp; trust proxy</h2>
<p dir="auto">Two settings are required: tell NodeBB to trust the proxy headers, and restrict <code>socket.io</code> to your domain to prevent 403 origin errors.</p>
<pre><code class="json">{
    "url": "https://your-domain.com",

    "socket.io": {
        "origins": "https://your-domain.com:*"
    },

    "trust proxy": true,

    "secret": "...",
    "database": "mongo",
    "port": [4567, 4568, 4569],
    "redis": { }
}
</code></pre>
<blockquote>
<p dir="auto">Without <code>"trust proxy": true</code>, NodeBB ignores <code>X-Forwarded-Proto</code> and may treat requests as HTTP even when the user is on HTTPS, causing CSRF and cookie issues behind Cloudflare.</p>
</blockquote>
<hr />
<h2>Step 5 : Cloudflare: WAF rules (bypass <code>socket.io</code> &amp; <span class="glossary-wrapper" title="application programming interface" data-bs-toggle="tooltip" data-bs-placement="top"><span class="glossary-word">API</span></span>)</h2>
<p dir="auto">Cloudflare’s WAF can interfere with <code>socket.io's</code> long-polling and your <span class="glossary-wrapper" title="application programming interface" data-bs-toggle="tooltip" data-bs-placement="top"><span class="glossary-word">API</span></span> endpoints. Create a WAF <strong>skip rule</strong> to bypass managed rules for these paths.</p>
<p dir="auto">Go to <strong>Security → WAF → Custom rules</strong> and create a rule with this expression:</p>
<pre><code>(http.request.uri.path contains "/socket.io")
or (http.request.uri.path contains "/api/")
</code></pre>
<p dir="auto">Set the action to <strong>Skip → All remaining custom rules</strong> (and optionally also skip managed rulesets). Name it something like <code>NodeBB - bypass </code>socket.io<code> and <span class="glossary-wrapper" title="application programming interface" data-bs-toggle="tooltip" data-bs-placement="top"><span class="glossary-word">API</span></span></code>.</p>
<blockquote>
<p dir="auto">On the free Cloudflare plan you get 5 custom WAF rules.</p>
</blockquote>
<p dir="auto">This single rule covers both <code>socket.io</code> and <span class="glossary-wrapper" title="application programming interface" data-bs-toggle="tooltip" data-bs-placement="top"><span class="glossary-word">API</span></span>.</p>
<hr />
<h2>Step 6 : Cloudflare: Cache rules (bypass <code>socket.io</code>)</h2>
<p dir="auto">Cloudflare must never cache <code>socket.io</code> traffic. Go to <strong>Caching → Cache Rules</strong> and create a rule with this expression:</p>
<pre><code>(http.request.uri.path contains "/socket.io/")
or (http.request.uri.path contains "/api/")
</code></pre>
<p dir="auto">Set the cache status to <strong>Bypass</strong>.</p>
<blockquote>
<p dir="auto">Optional: add a second cache rule to <strong>Cache Everything</strong> for <code>/assets/</code> and <code>/uploads/</code> paths to offload static files to Cloudflare’s <span class="glossary-wrapper" title="content delivery network" data-bs-toggle="tooltip" data-bs-placement="top"><span class="glossary-word">CDN</span></span> and reduce load on your server.</p>
</blockquote>
<hr />
<h2>Step 7 : Reload &amp; verify</h2>
<pre><code class="bash"># Test nginx config first
sudo nginx -t

# If OK, reload (zero downtime)
sudo systemctl reload nginx

# Restart NodeBB cluster
cd /path/to/nodebb
./nodebb restart
</code></pre>
<p dir="auto">Open your forum in a browser, open DevTools → Network, filter by <code>socket.io</code>. You should see:</p>
<pre><code>GET /socket.io/?transport=polling    → 200
GET /socket.io/?transport=websocket  → 101 Switching Protocols
</code></pre>
<p dir="auto">All <code>socket.io</code> <code>400</code> errors should be gone. If you still see occasional 400s during a node restart, they are expected and transient - <code>socket.io</code> will reconnect automatically.</p>
<hr />
<h2>Summary checklist</h2>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>What</th>
<th>Where</th>
</tr>
</thead>
<tbody>
<tr>
<td>Scaling-Clustering Redis</td>
<td>server</td>
</tr>
<tr>
<td>Let’s Encrypt certificate via Certbot</td>
<td>server</td>
</tr>
<tr>
<td>SSL mode set to <strong>Full (Strict)</strong></td>
<td>Cloudflare dashboard</td>
</tr>
<tr>
<td>Always Use HTTPS + TLS 1.2 minimum</td>
<td>Cloudflare dashboard</td>
</tr>
<tr>
<td>Nginx real IP from Cloudflare ranges</td>
<td><code>/etc/nginx/conf.d/cloudflare-realip.conf</code></td>
</tr>
<tr>
<td>Upstream: <code>ip_hash</code> → <code>hash $http_x_forwarded_for consistent</code></td>
<td>nginx vhost</td>
</tr>
<tr>
<td><code>socket.io</code> origins + <code>trust proxy</code> in config.json</td>
<td>NodeBB</td>
</tr>
<tr>
<td>WAF skip rule for <code>/socket.io</code> and <code>/api/</code></td>
<td>Cloudflare dashboard</td>
</tr>
<tr>
<td>Cache bypass rule for <code>/socket.io</code> and <code>/api/</code></td>
<td>Cloudflare dashboard</td>
</tr>
</tbody>
</table>
]]></description><link>https://sudonix.org/topic/738/tutorial-nodebb-scaling-clustering-redis-proxied-cloudflare-free-plan-integration-guide</link><generator>RSS for Node</generator><lastBuildDate>Wed, 22 Apr 2026 13:50:00 GMT</lastBuildDate><atom:link href="https://sudonix.org/topic/738.rss" rel="self" type="application/rss+xml"/><pubDate>Tue, 21 Apr 2026 16:12:51 GMT</pubDate><ttl>60</ttl><item><title><![CDATA[Reply to Tutorial : NodeBB Scaling-clustering Redis + Proxied Cloudflare Free plan : integration guide on Tue, 21 Apr 2026 19:22:03 GMT]]></title><description><![CDATA[<p dir="auto"><a class="plugin-mentions-user plugin-mentions-a" href="/user/downpw" aria-label="Profile: downpw">@<bdi>downpw</bdi></a> this is a great walk through which I’m certain others will find extremely useful.</p>
<p dir="auto">Thanks</p>
]]></description><link>https://sudonix.org/post/10529</link><guid isPermaLink="true">https://sudonix.org/post/10529</guid><dc:creator><![CDATA[phenomlab]]></dc:creator><pubDate>Tue, 21 Apr 2026 19:22:03 GMT</pubDate></item></channel></rss>