Blog · Self-hosting

Install MongoDB on Ubuntu as a replica set

Installing MongoDB is a piece of cake. Don't let the forum threads scare you: with the right steps it takes three minutes, and here they are, in one command. You get a replica set from day one, so your app can use transactions and a second server is one line away.

Short answer

The newest MongoDB (9.0 today) as a replica set, with a password and a keyfile, listening only where it should:

curl -fsSL https://bsonjet.com/blog/install-mongodb-ubuntu-replica-set/install-mongodb.sh \
  | sudo bash

About 3 minutes on a clean Ubuntu 24.04. At the end it prints your connection string: user admin and a generated password, also saved for root in /root/mongodb-credentials.txt (sudo cat it any time).

With options, all of them optional:

curl -fsSL https://bsonjet.com/blog/install-mongodb-ubuntu-replica-set/install-mongodb.sh \
  | sudo \
  MONGO_HOST=db.example.com \
  ADMIN_USER=admin \
  ADMIN_PASSWORD='YourStrongPassword' \
  MONGO_VERSION=8.0 \
  RS_NAME=rs0 \
  bash
  • MONGO_HOST: the DNS name other machines connect to (your app server, your laptop, a second MongoDB later). Default: the server's name.
  • ADMIN_USER, ADMIN_PASSWORD: your own login. Default: admin and a generated password.
  • MONGO_VERSION: a specific version, e.g. 8.0. Default: the newest. RS_NAME: the replica set name, default rs0.

It's 282 lines, read them first. Prefer Docker? Install MongoDB in Docker as a replica set.

Why a replica set on one server?

  • Room to grow, for free. A second or third server later is one rs.add(): no reinstall, no downtime, no license. If one server dies, another takes over.
  • A move without downtime. Add the new server, let it sync, switch, remove the old one. We measured it: 0 failed writes.
  • Transactions. Without a replica set, the first transaction fails with Transaction numbers are only allowed on a replica set member or mongos. Mongoose, Prisma and every withTransaction() need one.

The price: the oplog, by default 5% of the free disk (at most 50 GB). That's it.

The traps the script avoids

  1. apt install works, mongod doesn't start

    On Ubuntu 24.04 with a newer kernel, MongoDB 8.0+ refuses to start: Linux kernel versions 6.19 and newer has a known incompatibility. Docker too. The official advice, a newer kernel, doesn't help on Ubuntu. We tried.

    one setting in the mongod service causes it, and the script switches it off. Also after a future kernel update. Installing by hand? This is the file:

    # /etc/systemd/system/mongod.service.d/kernel-6.19.conf
    [Service]
    UnsetEnvironment=GLIBC_TUNABLES
  2. The newest Ubuntu isn't supported yet

    Today on 26.04 the MongoDB repo even exists, just without the server, so you get Unable to locate package halfway through. The script checks first and stops with a clear message. (Or use Docker, it doesn't mind.)

  3. Illegal instruction (core dumped)

    MongoDB 5.0+ needs a CPU with AVX. Old hardware and some cheap VPS don't have it. Checked before anything is installed.

  4. Authentication on, replica set broken

    A replica set with a password needs a keyfile, and mongod refuses one that others can read. The script makes it, chmod 400, owned by mongodb.

  5. The app can't find the server

    The replica set tells every client its own name for the server. Set it up as localhost and nobody else can connect.

    the script uses the server's name and checks it works. Will other machines connect? Give it its DNS name from day one with MONGO_HOST (see above). And it never listens on a public address by accident, so port 27017 isn't open to the internet. Remember MongoBleed: about 75,000 exposed servers.

Updates: patches and the next version

# the newest patch of your version (security fixes)
curl -fsSL https://bsonjet.com/blog/install-mongodb-ubuntu-replica-set/update-mongodb.sh \
  | sudo bash

# the next version
curl -fsSL https://bsonjet.com/blog/install-mongodb-ubuntu-replica-set/update-mongodb.sh \
  | sudo bash -s -- --to 9.0

Why not just apt upgrade? It installs the patch and keeps the old mongod running: we checked, same process, binary deleted on disk. A security fix does nothing until a restart, and the script restarts. It also never leaves your version. Versions can't be skipped, so --to takes one step at a time:

7.0→8.0→9.0

It also sets the feature compatibility version on both sides of the step (the part everyone forgets until the next upgrade fails). Not sure which version you run? BsonJet shows it for every server, next to the newest MongoDB out there.

On more servers, run the patch update on the secondaries and the arbiter first, the primary last. --to is for a single server; a set follows MongoDB's rolling upgrade.

Adding more servers

Always an odd number. A replica set needs a majority of votes to pick its primary: with two servers, when one stops, the other has 1 vote of 2 and turns read-only. Two servers are less available than one.

The cheapest odd number is two data servers and an arbiter. The arbiter only votes, keeps no data and runs on the smallest VPS, or next to your app.

heartbeat replication Primary db1 · data · vote Secondary db2 · data · vote Arbiter vote only
3 votes, 2 copies of your data. Want 3 copies? Make the arbiter a data server.
  1. On db1, print the keyfile and the connection string:
    sudo cat /etc/mongodb/keyfile /root/mongodb-credentials.txt
  2. On db2, install it with both of them:
    curl -fsSL https://bsonjet.com/blog/install-mongodb-ubuntu-replica-set/install-mongodb.sh \
      | sudo \
      MONGO_HOST=db2.example.com \
      JOIN='paste MONGO_URI from db1' \
      KEYFILE='paste the keyfile from db1' \
      bash
  3. On the arbiter, the same plus ARBITER=1:
    curl -fsSL https://bsonjet.com/blog/install-mongodb-ubuntu-replica-set/install-mongodb.sh \
      | sudo \
      MONGO_HOST=arbiter.example.com \
      JOIN='paste MONGO_URI from db1' \
      KEYFILE='paste the keyfile from db1' \
      ARBITER=1 \
      bash
  4. In your app, list both data servers: db1.example.com,db2.example.com.

Each new server copies the data first and gets its vote only then, so the set never loses its majority.

Moving to a new server without downtime

Add the new server as above. When it's a secondary, let it take over and remove the old one, on the primary:

cfg = rs.conf()
cfg.members.find(m => m.host === "db2.example.com:27017").priority = 2
rs.reconfig(cfg)
// a few seconds later db2 is the primary; remove the old one
rs.remove("db1.example.com:27017")

In our test an app kept writing every 100 ms through the whole move: 0 failed writes. One catch: the running app found the new server by itself, but its connection string still names the old one. Update it before the next restart.

Then see what it's doing

Once it runs, BsonJet shows the whole replica set at a glance: every member's role, lag, disk and cache, the feature compatibility version and whether a newer MongoDB is out. Handy while the new server syncs.

BsonJet cluster overview: replica set rs0, feature compatibility 9.0, newest MongoDB 9.0.2, and the primary and a secondary with disk, cache, memory and connections
The replica set, its feature compatibility version and the newest MongoDB, in one tab.

Connect to your new server.

The full version is free for personal use, no registration.

Download for Windows, macOS or Linux