NodeBB sits between Flarum and Discourse. It is a Node.js application using Socket.IO for real-time updates, it runs on your choice of MongoDB, Redis, or PostgreSQL, and it ships with a genuinely useful set of bundled plugins including 2FA, spam filtering, and Markdown composition. It also has a native ActivityPub implementation, which means forum categories can federate with Mastodon, Lemmy, and other Fediverse platforms without a third-party plugin. That is a real differentiator if it matters to your community.
This guide deploys NodeBB 4.x on a RamNode KVM VPS with MongoDB, nginx as a reverse proxy with WebSocket support, Let's Encrypt, a systemd unit, an external SMTP relay, and a backup process.
1. Choose the right RamNode instance
NodeBB's resident footprint is modest once running. The pain point is npm install, which is memory-hungry and will get OOM-killed on an undersized box without swap.
| Community size | RAM | vCPU | Disk |
|---|---|---|---|
| Small forum, under 100 active users | 1 GB + swap | 1 | 25 GB |
| Under 500 active users | 2 GB | 2 | 40 GB |
| Larger, or clustered | 4 GB+ | 4 | 60 GB+ |
Notes for RamNode specifically:
- KVM, Ubuntu 24.04 LTS x86_64. MongoDB needs a real kernel and AVX support on modern versions. RamNode's KVM plans are fine here.
- Swap is not optional on 1 GB. NodeBB's own docs say installing dependencies can need more than 512 MB.
- Location matters more than usual. NodeBB's whole value proposition is real-time. Pick the RamNode location closest to your users.
2. Database choice
NodeBB supports three backends through a database abstraction layer. They are not equivalent for your purposes.
| Backend | When to pick it |
|---|---|
| MongoDB | The default and the best-documented path. Pick this unless you have a specific reason not to. |
| Redis | Fastest, but everything lives in memory. On a 1 GB RamNode plan this is a bad trade. Required if you want clustering. |
| PostgreSQL | Fine if you already run Postgres and want one less daemon. Less community mileage than Mongo. |
This guide uses MongoDB 8.0. Note that Redis is also required if you ever want horizontal clustering, since it doubles as the session store in that configuration. That is out of scope for a single-node RamNode deployment.
3. Prerequisites
- A domain or subdomain with an A record on your RamNode IPv4, propagated before you request certificates.
- An external SMTP relay.
- SSH access as a sudo user.
The SMTP situation on RamNode
RamNode does not permit mail services on its VPS platform, and outbound port 25 is not a path available to you. NodeBB needs email for registration confirmation, password resets, and digests. Configure it against a transactional provider over 587 or 465: Postmark, SendGrid, Mailgun, Amazon SES, Brevo, or similar.
NodeBB will install and run without email configured. It will also quietly fail to confirm any account. Set it up before you invite anyone, and configure SPF, DKIM, and DMARC at your DNS provider.
4. Base server preparation
apt update && apt upgrade -y
hostnamectl set-hostname forum.example.com
timedatectl set-timezone UTCReboot if the kernel changed.
Create a dedicated unprivileged user. Do not run NodeBB as root.
adduser --system --group --home /opt/nodebb --shell /bin/bash nodebbSwap
fallocate -l 2G /swapfile
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
echo '/swapfile none swap sw 0 0' >> /etc/fstab
echo 'vm.swappiness=10' >> /etc/sysctl.d/99-nodebb.conf
sysctl --system
free -hFirewall
NodeBB listens on 4567 behind nginx. That port must never be exposed.
apt install -y ufw
ufw allow 22/tcp
ufw allow 80/tcp
ufw allow 443/tcp
ufw --force enable
ufw status verboseDeliberately absent: 4567 and 27017. If you can reach either from outside, something is wrong.
Automatic security updates and fail2ban
apt install -y unattended-upgrades fail2ban
dpkg-reconfigure -plow unattended-upgrades
systemctl enable --now fail2ban5. Install Node.js 22
NodeBB 4.11 and later require Node.js 22 or greater. Node 20 is at or near end of life and NodeBB has moved its floor. Install from NodeSource rather than Ubuntu's repo, which lags.
apt install -y ca-certificates curl gnupg git build-essential
curl -fsSL https://deb.nodesource.com/setup_lts.x -o /tmp/nodesource_setup.sh
less /tmp/nodesource_setup.sh
bash /tmp/nodesource_setup.sh
apt install -y nodejs
node -v # expect v22.x or higher
npm -vbuild-essential is there because a few native modules compile during install. Leaving it out produces a confusing wall of node-gyp errors.
6. Install and secure MongoDB
Add the MongoDB 8.0 repository:
curl -fsSL https://www.mongodb.org/static/pgp/server-8.0.asc | \
gpg --dearmor -o /usr/share/keyrings/mongodb-server-8.0.gpg
echo "deb [ arch=amd64,arm64 signed-by=/usr/share/keyrings/mongodb-server-8.0.gpg ] https://repo.mongodb.org/apt/ubuntu noble/mongodb-org/8.0 multiverse" \
> /etc/apt/sources.list.d/mongodb-org-8.0.list
apt update
apt install -y mongodb-org
systemctl enable --now mongod
systemctl status mongodCreate the database and users
mongoshuse admin
db.createUser({
user: "admin",
pwd: "a-long-random-admin-password",
roles: [ { role: "root", db: "admin" } ]
})
use nodebb
db.createUser({
user: "nodebb",
pwd: "a-long-random-nodebb-password",
roles: [
{ role: "readWrite", db: "nodebb" },
{ role: "clusterMonitor", db: "admin" }
]
})
quit()The clusterMonitor role is what lets NodeBB show database statistics in the admin control panel. It is read-only. Skip it if you would rather not grant it, and accept that the ACP database page will be blank.
Enable authentication and bind locally
Edit /etc/mongod.conf:
net:
port: 27017
bindIp: 127.0.0.1
security:
authorization: enabledsystemctl restart mongod
ss -lntp | grep 27017Confirm the bind is on 127.0.0.1 only. An internet-reachable Mongo with weak auth is a well-known way to lose a database.
Test:
mongosh "mongodb://127.0.0.1:27017/nodebb" --username nodebb --authenticationDatabase nodebb7. Install NodeBB
Clone the versioned branch. Only versioned branches are stable; do not deploy from develop or master.
mkdir -p /opt/nodebb
chown nodebb:nodebb /opt/nodebb
sudo -u nodebb -H bash
cd /opt/nodebb
git clone -b v4.x https://github.com/NodeBB/NodeBB.git .Run the setup CLI. NodeBB does not start with npm start; it uses its own CLI wrapper.
./nodebb setupYou will be prompted. The answers that matter:
URL used to access this NodeBB https://forum.example.com
Please enter a NodeBB secret (accept the generated default)
Which database to use mongo
MongoDB connection URI (leave blank)
Host IP or address of your MongoDB instance 127.0.0.1
Host port of your MongoDB instance 27017
MongoDB username nodebb
Password of your MongoDB database <your nodebb password>
MongoDB database name nodebbGet the URL exactly right. If your forum will be served at https://forum.example.com, enter exactly that, with the scheme and no trailing slash. A wrong value here produces broken asset paths and a forum that half-works in ways that are tedious to diagnose. You can fix it later in config.json, but fix it properly rather than working around it.
Setup then prompts for the administrator account. That is your forum login, separate from the database credentials.
The result is /opt/nodebb/config.json. It holds your database credentials, so:
chmod 600 /opt/nodebb/config.jsonExit back to your sudo user.
8. systemd unit
NodeBB has a built-in ./nodebb start daemon mode. Use systemd instead. It gives you proper restart behavior, log integration, and boot ordering.
/etc/systemd/system/nodebb.service:
[Unit]
Description=NodeBB
Documentation=https://docs.nodebb.org
After=network.target mongod.service
Requires=mongod.service
[Service]
Type=simple
User=nodebb
Group=nodebb
WorkingDirectory=/opt/nodebb
ExecStart=/usr/bin/node loader.js --no-daemon --no-silent
Restart=always
RestartSec=5
StandardOutput=journal
StandardError=journal
SyslogIdentifier=nodebb
Environment=NODE_ENV=production
# Hardening
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/opt/nodebb
[Install]
WantedBy=multi-user.targetsystemctl daemon-reload
systemctl enable --now nodebb
systemctl status nodebb
journalctl -u nodebb -fIf ProtectSystem=strict causes trouble with a plugin that wants to write outside /opt/nodebb, add the path to ReadWritePaths rather than dropping the hardening.
9. nginx reverse proxy with WebSocket support
NodeBB listens on 4567. Without nginx you would be serving http://forum.example.com:4567, and the Socket.IO connection is the part people get wrong.
/etc/nginx/sites-available/nodebb:
upstream nodebb {
server 127.0.0.1:4567;
keepalive 32;
}
server {
listen 80;
listen [::]:80;
server_name forum.example.com;
client_max_body_size 32M;
location / {
proxy_pass http://nodebb;
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;
proxy_redirect off;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
access_log /var/log/nginx/nodebb.access.log;
error_log /var/log/nginx/nodebb.error.log;
}The three lines that make real-time work are proxy_http_version 1.1, the Upgrade header, and Connection "upgrade". Miss any of them and the forum loads but never updates live, and your browser console fills with failed Socket.IO polling.
ln -s /etc/nginx/sites-available/nodebb /etc/nginx/sites-enabled/
rm -f /etc/nginx/sites-enabled/default
nginx -t
systemctl reload nginxTell NodeBB it is behind a proxy
In /opt/nodebb/config.json:
{
"url": "https://forum.example.com",
"secret": "...",
"database": "mongo",
"port": 4567,
"bind_address": "127.0.0.1",
"trust_proxy": true
}bind_address on 127.0.0.1 means 4567 is unreachable from outside regardless of what ufw is doing. trust_proxy makes NodeBB honor X-Forwarded-For, which matters for rate limiting and IP-based moderation. Restart after editing:
systemctl restart nodebb10. TLS with Let's Encrypt
apt install -y certbot python3-certbot-nginx
certbot --nginx -d forum.example.com --agree-tos -m you@example.com --redirect
certbot renew --dry-runConfirm url in config.json is https://, not http://. Mismatched scheme produces mixed-content failures and broken Socket.IO handshakes.
Behind Cloudflare, use DNS-only for the initial issuance, then enable the proxy with SSL mode Full (strict). Also enable WebSockets in the Cloudflare network settings, or real-time will silently degrade.
11. Configure email
Admin control panel, Settings, Email. Set:
From address: forum@example.com
From name: Your Forum
Email service: Custom
SMTP host: smtp.postmarkapp.com
SMTP port: 587
Use TLS: yes (or 465 with implicit TLS)
Username: <provider token>
Password: <provider token>Use the "Send test email" control. Failures show up in journalctl -u nodebb.
Then turn on requireEmailConfirmation under Settings, User, so bots do not fill your user table.
12. Post-install hardening
In the ACP:
- Enable 2FA for administrators.
nodebb-plugin-2factoris bundled. Turn it on under Extend, Plugins, then enroll from your user settings. - Configure the spam-be-gone plugin with Akismet or similar. Bundled, disabled by default. A public forum without it will be full of casino posts within a week.
- Review Settings, Advanced, Rate limiting.
- Set
maximumFileSizeagainst your RamNode disk allocation. - Review the privilege matrix under Manage, Privileges. NodeBB's default privileges are permissive for registered users.
ActivityPub federation
NodeBB 4.x ships native ActivityPub. Categories can publish to and be followed from Mastodon, Lemmy, and Misskey. It is off by default. Before turning it on, understand that federation means your posts leave your server and are cached elsewhere, permanently and outside your control. That is a moderation and privacy decision, not a technical one. Enable it under Settings, ActivityPub once you have made it deliberately.
13. Backups
Three things to back up: the Mongo database, /opt/nodebb/public/uploads/, and config.json.
/usr/local/bin/nodebb-backup.sh:
#!/bin/bash
set -euo pipefail
BACKUP_DIR=/srv/backups/nodebb
STAMP=$(date +%Y%m%d-%H%M%S)
KEEP_DAYS=14
mkdir -p "$BACKUP_DIR/db-$STAMP"
mongodump \
--uri="mongodb://nodebb:${NODEBB_DB_PASS}@127.0.0.1:27017/nodebb?authSource=nodebb" \
--out="$BACKUP_DIR/db-$STAMP"
tar czf "$BACKUP_DIR/db-$STAMP.tar.gz" -C "$BACKUP_DIR" "db-$STAMP"
rm -rf "$BACKUP_DIR/db-$STAMP"
tar czf "$BACKUP_DIR/files-$STAMP.tar.gz" \
-C /opt/nodebb public/uploads config.json
find "$BACKUP_DIR" -type f -mtime +$KEEP_DAYS -deletechmod +x /usr/local/bin/nodebb-backup.sh
echo 'NODEBB_DB_PASS=your-nodebb-password' > /root/.nodebb-backup.env
chmod 600 /root/.nodebb-backup.envCron:
20 3 * * * . /root/.nodebb-backup.env && /usr/local/bin/nodebb-backup.sh >> /var/log/nodebb-backup.log 2>&1
45 3 * * * rsync -az --delete /srv/backups/nodebb/ user@backup-host:/srv/backups/nodebb/A RamNode VPS is a single node. Backups on the same disk as the forum are not backups.
Restore
systemctl stop nodebb
tar xzf db-20260716-032001.tar.gz -C /tmp
mongorestore --drop \
--uri="mongodb://nodebb:PASS@127.0.0.1:27017/nodebb?authSource=nodebb" \
/tmp/db-20260716-032001/nodebb
tar xzf files-20260716-032001.tar.gz -C /opt/nodebb
chown -R nodebb:nodebb /opt/nodebb
systemctl start nodebbTest this on a scratch VPS before you need it.
14. Upgrades
NodeBB ships releases at a high cadence. It has a built-in migration runner that discovers and executes upgrade scripts in semver order, which makes upgrades less frightening than they could be. It does not make backups optional.
Within the 4.x line
systemctl stop nodebb
sudo -u nodebb -H bash
cd /opt/nodebb
git fetch
git checkout v4.x
git pull
./nodebb upgrade
exit
systemctl start nodebb
journalctl -u nodebb -f./nodebb upgrade runs npm install, rebuilds assets, and runs pending migrations in one pass. On a 1 GB plan this is the step most likely to get OOM-killed, which is why you configured swap.
If a plugin breaks the build after an upgrade, start with plugins disabled:
./nodebb reset -pThen re-enable them one at a time.
Across major versions
Read the upstream upgrade notes first. Node.js floor changes between majors, and 4.11 already moved to Node 22. Check your Node version before starting:
node -v15. Troubleshooting
Forum loads but nothing updates in real time. WebSocket proxying. Check proxy_http_version 1.1 and the Upgrade/Connection headers in your nginx block. If you are behind Cloudflare, check that WebSockets are enabled there too.
Assets 404 or the page renders unstyled. url in config.json does not match how you are actually reaching the site. Fix it, then ./nodebb build and restart.
./nodebb upgrade gets killed. OOM. dmesg | grep -i oom. Add swap or size up. Consider --max-old-space-size tuning as a last resort.
Cannot connect to MongoDB after enabling auth. Confirm authSource. NodeBB's user was created in the nodebb database, so authSource=nodebb, not admin.
Service will not start after adding systemd hardening. ProtectSystem=strict is blocking a write. Check journalctl -u nodebb for the path and add it to ReadWritePaths.
Emails never arrive. Check journalctl -u nodebb around the send. Confirm 587 or 465 with a real provider. Port 25 is not available to you on RamNode.
High memory after weeks of uptime. Node processes grow. systemctl restart nodebb on a weekly timer is a legitimate mitigation on small plans, not a defeat.
16. Where NodeBB fits
NodeBB is the reasonable middle. It is lighter than Discourse and more capable out of the box than Flarum, the bundled plugin set covers most of what a real forum needs without hunting through a catalog, and the ActivityPub story is genuinely unique among the three. The cost is a Node.js and MongoDB stack to maintain, which is more surface area than Flarum's PHP and MariaDB, and a smaller community than Discourse's when you go looking for answers.
Pick Discourse if moderation tooling and trust levels are the point. Pick Flarum if you want the smallest possible footprint on shared infrastructure. Pick NodeBB if you want real-time, federation, or both. All three have companion guides.
Quick reference
Install root: /opt/nodebb
Config: /opt/nodebb/config.json
Uploads: /opt/nodebb/public/uploads
Listens on: 127.0.0.1:4567
Service: systemctl {start,stop,restart,status} nodebb
Logs: journalctl -u nodebb -f
./nodebb setup # initial configuration
./nodebb upgrade # pull deps, rebuild, migrate
./nodebb build # rebuild assets only
./nodebb reset -p # disable all plugins
./nodebb reset -t # reset to default theme