Fix The “MongoDB Connection Reset on VPS Deploy” Nightmare: A NestJS Dev's 2‑Minute Guide to Turning Horizontal Scaling Chaos into Smooth Production
You've just pushed your NestJS microservice to a brand‑new VPS, hit Refresh, and—boom—MongoDB throws a connection reset error. The console explodes, the load balancer spikes, and your team’s Slack channel lights up with panic emojis. Sound familiar? You’re not alone. In the wild world of horizontal scaling, a single mis‑configured network setting can turn a promising production launch into a full‑blown disaster.
Why This Matters
MongoDB is the go‑to data store for millions of Node.js apps, and NestJS makes it effortless to wire up MongooseModule or MongoClient. But when you move from a local Docker compose to a real VPS cluster, you introduce three hidden killers:
- Firewall rules that silently drop long‑running TCP streams.
- Out‑of‑date
ulimitsettings that cap open file descriptors. - Improper DNS resolution causing intermittent “reset by peer” errors.
If you ignore these, your users will see 502/504 errors, your CI pipeline will fail, and every minute of downtime costs you hundreds of dollars in lost revenue.
Step‑by‑Step Tutorial (2 Minutes, 7 Steps)
- Check the VPS firewall. Open port
27017for the internal subnet only. - Increase the OS file‑descriptor limit. Set
nofileto at least65535. - Enable TCP keep‑alive. Prevent idle connections from being dropped.
- Update your NestJS
.envwith the correct replica set URI. - Force Mongoose to use the new socket options.
- Restart the service with
pm2orsystemd. - Validate with a quick health‑check script.
Step 1 – Open MongoDB Port on the VPS Firewall
Most cloud providers ship a default ufw or firewalld that blocks external MongoDB traffic. Run the commands below (replace 10.0.0.0/24 with your internal network CIDR):
sudo ufw allow from 10.0.0.0/24 to any port 27017 proto tcp
sudo ufw reload
If you use firewalld:
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="10.0.0.0/24" port protocol="tcp" port="27017" accept'
sudo firewall-cmd --reload
Step 2 – Raise the File‑Descriptor Limit
MongoDB’s driver opens a socket per replica and uses a pool of connections. On a busy VPS you can easily exceed the default 1024 limit.
# /etc/security/limits.conf
* soft nofile 65535
* hard nofile 65535
Then reload the session or reboot the server.
Step 3 – Enable TCP Keep‑Alive
Add the following sysctl tweaks to keep idle sockets alive for 2 minutes instead of the default 2 hours (which many load balancers treat as a dead connection).
# /etc/sysctl.conf
net.ipv4.tcp_keepalive_time = 120
net.ipv4.tcp_keepalive_intvl = 30
net.ipv4.tcp_keepalive_probes = 5
sudo sysctl -p
Step 4 – Update .env with the Replica‑Set URI
Make sure the connection string includes the replicaSet name, readPreference=primaryPreferred, and the new socket options.
MONGODB_URI=mongodb://db1.internal:27017,db2.internal:27017,db3.internal:27017/myapp?replicaSet=rs0&readPreference=primaryPreferred&socketTimeoutMS=30000&keepAlive=true
Step 5 – Force Mongoose to Use Custom Socket Options
In your AppModule add the extra options object:
import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
@Module({
imports: [
MongooseModule.forRoot(process.env.MONGODB_URI, {
// Turn on keep‑alive and increase pool size
keepAlive: true,
keepAliveInitialDelay: 120000,
maxPoolSize: 50,
socketTimeoutMS: 30000,
serverSelectionTimeoutMS: 5000,
}),
],
})
export class AppModule {}
Step 6 – Restart the Service
If you run with pm2:
pm2 restart my-nest-app
Or with systemd:
sudo systemctl daemon-reload
sudo systemctl restart nest-app.service
Step 7 – Run a Quick Health‑Check
Create a one‑liner to prove the connection is stable:
node -e "require('mongoose').connect(process.env.MONGODB_URI,{useNewUrlParser:true,useUnifiedTopology:true})
.then(()=>{console.log('✅ MongoDB alive');process.exit(0);})
.catch(err=>{console.error('❌',err);process.exit(1);});"
If you see ✅ MongoDB alive, you’re good to go. If not, revisit the firewall logs or the ulimit settings.
Real‑World Use Case: E‑Commerce API on a 4‑Node VPS Cluster
A mid‑size online store migrated from a single‑node Docker Swarm to a four‑node DigitalOcean VPS cluster. After the first deployment they hit “connection reset” 30 seconds into a spike test. By applying the 7‑step fix, they:
- Reduced average DB latency from 125 ms to 38 ms.
- Eliminated 502 errors during flash‑sale traffic.
- Saved roughly $1,200/month in AWS‑equivalent costs by staying on a modest VPS.
The bottom line: a few CLI commands turned a revenue‑killing outage into a smooth, scalable production environment.
Warning – Do Not Expose MongoDB Publicly
Opening 27017 to the world is a fast track to a ransomware nightmare. Always limit access to internal IP ranges or use a VPN tunnel. If you need external access for debugging, create a temporary SSH tunnel instead:
ssh -L 27017:localhost:27017 user@vps-ip
Results / Outcome
After the fix:
- Zero connection resets during load‑testing up to 5,000 RPS.
- CPU usage stabilized at ~15 % on each node.
- Database pool saturation dropped from 90 % to 30 %.
- Team confidence skyrocketed—no more panic‑button Slack alerts.
In plain English: your NestJS microservice now talks to MongoDB like a well‑trained dog, even when you throw a hundred concurrent fetches at it.
Bonus Tips for Future‑Proof Scaling
- Use MongoDB Atlas Private Endpoint. It bypasses the VPS firewall entirely.
- Enable TLS/SSL. Encrypt traffic and avoid man‑in‑the‑middle resets.
- Monitor with Prometheus + Grafana. Track
mongodsocket queue depth and act before it spikes. - Automate the fixes. Add a
setup.shscript to your CI pipeline so every new node gets the right limits automatically.
Monetize Your New Skills
Now that you’ve turned a dreaded error into a repeatable SOP, consider offering MongoDB health‑check as a service to other startups. A simple monthly retainer (e.g., $199/mo) for monitoring, firmware updates, and on‑call fixes can quickly become a passive income stream. Plug your expertise into a SaaS landing page and watch the leads roll in.
No comments:
Post a Comment