Your MongoDB Is One Port Scan Away From Disaster — Here’s How to Actually Secure It on GCP
Most tutorials show you how to get MongoDB running. Almost none of them show you how to stop someone else from getting into it too. This…
Your MongoDB Is One Port Scan Away From Disaster — Here’s How to Actually Secure It on GCP
Most tutorials show you how to get MongoDB running. Almost none of them show you how to stop someone else from getting into it too. This guide fixes that — a complete walkthrough of building a 3-node MongoDB quorum on GCP, hidden behind a Pritunl VPN, accessible from Compass on your local workspace like it’s sitting right on your network.

Image generated by ai
The Part Nobody Warns You About
Here’s a scenario that plays out more often than anyone in the industry likes to admit.
A developer spins up a MongoDB instance on a cloud VM, opens port 27017 to get things working quickly, and tells themselves they’ll lock it down properly later. Except “later” never comes, because the database is working fine and there are features to ship. Weeks pass. Then one morning, the database is gone — or worse, it’s still there but someone else has been quietly reading it for weeks.
This is not hypothetical. Shodan, a search engine that indexes internet-connected devices, lists hundreds of thousands of openly accessible MongoDB instances at any given time. Bots scan for open port 27017 continuously. If your database has a public IP and an open port, it’s not a matter of if it gets found — it’s when.
But here’s what’s interesting: fixing this properly is not actually hard. It just requires understanding that the firewall is not your security layer — the network is. If your database never has a public IP to begin with, there’s nothing for a port scanner to find. No exposed port. No attack surface. The database simply does not exist from the internet’s perspective.
That’s exactly what we’re building here. Three MongoDB nodes tucked inside a private GCP VPC — no public IPs, completely invisible to the outside world. The only way in is through a Pritunl VPN server that hands you a cryptographically signed .ovpn profile. Connect to it, and your workspace becomes part of the private network. Open Compass, paste a connection string, and you’re in — as if the database is running locally.
And if one of your three Mongo nodes goes down? The other two elect a new primary automatically, in under 10 seconds, without any intervention from you. That’s the quorum doing its job.
Here’s exactly what we’re building, and then we’ll go make it real:

