Skip to content

Tutorial : NodeBB Scaling-clustering Redis + Proxied Cloudflare Free plan : integration guide

Performance
2 2 26 1
  • Also posted here : https://community.nodebb.org/topic/19292/nodebb-scaling-clustering-redis-proxied-cloudflare-free-plan-integration-guide


    Hello,

    This guide covers a modern setup (2026+) running NodeBB in cluster mode with Redis behind nginx and Cloudflare’s free plan in proxied mode. The documentation here is over 10 years old and don’t account for cluster mode, Redis pub/sub, or how Cloudflare’s proxied mode interacts with socket.io session stickiness. This guide fills that gap.

    Also, the official NodeBB nginx documentation will cause silent socket.io failures without the fixes described here.
    – See here : https://community.nodebb.org/topic/19225/websocket-socket.io-403-xhr-poll-error-behind-cloudflare-cluster-scaling-redis/5

    Prerequisites:

    • NodeBB multi-process cluster with Redis pub/sub running, nginx configured as reverse proxy.
      – See official documentation here : https://docs.nodebb.org/configuring/scaling/
    • Cloudflare in proxied mode (orange cloud) pointing to your server IP.
    • A valid SSL certificate installed on your server (covered in step 1 below)

    Why the official NodeBB nginx doc breaks with Cloudflare

    The official docs recommend ip_hash for session stickiness. This silently breaks behind Cloudflare because Cloudflare uses multiple outgoing IPs for the same end user. The socket.io 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 → 400 Bad Request.


    Step 1 : SSL: Let’s Encrypt certificate + Cloudflare Full (Strict) mode

    Why Full (Strict) and not just Full or Flexible?

    Cloudflare offers four SSL modes:

    Mode Visitor → CF CF → your server Risk
    Off HTTP HTTP Everything in plaintext
    Flexible HTTPS HTTP CF to server is unencrypted — never use this
    Full HTTPS HTTPS Accepts any cert, including self-signed — not validated
    Full (Strict) HTTPS HTTPS Requires a valid, trusted cert — recommended

    Full (Strict) 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.

    Install Certbot and obtain a certificate

    # 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
    

    Certbot will automatically edit your nginx vhost to add the SSL configuration and set up an HTTP → HTTPS redirect.

    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.

    Verify auto-renewal

    # Test the renewal process (dry run — no cert is actually renewed)
    sudo certbot renew --dry-run
    

    Certbot installs a systemd timer automatically. Certificates are renewed 30 days before expiry.

    Configure Cloudflare SSL mode

    In your Cloudflare dashboard, go to SSL/TLS → Overview and select Full (Strict).

    Also enable these recommended options under SSL/TLS → Edge Certificates:

    • Always Use HTTPS → On
    • Minimum TLS Version → TLS 1.2
    • Opportunistic Encryption → On
    • TLS 1.3 → On
    • Automatic HTTPS rewrites → On

    Update your nginx vhost to listen on 443

    After Certbot runs, your vhost should contain (Certbot adds this automatically):

    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;
    }
    

    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.


    Step 2 : Nginx: trust Cloudflare’s real IP headers

    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 $http_x_forwarded_for resolves correctly for the upstream hash in step 3.

    Add this in you nodebb nginx virtualhost configuration :

    # 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;
    

    Using CF-Connecting-IP instead of X-Forwarded-For is safer - Cloudflare guarantees this header contains exactly the real visitor IP with no spoofing risk.


    Step 3 : Nginx: fix upstream session stickiness

    The problem:

    User → CF IP 1 → ip_hash → :4567  āœ“ (session created)
    User → CF IP 2 → ip_hash → :4568  āœ— (400 — no session!)
    

    The fix:

    User real IP → consistent hash → always :4567  āœ“
    

    In your NodeBB nginx vhost, update the upstream block like this :

    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;
    }
    

    Make sure WebSocket proxying is present in your location /socket.io/ block:

    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;
    }
    

    Step 4 : NodeBB config.json: origins & trust proxy

    Two settings are required: tell NodeBB to trust the proxy headers, and restrict socket.io to your domain to prevent 403 origin errors.

    {
        "url": "https://your-domain.com",
    
        "socket.io": {
            "origins": "https://your-domain.com:*"
        },
    
        "trust proxy": true,
    
        "secret": "...",
        "database": "mongo",
        "port": [4567, 4568, 4569],
        "redis": { }
    }
    

    Without "trust proxy": true, NodeBB ignores X-Forwarded-Proto and may treat requests as HTTP even when the user is on HTTPS, causing CSRF and cookie issues behind Cloudflare.


    Step 5 : Cloudflare: WAF rules (bypass socket.io & API)

    Cloudflare’s WAF can interfere with socket.io's long-polling and your API endpoints. Create a WAF skip rule to bypass managed rules for these paths.

    Go to Security → WAF → Custom rules and create a rule with this expression:

    (http.request.uri.path contains "/socket.io")
    or (http.request.uri.path contains "/api/")
    

    Set the action to Skip → All remaining custom rules (and optionally also skip managed rulesets). Name it something like NodeBB - bypass socket.io and API.

    On the free Cloudflare plan you get 5 custom WAF rules.

    This single rule covers both socket.io and API.


    Step 6 : Cloudflare: Cache rules (bypass socket.io)

    Cloudflare must never cache socket.io traffic. Go to Caching → Cache Rules and create a rule with this expression:

    (http.request.uri.path contains "/socket.io/")
    or (http.request.uri.path contains "/api/")
    

    Set the cache status to Bypass.

    Optional: add a second cache rule to Cache Everything for /assets/ and /uploads/ paths to offload static files to Cloudflare’s CDN and reduce load on your server.


    Step 7 : Reload & verify

    # 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
    

    Open your forum in a browser, open DevTools → Network, filter by socket.io. You should see:

    GET /socket.io/?transport=polling    → 200
    GET /socket.io/?transport=websocket  → 101 Switching Protocols
    

    All socket.io 400 errors should be gone. If you still see occasional 400s during a node restart, they are expected and transient - socket.io will reconnect automatically.


    Summary checklist

    What Where
    Scaling-Clustering Redis server
    Let’s Encrypt certificate via Certbot server
    SSL mode set to Full (Strict) Cloudflare dashboard
    Always Use HTTPS + TLS 1.2 minimum Cloudflare dashboard
    Nginx real IP from Cloudflare ranges /etc/nginx/conf.d/cloudflare-realip.conf
    Upstream: ip_hash → hash $http_x_forwarded_for consistent nginx vhost
    socket.io origins + trust proxy in config.json NodeBB
    WAF skip rule for /socket.io and /api/ Cloudflare dashboard
    Cache bypass rule for /socket.io and /api/ Cloudflare dashboard
  • DownPWundefined DownPW referenced this topic
  • DownPWundefined DownPW marked this topic as a regular topic
  • @downpw this is a great walk through which I’m certain others will find extremely useful.

    Thanks


