There is one configuration detail behind almost every report that self-hosted Rocket.Chat is broken, and it is in the database rather than the application. Messages store correctly and never appear until a refresh, which reads as a bug and is a two-line fix. This covers that, the Node version discipline it also needs, and the push notification limit nobody mentions up front.

Rocket.Chat is a Node application on MongoDB, and both halves have opinions. The Node side wants a specific major version. The MongoDB side wants to be a replica set, even with one member, because Rocket.Chat relies on the oplog to push messages in real time. Get those two right and the rest is configuration.

The Replica Set Requirement

This is the detail that turns a working install into a broken one, and it is worth understanding rather than copying.

Rocket.Chat watches MongoDB's oplog to learn about new messages and push them to connected clients. A standalone mongod has no oplog. The application starts, accepts logins, stores messages, and requires a page refresh before anyone sees them, which users report as "chat is broken" rather than as a database configuration issue.

A single-member replica set gives you the oplog with none of the operational weight of real replication. Initialise it once and it stays.

# /etc/mongod.conf
replication:
  replSetName: "rs01"

# then, once
mongosh --eval 'rs.initiate({_id:"rs01",members:[{_id:0,host:"127.0.0.1:27017"}]})'
mongosh --eval 'rs.status().ok'

Sizing

ScalevCPU / RAM / SSDMonthly on per-resource pricing
Up to 50 users2 / 4 GB / 60 GB$9.54
50-200 users4 / 8 GB / 120 GB$19.08
200-500 users8 / 16 GB / 250 GB$38.26

Those use MassiveGRID's per-resource rates of $2.87 per CPU core, $0.80 per GB of RAM and $0.01 per GB of SSD per month, less 20% on annual billing.

Storage grows from file uploads rather than from messages. A team that shares screenshots all day will outgrow the disk long before it outgrows the CPU, so plan the disk against upload volume and keep an eye on it. MongoDB also wants RAM for its working set: a busy instance with too little memory shows up as message latency, not as an error.

Installing

The snap package is the fastest route and the least controllable. For a deployment you intend to keep, install from the release tarball against a Node version Rocket.Chat supports, or use the official Docker Compose file. Both are fine; mixing approaches later is not.

sudo apt update && sudo apt install -y curl gnupg build-essential graphicsmagick
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
sudo apt install -y nodejs
sudo npm install -g n && sudo n 22.11.0
node -v

Pin the Node major version and record which one you used. Rocket.Chat is specific about supported versions, and an unattended Node upgrade is a classic cause of a service that stops starting after a reboot with a stack trace that names neither Node nor the upgrade.

Run it under systemd rather than in a shell, with Restart=always, the environment variables set in the unit, and the process owned by a dedicated unprivileged user.

The Environment Variables That Matter

ROOT_URL=https://chat.example.com
MONGO_URL=mongodb://127.0.0.1:27017/rocketchat?replicaSet=rs01
MONGO_OPLOG_URL=mongodb://127.0.0.1:27017/local?replicaSet=rs01
PORT=3000

ROOT_URL must be the external HTTPS URL. Rocket.Chat builds websocket endpoints, file links and OAuth callbacks from it, so a wrong value produces an application that loads and then fails to connect, plus mobile apps that cannot log in.

MONGO_OPLOG_URL points at the local database, not the application database. That is not a typo in the documentation; the oplog lives in local.

Reverse Proxy With Websockets

server {
    listen 443 ssl http2;
    server_name chat.example.com;

    client_max_body_size 200M;

    location / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forward-Proto $scheme;
        proxy_read_timeout 3600s;
    }
}

Without the upgrade headers the client falls back to long polling: messages arrive late, presence is wrong, and CPU on both ends is higher than it should be. Raise the body size limit to match the file upload limit you set in the admin panel, or uploads fail at the proxy before the application sees them.

Mobile Push, and the Thing Nobody Expects

Push notifications to the iOS and Android apps go through Rocket.Chat's gateway, because Apple and Google push requires certificates tied to the published apps. A self-hosted instance using the official mobile apps therefore routes notification metadata through that gateway, and the free tier limits how many.

Three options, and it is worth choosing deliberately rather than discovering the limit. Accept the gateway and its limits, which is fine for a small team. Take a subscription that raises them. Or build and publish your own app with your own certificates, which is a real project. Whichever you choose, tell users what to expect, because "I don't get notifications" is the most common complaint about self-hosted chat.

Backups

mongodump --uri="mongodb://127.0.0.1:27017/rocketchat?replicaSet=rs01" \
  --archive=/backup/rc-$(date +%F).archive --gzip
tar czf /backup/rc-uploads-$(date +%F).tar.gz /var/lib/rocketchat/uploads

File uploads default to the local filesystem or GridFS inside MongoDB, depending on configuration, and you need to know which before writing a backup script. If uploads are on the filesystem, a mongodump alone restores a chat history where every attachment is a dead link.

Keep the copies off the instance. Backup services use block-level incremental backups with AES-256 encryption at $0.01 per GB, which is the appropriate place for a message archive that may also be a compliance record.

What You Give Up Against Slack

You give up the app directory, some polish in search, and the fact that everyone already has an account. You gain unlimited message history, which is the feature the hosted tools charge most for, control over where the data sits, no per-seat pricing, and the ability to place the server in a jurisdiction you chose.

Our guide to self-hosting Mattermost covers the closest alternative, which uses PostgreSQL and has a different operational profile; the choice between them is mostly about whether you would rather run MongoDB or Postgres.

Infrastructure for a Tool a Team Lives In

Chat is the application people notice first when a server has a bad day, and it holds the record of decisions nobody wrote down anywhere else.

MassiveGRID runs Proxmox high-availability clustering with automatic failover over Ceph storage replicating every block three times across independent NVMe drives, behind a 100% uptime SLA. A Linux VPS at the rates above suits most teams; where chat is business-critical, high-availability cloud servers add managed operations from $9.99 plus $5 management. Instances can be ordered across a partner footprint of more than 700 datacenters in 85 metros, 30 countries and six continents, with auto-provisioning in New York, London, Frankfurt and Singapore.

Further Reading