The final outcome of the process (Generated by AI)
Let’s build it — from a completely blank GCP project.
Phase 1 — Create the VPC and Firewall Rules
Before you create a single VM, you want the network to exist. Everything will live inside this network, and the firewall rules will control exactly what can talk to what.
Step 1.1 — Create the VPC
In the GCP Console, go to VPC Network → VPC Networks → Create VPC Network.
Fill in the following:
- Name:`mongo-vpc`
- Subnet creation mode: Custom
- Subnet name: `mongo-subnet`
- Region: Choose the region closest to you (e.g. `asia-south1` for Mumbai)
- IP address range: `10.0.0.0/16`
- Private Google Access: On (allows VMs without public IPs to reach Google APIs)
- Flow logs: Off (save cost for now, enable later if you need to debug traffic)
Click Create and wait about 30 seconds for it to provision.
Step 1.2 — Create All Firewall Rules
This is the most important step. A misconfigured or missing firewall rule is the number one reason VPN connections fail and SSH times out. Go to VPC Network → Firewall → Create Firewall Rule and create each of the following rules one by one.
Rule 1: allow-ssh
This lets you SSH into any VM in the VPC. Without this, the GCP console SSH button and your terminal will both time out.
- Direction: Ingress
- Targets: All instances in the network
- Source: `0.0.0.0/0` (or narrow it to your home IP for better security: `your.ip.address/32`)
- Protocols: TCP, port `22`
Rule 2: allow-pritunl-vpn
This is the port your VPN clients connect to. Pritunl uses OpenVPN under the hood, which defaults to UDP 1194.
- Direction: Ingress
- Targets: Network tags → `pritunl`
- Source: `0.0.0.0/0`
- Protocols: UDP, port `1194`
Rule 3: allow-pritunl-web
Pritunl has a web UI you use to configure VPN users and download .ovpn profiles. This opens it up — restrict to your IP if possible.
- Direction: Ingress
- Targets: Network tags → `pritunl`
- Source: Your IP `/32`
- Protocols: TCP, ports `80, 443`
Rule 4: allow-internal
This allows all traffic between VMs within the VPC. This is what lets Mongo nodes replicate to each other, and lets Pritunl route traffic to the Mongo VMs after your VPN tunnel is established.
- Direction: Ingress
- Targets: All instances in the network
- Source: `10.0.0.0/16`
- Protocols: All
Rule 5: allow-mongo-from-vpn
This is the rule that lets Compass on your workspace actually reach MongoDB. When you connect to Pritunl, your workspace gets assigned an IP in the VPN tunnel range (we’ll set that to 192.168.100.0/24). This rule opens port 27017 specifically for that subnet.
- Direction: Ingress
- Targets: Network tags → `mongo`
- Source: `192.168.100.0/24`
- Protocols: TCP, port `27017`
Important: The
192.168.100.0/24source range here must exactly match the Virtual Network CIDR you configure in Pritunl later. We’ll set both to the same value in Phase 3.
Step 1.3 — Create the 4 Virtual Machines
Now create your VMs. You’ll have one Pritunl server (public-facing) and three MongoDB nodes (private only).
Go to Compute Engine → VM Instances → Create Instance.
VM 1 — Pritunl VPN Server
This is the only VM that gets a public IP. It’s lightweight, so a small machine type is fine.
- Name: `pritunl-server`
- Region & Zone: Same region as your subnet (e.g. `asia-south1-a`)
- Machine type: `e2-small` (2 vCPU, 2 GB RAM - more than enough for a VPN gateway)
- Boot disk: Ubuntu 22.04 LTS, 20 GB SSD (or the default)
- Networking tab:
- Network: `mongo-vpc`
- Subnetwork: `mongo-subnet`
- External IPv4 address: **Reserve a static IP** (don't use ephemeral - it'll change on restart and break your `.ovpn` profile)
- Network tags: `pritunl`
VMs 2, 3, 4 — MongoDB Nodes
These three VMs have no public IP. They are completely invisible to the internet. The only way in is through the VPN tunnel.
Repeat this for mongo-1, mongo-2, and mongo-3:
- Name: `mongo-1` / `mongo-2` / `mongo-3`
- Region & Zone: Same as above
- Machine type: `e2-medium` (2 vCPU, 4 GB RAM - minimum for a Mongo node under real workload)
- Boot disk: Ubuntu 22.04 LTS, 50 GB SSD (increase based on your data size)
- Networking tab:
- Network: `mongo-vpc`
- Subnetwork: `mongo-subnet`
- External IPv4 address: **None** - these nodes must not be publicly reachable
- Network tags: `mongo`
Once all four VMs are running, note down the internal IPs of your three Mongo VMs. You can find them in the VM list under the “Internal IP” column. You’ll need these in Phase 4 when you initialise the replica set. They’ll look something like:
mongo-1 → 10.0.0.3
mongo-2 → 10.0.0.4
mongo-3 → 10.0.0.5

