MongoDB remote access requires three changes: bind the daemon to an address your app server can reach, turn on authorization so every connection needs a user, and restrict port 27017 to known client IPs. Do all three in that order and your database stays private while remaining reachable from your application.
By default MongoDB listens only on 127.0.0.1 and has no authentication. Opening the port without the other two steps is how databases end up indexed by search engines and wiped. This guide walks through the safe sequence on Ubuntu, Debian, AlmaLinux or Rocky.
Before you start
You need:
- A Linux VPS with MongoDB already installed and running (
sudo systemctl status mongod). mongoshavailable on the server. On Ubuntu and Debian it ships withmongodb-mongosh; on RHEL-family systems install it from the MongoDB repository.- Root or sudo access, and the ability to edit
/etc/mongod.conf. - The public IP of the machine that will connect, for example
198.51.100.25. This guide uses203.0.113.10as the server address.
Warning: Back up the config before editing it: sudo cp /etc/mongod.conf /etc/mongod.conf.bak. A YAML indentation mistake stops mongod from starting.
If MongoDB is not installed yet, follow How to install MongoDB on Ubuntu and secure it first, then come back here.
Confirm the current state
Check what the daemon is listening on:
sudo ss -tlnp | grep 27017You should see 127.0.0.1:27017. That confirms remote connections are refused right now, which is the correct starting point.
Connect locally and list the databases:
mongosh --host 127.0.0.1 --port 27017show dbsIf show dbs returns data without asking for a password, authorization is still off. Leave this shell open for the next step.
Create the admin user before enabling authorization
Create users first. If you enable authorization with no users defined, you lock yourself out of everything except the localhost exception.
In the mongosh session, switch to the admin database and create an administrative account:
use admin
db.createUser({
user: "siteAdmin",
pwd: passwordPrompt(),
roles: [
{ role: "userAdminAnyDatabase", db: "admin" },
{ role: "readWriteAnyDatabase", db: "admin" }
]
})passwordPrompt asks you to type the password instead of leaving it in your shell history. Note the output — it should report ok: 1.
Now create a least-privilege account for the application. Replace appdb with your real database name:
use appdb
db.createUser({
user: "deploy",
pwd: passwordPrompt(),
roles: [ { role: "readWrite", db: "appdb" } ]
})Tip: Give each application its own user scoped to its own database. A single shared root account makes auditing impossible and turns one leaked password into a full compromise.
Allow MongoDB remote access with bindIp
Open the config file:
sudo nano /etc/mongod.confFind the net section and set bindIp to the loopback address plus the address the app server should reach. If your VPS has a private network interface, bind to that instead of the public IP — traffic then never crosses the public internet.
net:
port: 27017
bindIp: 127.0.0.1,203.0.113.10YAML is whitespace-sensitive. Use two spaces for the nested keys, not tabs, and keep the indentation of port and bindIp identical.
In the same file, enable authorization:
security:
authorization: enabledIf the security block does not exist, add it at the end of the file.
Warning: Restarting mongod drops every open connection. Schedule this for a maintenance window if the database is already serving traffic.
sudo systemctl restart mongod
sudo systemctl status mongodConfirm the daemon picked up the new bind address:
sudo ss -tlnp | grep 27017You should now see 203.0.113.10:27017 alongside 127.0.0.1:27017.
Restrict port 27017 with the firewall
Binding to a public IP exposes the port to the internet. Close it to everything except your app server.
On Ubuntu or Debian with UFW:
sudo ufw allow from 198.51.100.25 to any port 27017 proto tcp
sudo ufw deny 27017/tcp
sudo ufw status numberedOn AlmaLinux or Rocky with firewalld, use a rich rule:
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="198.51.100.25" port port="27017" protocol="tcp" accept'
sudo firewall-cmd --reload
sudo firewall-cmd --list-rich-rulesIf your provider also offers a cloud firewall or security group, add the same rule there. Host firewalls and network firewalls are independent layers.
Check it works
From the app server, connect with the application user:
mongosh "mongodb://deploy@203.0.113.10:27017/appdb?authSource=appdb"Enter the password when prompted, then run:
db.runCommand({ ping: 1 })A reply of { ok: 1 } means remote access and authentication both work. Try a write to be sure the role is correct:
db.test.insertOne({ check: "remote" })Now verify the negative cases. From a machine that is not on the allow list, the connection should time out rather than be refused — that is the firewall dropping the packets. From the server itself, this should fail:
mongosh "mongodb://203.0.113.10:27017/appdb"An authentication error here is the expected result. No credentials, no access.
Troubleshooting
MongoNetworkError: connection refused—mongodis still bound to127.0.0.1only. Re-check thebindIpline in/etc/mongod.conf, confirm the indentation, and restart the service.sudo ss -tlnp | grep 27017shows the truth.MongoNetworkError: connection timed out— the firewall is dropping packets. Confirm the client IP in your UFW rule or firewalld rich rule matches the source address exactly, and check any cloud security group.MongoServerError: Authentication failed— the password is wrong, orauthSourcepoints at the wrong database. Users created insideappdbmust authenticate againstappdb, notadmin.MongoServerError: not authorized on appdb to execute command— the user exists but lacks the role. GrantreadWriteon that database withdb.grantRolesToUserwhile connected as an admin user.mongodwill not start after the edit — almost always a YAML problem. Runsudo journalctl -u mongod -n 50to see the parse error, then restore/etc/mongod.conf.bakand redo the edit with consistent two-space indentation.
Next steps
- Harden the server itself with SSH hardening: auditing and limiting shell access for service accounts.
- Add IP-based rules at the network layer with firewalld rich rules for IP-based access.
- Running other data stores? Configure PostgreSQL remote access with pg_hba.conf and configure Redis remote access with bind and protected-mode follow the same pattern.
