Use case · Database

Self hosted MongoDB Community Server on a dedicated machine

Self hosted MongoDB means running MongoDB Community Server, currently the 9.0 series, on a machine you control: install it from MongoDB's own apt repository, turn authorization on before the first user exists, keep port 27017 on loopback, and size memory around the WiredTiger cache. An NVMe cartridge with root access covers a single node; three of them make a replica set.

Configure the server

What mongod needs from the hardware

Memory decides most of it. The production notes say MongoDB uses two caches at once: the WiredTiger internal cache, which holds collection data uncompressed, and the operating system's filesystem cache, which holds the compressed on-disk files and takes whatever memory nothing else is using. The default internal cache is the larger of 50% of (RAM minus 1 GB) or 0.256 GB, and the notes advise against raising it above that default. They also assume one mongod per machine.

Machine RAMDefault WiredTiger cacheLeft for file cache, OS and connections
32 GB15.5 GB16.5 GB
64 GB31.5 GB32.5 GB
128 GB63.5 GB64.5 GB

The figures are our arithmetic from the documented formula. MongoDB gives no fixed ratio of memory to data size; what matters is whether your working set, the documents and indexes your queries touch every day, fits in that cache.

  • CPU: at least two real cores (or one multi-core physical CPU) per mongod. On x86_64 MongoDB 5.0 and later needs AVX, and the notes set the floor at a Haswell or later Intel Core-class processor or a Bulldozer or later AMD one.
  • Storage: SSD where it is economical, XFS on Linux (the notes call it strongly recommended for data-bearing nodes with WiredTiger), the none I/O scheduler for NVMe or SSD on physical servers, and noatime on the data volume.
  • RAID: the notes name RAID 10 for optimal storage performance and say RAID 5 and RAID 6 do not typically provide enough.

Which machine to pick

For one node or a small replica set, an NVMe dedicated server is the direct fit: a whole HPE Moonshot cartridge with its NVMe SSD on PCIe, root, and a 1 Gbps unmetered port. The NVMe lines are Intel Xeon D-1500 (D-1548 with 8 cores, D-1587 with 16, 32 GB to 128 GB DDR4), Intel Xeon E3-1585L v5 (4 cores, 32 GB to 64 GB) and Intel Xeon E-2286M (8 cores, 32 GB to 64 GB, Bucharest only). All three are Intel generations after Haswell with AVX, and the first block below checks for AVX before it installs anything.

  • Largest working set: Xeon D-1587 with 128 GB. By the formula above that is a 63.5 GB default cache.
  • Two drives in RAID 1: each NVMe cartridge takes one drive or two mirrored in software, 240 GB to 3.84 TB per drive. Usable space stays the size of one drive, and a mirror covers a failed drive, not a dropped collection.
  • Console: the E3-1585L v5 line is the m710x cartridge, which has its own browser console. Moonshot cartridges other than the m510 and m710x have power control only.

A racked server is live about 30 minutes after the order is approved; a build to order takes 4 to 24 hours.

Install with authorization on from the start

The block follows the Ubuntu steps from the MongoDB install page for 9.0 (it supports the 22.04, 24.04 and 26.04 LTS releases; for 22.04 or 26.04 replace both noble strings with jammy or resolute). Our changes: the key is downloaded to a file and dearmored with --batch --yes so a re-run cannot stop at an overwrite prompt; ufw comes on with SSH only and IPv6 rules enabled; security.authorization is appended before the first start; vm.swappiness goes in /etc/sysctl.d rather than /etc/sysctl.conf. The block stops if the packaged config does not show bindIp: 127.0.0.1.

# Ubuntu 24.04 (noble), as a user with sudo. Safe to re-run.
grep -q '^VERSION_CODENAME=noble' /etc/os-release && grep -qw avx /proc/cpuinfo &&
sudo apt-get install -y gnupg curl &&
curl -fsSL https://pgp.mongodb.com/server-9.asc -o /tmp/server-9.asc &&
sudo gpg --batch --yes --dearmor -o /usr/share/keyrings/mongodb-server-9.gpg /tmp/server-9.asc &&
echo "deb [ arch=amd64,arm64 signed-by=/usr/share/keyrings/mongodb-server-9.gpg ] https://repo.mongodb.org/apt/ubuntu noble/mongodb-org/9.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-9.0.list &&
sudo apt-get update && sudo apt-get install -y mongodb-org &&
sudo sed -i 's/^IPV6=.*/IPV6=yes/' /etc/default/ufw &&
sudo ufw allow OpenSSH && sudo ufw --force enable &&
grep -q '^  bindIp: 127.0.0.1$' /etc/mongod.conf &&
{ grep -q '^security:' /etc/mongod.conf || printf 'security:\n  authorization: enabled\n' | sudo tee -a /etc/mongod.conf >/dev/null; } &&
grep -q '^  authorization: enabled$' /etc/mongod.conf &&
echo 'vm.swappiness = 1' | sudo tee /etc/sysctl.d/90-mongodb.conf && sudo sysctl --system >/dev/null &&
sudo systemctl enable --now mongod && sleep 5 && systemctl is-active --quiet mongod &&
echo "mongod is up on 127.0.0.1 with authorization on" ||
echo "STOP - read the output above and /var/log/mongodb/mongod.log"