Architecture of the setup
Phase 2 — Install and Configure Pritunl
Now SSH into pritunl-server. You can do this straight from the GCP console by clicking the SSH button next to the VM, or from your terminal:
gcloud compute ssh pritunl-server - zone asia-south1-a
Step 2.1 — Install Pritunl and MongoDB (Pritunl needs MongoDB locally for its own config database)
Run each block carefully:
# Add Pritunl's apt repository
sudo tee /etc/apt/sources.list.d/pritunl.list << EOF
deb https://repo.pritunl.com/stable/apt jammy main
EOF
# Add MongoDB 7.0 repository (Pritunl uses Mongo to store its own config)
curl -fsSL https://www.mongodb.org/static/pgp/server-7.0.asc \
| sudo gpg - dearmor -o /usr/share/keyrings/mongodb-server-7.0.gpg
echo "deb [ arch=amd64 signed-by=/usr/share/keyrings/mongodb-server-7.0.gpg ] \
https://repo.mongodb.org/apt/ubuntu jammy/mongodb-org/7.0 multiverse" \
| sudo tee /etc/apt/sources.list.d/mongodb-org-7.0.list
# Add Pritunl's GPG key
sudo apt-key adv - keyserver hkp://keyserver.ubuntu.com \
- recv 7568D9BB55FF9E5287D586017AE645C0CF8E292A
# Install both packages
sudo apt-get update
sudo apt-get install -y pritunl mongodb-org
# Start and enable both services
sudo systemctl enable - now pritunl
sudo systemctl enable - now mongod
Step 2.2 — Get Your Setup Key and Default Password
sudo pritunl setup-key
sudo pritunl default-password
Copy both outputs — you’ll need them in the next step.
Step 2.3 — Complete Setup in the Web UI
Open your browser and go to *https://<pritunl-server-external-ip>*. You’ll see a certificate warning — that’s expected for now, proceed past it.
You’ll be asked for the setup key. Paste it in, click Save, then log in using pritunl as the username and the default password you just copied.
Once inside the dashboard, you’re going to do four things:
1. Create an Organization
Go to Users → Add Organization. Name it whatever you like — myorg works fine. An organization in Pritunl is just a container for VPN users.
2. Create a User
Under your new organization, click Add User. Give it a name like localuser. This represents your workspace’s VPN identity. You can add a PIN for extra security, but it’s optional.
3. Create a VPN Server
Go to Servers → Add Server. Fill in:
- Name: `vpn-server`
- Port: `1194`
- Protocol: `UDP`
- Virtual Network: `192.168.100.0/24` - this is the IP range your VPN clients will get assigned from. **This must match the source range in your `allow-mongo-from-vpn` firewall rule.**
- DNS Server: `8.8.8.8`
4. Attach the Organization and Start the Server
Click on your newly created server → Attach Organization → select myorg → Attach. Then click Start Server.
Step 2.4 — Download the VPN Profile
Go back to Users, find your user (localuser), and click the chain-link icon on the right. Click Download Profile. This gives you an .ovpn file.
On your local machine, install the [Pritunl Client] (available for Mac, Windows, and Linux). Open it, click the + button, and import the .ovpn file. Then click Connect.
If everything is set up correctly, you should be assigned an IP in the 192.168.100.x range. You can verify this by running:
# Mac/Linux
ifconfig | grep 192.168.100
# Windows
ipconfig | findstr 192.168.100
# And do a quick connectivity test:
ping 10.0.0.3 # should reach mongo-1's private IP
If the ping succeeds, your VPN tunnel is working. You are now on the same private network as your MongoDB VMs.
Phase 3 — Install MongoDB on All Three Nodes
Your Mongo VMs have no public IP, so you can’t SSH into them directly from your workspace. You have two options:
-
Use the GCP Console’s browser SSH button (click the SSH button next to each VM in Compute Engine → VM Instances)
-
Or SSH from the Pritunl VM as a jump host:
# From your workspace (VPN connected)
ssh -J <user>@<pritunl-external-ip> <user>@10.0.0.3
Run the following on all three nodes — mongo-1, mongo-2, and mongo-3:
Step 3.1 — Install MongoDB 7.0
# Add the MongoDB GPG key and repository
curl -fsSL https://www.mongodb.org/static/pgp/server-7.0.asc \
| sudo gpg - dearmor -o /usr/share/keyrings/mongodb-server-7.0.gpg
echo "deb [ arch=amd64 signed-by=/usr/share/keyrings/mongodb-server-7.0.gpg ] \
https://repo.mongodb.org/apt/ubuntu jammy/mongodb-org/7.0 multiverse" \
| sudo tee /etc/apt/sources.list.d/mongodb-org-7.0.list
sudo apt-get update
sudo apt-get install -y mongodb-org
# Confirm installation
mongod - version
Step 3.2 — Generate the Keyfile (Do This on mongo-1 First)
When MongoDB runs as a replica set with authentication enabled, all nodes need to share a secret keyfile so they can verify they’re talking to each other — not some intruder. Think of it as a shared password between the nodes.
Run this on mongo-1:
# Generate a random 756-byte base64 key
openssl rand -base64 756 > /tmp/mongodb-keyfile
# Place it where MongoDB expects it
sudo cp /tmp/mongodb-keyfile /etc/mongodb-keyfile
sudo chmod 400 /etc/mongodb-keyfile
sudo chown mongodb:mongodb /etc/mongodb-keyfile
Now copy this same keyfile to mongo-2 and mongo-3. The simplest way is to print it and paste it manually, since these VMs have no direct external access:
# On mongo-1 - print the keyfile contents
cat /tmp/mongodb-keyfile
Copy the output. Then SSH into mongo-2 and mongo-3 and run:
# On mongo-2 and mongo-3 - paste the contents
sudo nano /etc/mongodb-keyfile
# Paste the key, save, then:
sudo chmod 400 /etc/mongodb-keyfile
sudo chown mongodb:mongodb /etc/mongodb-keyfile
Step 3.3 — Configure mongod.conf on All Three Nodes
The default MongoDB config binds only to 127.0.0.1, which means it refuses all connections from other machines. We need to change that.
Edit /etc/mongod.conf on each node:
sudo nano /etc/mongod.conf
Replace the relevant sections to look like this:
# mongod.conf
# Where MongoDB stores its data and logs
storage:
dbPath: /var/lib/mongodb
systemLog:
destination: file
logAppend: true
path: /var/log/mongodb/mongod.log
# Network settings
net:
port: 27017
bindIp: 0.0.0.0 # Listen on all interfaces - the firewall controls access, not this
# Process management
processManagement:
timeZoneInfo: /usr/share/zoneinfo
# Replica set config
replication:
replSetName: "rs0" # Must be identical on all three nodes
# Security
security:
authorization: enabled
keyFile: /etc/mongodb-keyfile
Note on
bindIp: 0.0.0.0: This does not mean MongoDB is exposed to the internet. Your GCP firewall rules ensure port 27017 is only reachable from the VPN subnet.bindIpjust tells MongoDB which network interface to listen on — setting it to0.0.0.0means “all interfaces,” which is what allows the other nodes and VPN clients to reach it.
Step 3.4 — Start MongoDB on All Three Nodes
sudo systemctl enable - now mongod
# Check it's running cleanly
sudo systemctl status mongod
If the status shows active (running), you’re good. If it shows a failure, check the logs:
sudo tail -50 /var/log/mongodb/mongod.log
The most common startup failure is a keyfile permissions problem. Make sure the file is owned by mongodb and has 400 permissions.
Phase 4 — Initialise the Replica Set
This is the step that turns three independent MongoDB instances into a single, coordinated replica set. You only need to do this once, from mongo-1.
SSH into mongo-1 and open the MongoDB shell:
mongosh
Step 4.1 — Initiate the Replica Set
rs.initiate({
_id: "rs0",
members: [
{ _id: 0, host: "10.0.0.3:27017" }, // mongo-1 - becomes PRIMARY
{ _id: 1, host: "10.0.0.4:27017" }, // mongo-2 - becomes SECONDARY
{ _id: 2, host: "10.0.0.5:27017" } // mongo-3 - becomes SECONDARY
]
})
Replace the IPs with your actual internal IPs from Step 1.3. MongoDB will take about 10–20 seconds to elect a primary. The shell prompt will change from > to rs0 [direct: primary]> when it’s ready.
Step 4.2 — Verify the Replica Set is Healthy
rs.status()
You’re looking for:
-
One member with ”stateStr”: “PRIMARY”
-
Two members with ”stateStr”: “SECONDARY”
-
No members showing ”stateStr”: “STARTUP” or ”stateStr”: “DOWN”
If any node shows STARTUP for more than 30 seconds, it usually means the other nodes can’t be reached. Check that the allow-internal firewall rule is in place and that mongod is running on all three VMs.
Step 4.3 — Create the Admin User
While you’re still on mongo-1 in the MongoDB shell (and the node is PRIMARY), create your admin user:
use admin
db.createUser({
user: "mongoAdmin",
pwd: "Your_password",
roles: [
{ role: "root", db: "admin" }
]
})
Pick a strong password and write it down — you’ll need it every time you connect.
To confirm authentication is working, exit and reconnect with credentials:
mongosh "mongodb://mongoAdmin:Your_password@localhost:27017/?authSource=admin&replicaSet=rs0"
Phase 5 — Connect via MongoDB Compass
This is the moment everything comes together. Make sure your Pritunl VPN is connected on your local machine, then open MongoDB Compass.
Option A — Connection String (Quickest)
In Compass, paste this into the connection string field:
mongodb://mongoAdmin:YourStrongPasswordHere@10.0.0.3:27017,10.0.0.4:27017,10.0.0.5:27017/?replicaSet=rs0&authSource=admin
Click Connect. Compass will connect to whichever node is currently primary and show your databases.
Option B — Advanced Connection (More Control)
Click Advanced Connection Options and fill in:
- **General tab → Hosts:**
- `10.0.0.3:27017`
- `10.0.0.4:27017`
- `10.0.0.5:27017`
- **General tab → Direct Connection:** Off (you want it to discover the replica set topology)
- **Authentication tab → Method:** Username/Password
- Username: `mongoAdmin`
- Password: `YourStrongPasswordHere`
- Authentication Database: `admin`
- **Advanced tab → Replica Set Name:** `rs0`
Click Connect.
Verifying Everything Works End to End
Once connected in Compass, you should see the connection in the sidebar showing rs0 with the current primary indicated. You can also verify from the terminal on any of the Mongo VMs:
// On mongo-1 (or any node)
rs.status() // Check replica set health
rs.isMaster() // See which node is primary
db.adminCommand({ replSetGetStatus: 1 }) // Detailed status
And from your local machine (VPN connected), this should work:
mongosh "mongodb://mongoAdmin:YourStrongPasswordHere@10.0.0.3:27017,10.0.0.4:27017,10.0.0.5:27017/?replicaSet=rs0&authSource=admin"