Related Topics
  • Spam spam spam

    Solved Configure nodebb
    6
    2 Votes
    6 Posts
    952 Views
    @Panda said in Spam spam spam: ok, yes Ive seen the queue, it shows IP, but doesnt have a field for comments from registrant. It’s not designed for that. It merely serves as a gateway between posts appearing on your form or not. @Panda said in Spam spam spam: It would be better if nodebb had this plugin included in ACP list, as not only then do you know its approved and should work, but many people cant or dont want to use CLI on the server That’s a question for the NodeBB devs but in all honesty you can’t not use the CLI when installing nodebb so to be this isn’t a big deal.
  • NodeBB v3 Chat Very Slow

    Moved Performance nodebb v3 nodebb chat
    47
    11 Votes
    47 Posts
    12k Views
    @DownPW Seems fine.
  • build nodebb Warning in entrypoint size limit

    Solved Performance nodebb
    2
    0 Votes
    2 Posts
    770 Views
    @eeeee they are nothing to worry about, and can be ignored.
  • 1 Votes
    3 Posts
    849 Views
    @qwinter yes, I recently migrated this site to CF in full and noticed the same thing. Seems CF also has native socket support now under the free plan, so win/win. I’ve not noticed any degradation of service since moving so happy to stay put for the time being.
  • NodeBB slow after DB recovery

    Solved Performance performance nodebb
    1
    5 Votes
    1 Posts
    655 Views
    No one has replied
  • NodeBB templates

    Locked Chitchat themes templates nodebb
    12
    4 Votes
    12 Posts
    3k Views
    Placing this here for reference https://sudonix.com/topic/216/nodebb-js-script-css-theme-switcher Further information and posts can be found at this link
  • NodeBB Footer

    Solved Customisation footer nodebb
    10
    1 Votes
    10 Posts
    2k Views
    @phenomlab said in NodeBB Footer: @jac and you. Hope all is well and you recover quickly Thanks pal
  • NodeBB Design help

    Solved Customisation
    8
    3
    2 Votes
    8 Posts
    2k Views
    @riekmedia I’ve applied some new CSS to your site. Can you reload the page and try again ? For the record, this is what I added #footer { background: #2d343e; border-top: 4px solid #2d343e; font-size: 0.9em; margin-top: 70px; padding: 80px 0 0; position: relative; clear: both; bottom: 0; left: 0; right: 0; z-index: 1000; margin-left: -15px; margin-right: -338px; } The /categories page seems a bit messed up, so looking at that currently EDIT - issued some override CSS in the CATEGORIES widget <!--- CSS fix for overspill on /categories page - DO NOT DELETE --> <style> #footer { margin-right: -45px; } </style> That should resolve the /categories issue.