With access control on and no users yet, the localhost exception lets a connection from the machine itself create exactly one first user in the admin database. Start mongosh, then enter the remaining lines one at a time: each passwordPrompt() waits for a password, and a pasted line after it would be read as one.

mongosh
use admin
db.createUser({ user: "admin", pwd: passwordPrompt(), roles: [ { role: "userAdminAnyDatabase", db: "admin" }, { role: "readWriteAnyDatabase", db: "admin" } ] })
db.auth("admin", passwordPrompt())
db.createUser({ user: "backup", pwd: passwordPrompt(), roles: [ "backup", "restore" ] })
db.getSiblingDB("app").createUser({ user: "appuser", pwd: passwordPrompt(), roles: [ { role: "readWrite", db: "app" } ] })

The first user follows the SCRAM tutorial (userAdminAnyDatabase plus readWriteAnyDatabase). The backup user carries the built-in backup and restore roles that mongodump and mongorestore need; appuser can only read and write the app database. Your application then connects with mongodb://appuser:PASSWORD@127.0.0.1:27017/app?authSource=app.

Production notes worth applying before data arrives

  • Transparent Hugepages and TCMalloc. From 8.0, MongoDB's TCMalloc page says THP must be enabled before mongod starts, through a oneshot systemd unit (enable-transparent-huge-pages.service, ordered Before=mongod.service). The unit sets THP enabled to always, defrag to defer+madvise and khugepaged max_ptes_none to 0, and it also sets vm.overcommit_memory to 1. Copy it from that page, then sudo systemctl daemon-reload && sudo systemctl enable --now enable-transparent-huge-pages && sudo systemctl restart mongod (enable alone would wait for the next boot); confirm the THP enabled file under /sys reads [always], then check in mongosh that db.serverStatus().tcmalloc.usingPerCPUCaches is true. If it is false, the page says to disable glibc rseq: add Environment=GLIBC_TUNABLES=glibc.pthread.rseq=0 under [Service] with sudo systemctl edit mongod, then restart.
  • File system. Check it with findmnt -no FSTYPE --target /var/lib/mongodb. If it prints ext4, plan an XFS volume for the data directory before you load anything.
  • Open files. systemctl show mongod -p LimitNOFILE should be 64000 or more; below 64000 mongod logs a startup warning.
  • NUMA. The Xeon D line is single socket. On a multi-socket machine the notes ask for NUMA off in the BIOS, or zone reclaim off plus numactl --interleave=all in the unit, and no numad.
  • Clock and readahead. Run an NTP-compatible daemon such as systemd-timesyncd; keep readahead between 8 and 32.

Port 27017 stays off the internet

The install page warns that before you bind to a publicly reachable address you must secure the deployment, and the security checklist asks for access control, TLS on every connection and a firewall that admits only trusted clients. For a GUI such as Compass, forward the port over SSH under your own account instead of opening anything: ssh -N -L 27017:127.0.0.1:27017 you@server.

If the application runs on another machine, three changes go together: add the server's own address to net.bindIp next to 127.0.0.1, configure net.tls, and admit only the client with sudo ufw allow from 203.0.113.10 to any port 27017 proto tcp. mongod runs on the host, not in Docker, so ufw filters it on IPv4 and IPv6. Never ufw allow 27017.

Backups with mongodump, and the restore

The backup methods page calls mongodump and mongorestore tools for small deployments, and the backup-tools tutorial adds that they force the database to read all data through memory, evicting frequently used data and slowing the regular workload while they run. Make the password file empty and private with sudo install -m 600 /dev/null /root/.mongodump.yaml, then open it with sudo nano /root/.mongodump.yaml and write one YAML line: the key password, a colon, and the password of the backup user in double quotes (pick one without a double quote or backslash in it). mongodump's --config option is the documented way to keep the password off the command line and out of ps.

# As root, after creating /root/.mongodump.yaml (see above)
install -d -m 700 /var/backups/mongodb && tee /usr/local/sbin/mongo-backup.sh >/dev/null <<'EOF'
set -euo pipefail
umask 077
d=/var/backups/mongodb
f="$d/mongo-$(date +%F-%H%M%S).archive.gz"
mongodump --config=/root/.mongodump.yaml --username=backup --authenticationDatabase=admin \
  --archive="$f" --gzip --quiet
gzip -t "$f"
tar -czf "$f.etc.tgz" -C / etc/mongod.conf
find "$d" -name 'mongo-*' -mtime +7 -delete
echo "backup ok $f"
EOF
printf '15 3 * * * root bash /usr/local/sbin/mongo-backup.sh >>/var/log/mongo-backup.log 2>&1\n' > /etc/cron.d/mongo-backup &&
bash /usr/local/sbin/mongo-backup.sh || echo "STOP - backup FAILED, see the output above"