Troubleshooting Reference
Rather than leaving you with a wall of “if this then that,” here are the most common failures by symptom:
SSH times out from terminal
The allow-ssh firewall rule is missing, or the network tag doesn’t match what’s on the VM. Go to Compute Engine → VM instances → click the VM → Edit → Network tags and confirm pritunl is in the tags for the Pritunl VM. Then check your firewall rule targets that exact tag.
VPN client connects but you can’t ping Mongo VMs
The Pritunl Virtual Network CIDR (192.168.100.0/24) doesn’t match the source range in your allow-mongo-from-vpn firewall rule. They must be identical. Also double-check that the mongo network tag is applied to all three Mongo VMs.
MongoDB won’t start after configuring the keyfile
Almost always a permissions issue. Run:
sudo ls -la /etc/mongodb-keyfile
# Should show: -r - - - - 1 mongodb mongodb
If it doesn’t, fix it:
sudo chmod 400 /etc/mongodb-keyfile
sudo chown mongodb:mongodb /etc/mongodb-keyfile
sudo systemctl restart mongod
Replica set members stuck in STARTUP state
The nodes can’t reach each other on port 27017. Verify:
-
The
allow-internalfirewall rule exists and covers10.0.0.0/16on all protocols -
mongodis actually running on all three nodes (sudo systemctl status mongod) -
The
replSetNamein/etc/mongod.confis exactlyrs0on all three — any typo breaks it -
The IPs in
rs.initiate()match the actual internal IPs of the VMs
Compass connects but shows authentication error
The authSource=admin parameter is missing from your connection string, or you’re using the wrong username/password. MongoDB’s admin user is in the admin database, so authSource=admin must be explicitly specified.
What You’ve Built
At this point you have a production-grade setup:
-
A 3-node MongoDB replica set with automatic failover — if any node goes down, the remaining two elect a new primary within seconds
-
Authentication enforced on all nodes, both for client connections and internal replication
-
All database traffic completely off the public internet, accessible only through your VPN tunnel
-
A clean GCP firewall posture where each rule has a specific, understood purpose
Your Compass connection goes through your workspace → Pritunl VPN tunnel → GCP private network → MongoDB primary, and MongoDB replication happens entirely within the private VPC on port 27017, invisible to the outside world.
- When you’re ready to migrate the workload to your own server, the MongoDB setup stays entirely untouched. All you’ll need to do is open port 27017 in the GCP firewall for your server’s static IP, and point your connection string at the same Mongo IPs — nothing on the database side changes at all.
메타데이터
- post_id
- 865a7bc641db
- slug
- your-mongodb-is-one-port-scan-away-from-disaster-heres-how-to-actually-secure-it-on-gcp-865a7bc641db
- url
- https://medium.com/@goyalaryan51/your-mongodb-is-one-port-scan-away-from-disaster-heres-how-to-actually-secure-it-on-gcp-865a7bc641db
- canonical_url
- https://medium.com/@goyalaryan51/your-mongodb-is-one-port-scan-away-from-disaster-heres-how-to-actually-secure-it-on-gcp-865a7bc641db
- author_url
- https://medium.com/@goyalaryan51
- status
- ok
- fetched_at
- 2026-06-15 20:49:13