The script stops at the first error, checks the gzip stream, keeps a copy of mongod.conf, and prunes archives older than seven days only after a good one exists. Copy /var/backups/mongodb to another host with plain rsync -a, never --delete. A standalone has no oplog, so these dumps are not point-in-time; --oplog needs a replica set.

  1. Before restoring over live data, stop the application so nothing writes, then run the backup script yourself (sudo bash /usr/local/sbin/mongo-backup.sh) and confirm it prints backup ok with a new file name. That archive is your rollback, and the seven-day prune will not touch it.
  2. Restore as root: gzip -t FILE && mongorestore --config=/root/.mongodump.yaml --username=backup --authenticationDatabase=admin --archive=FILE --gzip --drop --nsExclude='admin.*'. --drop replaces only the collections in the archive. Excluding admin matters: with --drop, a restore that includes it replaces every user with the users in the dump.
  3. If the result is wrong, keep the application stopped and run the same command against the archive from step 1. --drop only replaces collections that archive contains, so drop by hand any collection the first restore added that the step-1 archive does not have.

Filesystem snapshots are the other documented method. MongoDB's LVM tutorial asks for the data files, the journal and the configuration on one logical volume that holds nothing else; if the journal sits on another volume, flush and lock writes with db.fsyncLock() for the length of the snapshot. LVM snapshots are full images with no incremental option, and a snapshot on the same drives only counts as a backup once it is copied off the machine. Decide on LVM for /var/lib/mongodb before you load data.

Replica sets and version upgrades

The replica set tutorial recommends three members, an odd number, each on its own machine, addressed by DNS hostnames rather than IPs. For members proving their identity to each other, the keyfile tutorial limits keyfiles to testing and development and points production at X.509 certificates. We sell no private VLAN, so traffic between members crosses the public port: turn on TLS (net.tls) on every member, and add ufw rules that admit each member's address and nothing else. Initiate the set from one member over localhost, then add users once a primary is elected.

Upgrades go one major release at a time. MongoDB 9.0 accepts an upgrade from 8.0 or 8.3 only, with featureCompatibilityVersion already at the old version; from 7.0 you go to 8.0 first. With the apt install, add the new repository and upgrade the packages; a replica set upgrades secondaries one at a time, then steps the primary down. Set the new feature compatibility version only after a burn-in period, because it complicates a downgrade.

The SSPL, read plainly

MongoDB's licensing page puts the Database Server under its Server Side Public License v1.0 for every version released after 16 October 2018, and under the GNU AGPL v3.0 before that; the client libraries MongoDB supports are Apache 2.0. Community Server costs nothing to download or run, and MongoDB also offers commercial licences.

Our general reading, not legal advice. The SSPL lets you run the unmodified program, subject to section 13. Distributing copies, modified or not, means passing on the licence and the corresponding source. Section 13: if you make the functionality of the program, or a modified version, available to third parties as a service, you must release the Service Source Code, meaning the program plus the management, interface, API, automation, monitoring, backup, storage and hosting software behind that service, under the SSPL. Whether your product counts is a question for your lawyer.

When this is the wrong machine

  • Data past 3.84 TB, or RAID 10. NVMe cartridges stop at two drives in RAID 1. The production notes prefer RAID 10, which on our side means a rack or blade server with a hardware controller: see enterprise dedicated servers.
  • Enterprise features. The checklist lists LDAP and Kerberos authentication and the auditing facility as Enterprise or managed-service features; Community has SCRAM and X.509.
  • Search and vector search. Community does not include MongoDB Search by default. Both MongoDB Search and Vector Search come from a separate process, mongot, which needs MongoDB 8.3 or later running as an initiated replica set with keyfile access control and a user holding the searchCoordinator role. The standalone set up on this page is not enough.
  • A relational schema. For tables and joins, read the PostgreSQL on bare metal or the MariaDB setup notes. For a pure cache next to MongoDB, see Redis on bare metal.

Questions

Is self hosted MongoDB free?

Community Server has no licence fee: you download it from MongoDB's repository and run it. It is licensed under the SSPL v1.0, which puts conditions on distributing it and on making its functionality available to third parties as a service; whether your product falls under that clause is a legal question. You pay only for the machine.

How much memory should the server have?

Enough that the working set, the documents and indexes queried every day, fits in the WiredTiger cache. That cache defaults to half of (RAM minus 1 GB), and the rest serves the filesystem cache, so a 64 GB machine gives about 31.5 GB to WiredTiger by default. MongoDB publishes no fixed ratio of RAM to data size.

Do I need a replica set, or is one node enough?

One node with tested backups works for development and for data you can restore from a dump. MongoDB recommends replica sets for production: three members on separate machines survive the loss of one, and the oplog allows point-in-time dumps. Plan three machines and TLS between them from the start.

Put MongoDB on NVMe you control

Pick the NVMe line and memory for your working set, mirror the drives if you want, and install MongoDB with root.

Configure the server

Related on Server Room

Other use cases