MongoDB Connection String: URI and mongodb+srv
Chat2DB TeamAlmost every MongoDB connection problem comes down to one line of text: the connection string. A single unencoded @ in a password, a missing authSource, or a mongodb+srv:// URI pointed at a hostname without SRV records is enough to turn a five-minute setup into an hour of reading stack traces.
This guide walks through the MongoDB URI format piece by piece, explains the difference between the standard mongodb:// scheme and the DNS seed list mongodb+srv:// scheme, covers the connection options you will actually use in production, and shows working examples for mongosh, Node.js, and Python. It finishes with the three errors developers hit most often and how to diagnose each one.
The anatomy of a MongoDB connection string
A MongoDB connection string is a URI. The general shape of the standard format is:
mongodb://[username:password@]host1[:port1][,host2[:port2],...][/defaultauthdb][?option1=value1&option2=value2]Each part has a specific job:
- Scheme —
mongodb://ormongodb+srv://. This decides how hosts are discovered. - Credentials —
username:password@. Optional; omit it for a local server without authentication. - Host list — one or more
host:portpairs separated by commas. The port defaults to27017. - Default database — the path after the host list, for example
/inventory. The driver uses it as the default database for operations and, unless you override it, as the authentication database. - Options — a query string of
key=valuepairs joined with&.
A minimal local connection string looks like this:
mongodb://localhost:27017A realistic production string for a three-member replica set looks like this:
mongodb://app_user:S3cretPass@db1.example.com:27017,db2.example.com:27017,db3.example.com:27017/inventory?replicaSet=rs0&authSource=admin&tls=true&w=majority&retryWrites=trueOption names are case-insensitive in the MongoDB URI specification, but the camelCase spelling (replicaSet, authSource, readPreference) is the conventional form and what you will see in documentation, so stick with it.
mongodb:// vs mongodb+srv://
The two schemes point to the same servers. They differ in how the client finds those servers.
The standard mongodb:// format
With mongodb://, you list every seed host explicitly. The driver connects to one or more of them, reads the replica set configuration, and then discovers the rest of the topology. You do not strictly need to list every member, but listing several means the driver can still bootstrap if one host is down.
Use the standard format when:
- You connect to a local or self-hosted server.
- Your DNS does not publish SRV records for the cluster.
- You need to connect to a single member directly (see
directConnectionbelow). - You are on a network where SRV and TXT lookups are blocked or unreliable.
The mongodb+srv:// DNS seed list format
With mongodb+srv://, you provide exactly one hostname and no port:
mongodb+srv://app_user:S3cretPass@cluster0.abcde.mongodb.net/inventory?retryWrites=true&w=majorityThe driver then performs DNS lookups:
- An SRV lookup for
_mongodb._tcp.cluster0.abcde.mongodb.net, which returns the actual hostnames and ports of the cluster members. - A TXT lookup on
cluster0.abcde.mongodb.net, which can supply default options. The TXT record may only setauthSource,replicaSet, andloadBalanced.
Three behaviors of the SRV format surprise people:
- TLS is enabled by default. A
mongodb+srv://connection impliestls=true. You can explicitly settls=false, but that is rarely what you want. - You cannot specify a port or more than one host. Doing so is a parse error.
- Options in the URI override TXT options. If the TXT record says
authSource=adminand your URI saysauthSource=inventory, the URI wins.
MongoDB Atlas hands out mongodb+srv:// strings by default because it lets Atlas change the underlying hosts without you editing your configuration. If you are running your own replica set, you can publish your own SRV records and use the same format.
URL-encoding special characters in passwords
The connection string is a URI, so certain characters in the username or password must be percent-encoded. If your password contains any of these, encode them:
| Character | Encoded |
|---|---|
: | %3A |
/ | %2F |
? | %3F |
# | %23 |
[ | %5B |
] | %5D |
@ | %40 |
% | %25 |
For example, the password p@ss:w/rd#1 becomes p%40ss%3Aw%2Frd%231:
mongodb://app_user:p%40ss%3Aw%2Frd%231@db1.example.com:27017/?authSource=adminThe most common symptom of forgetting this step is not a clear "bad password" message. An unencoded @ makes the parser treat part of the password as a hostname, so you get a DNS error or a confusing "invalid host" message instead.
Do not encode by hand. Let the language do it:
// Node.js
const user = encodeURIComponent("app_user");
const pass = encodeURIComponent("p@ss:w/rd#1");
const uri = `mongodb://${user}:${pass}@db1.example.com:27017/?authSource=admin`;# Python
from urllib.parse import quote_plus
user = quote_plus("app_user")
password = quote_plus("p@ss:w/rd#1")
uri = f"mongodb://{user}:{password}@db1.example.com:27017/?authSource=admin"A cleaner alternative in most drivers is to pass the credentials as separate client options instead of embedding them in the URI. Then no encoding is necessary, and the password does not end up in logs that print the URI.
Connection options that matter
MongoDB supports dozens of URI options. These are the ones worth understanding before you ship.
authSource and authMechanism
authSource names the database where the user was created. If you omit it, the driver uses the default database from the path, and if there is no path it falls back to admin.
This is the source of a classic bug: the user was created in admin, but the URI ends in /inventory, so the driver tries to authenticate against inventory and fails. The fix is to add authSource=admin:
mongodb://app_user:secret@db1.example.com:27017/inventory?authSource=adminauthMechanism is usually negotiated automatically (SCRAM-SHA-256 on modern servers). You set it explicitly for things like MONGODB-X509, MONGODB-AWS, or GSSAPI (Kerberos).
replicaSet
replicaSet=rs0 tells the driver the name of the replica set it expects. With the standard format, setting it makes the driver treat the host list as seeds for that set and verify that the servers really belong to it. With SRV, Atlas usually provides it via the TXT record.
tls, tlsCAFile, and tlsAllowInvalidCertificates
tls=trueenables TLS. (ssl=trueis the older alias.)tlsCAFile=/path/to/ca.pempoints to a custom certificate authority, which you need for self-signed or internal CAs.tlsCertificateKeyFile=/path/to/client.pemprovides a client certificate for X.509 authentication.tlsAllowInvalidCertificates=trueandtlsAllowInvalidHostnames=truedisable verification. They are useful for a quick local test and dangerous anywhere else.
readPreference
readPreference controls which replica set members serve reads:
primary(default) — all reads go to the primary.primaryPreferred— primary if available, otherwise a secondary.secondary— only secondaries.secondaryPreferred— secondaries if available, otherwise the primary.nearest— the member with the lowest network latency, primary or secondary.
Reading from secondaries can return slightly stale data because replication is asynchronous. Use it for analytics or reporting queries that tolerate lag, not for read-after-write flows.
w, retryWrites, and retryReads
w sets the default write concern. w=majority means a write is acknowledged only after a majority of voting members have it, which protects it from being rolled back on failover. w=1 acknowledges after the primary alone.
retryWrites=true lets the driver automatically retry certain write operations once after a transient network error or primary election. Modern drivers enable it by default, but many URIs still include it explicitly, which does no harm. retryReads works the same way for reads.
maxPoolSize, minPoolSize, and timeouts
Each MongoClient maintains a connection pool per server. maxPoolSize caps it; most official drivers default to 100. If you run many application instances, multiply maxPoolSize by the instance count and compare the result with the server's connection limits before raising it.
Useful timeout options:
serverSelectionTimeoutMS— how long the driver waits to find a suitable server before throwing. Many drivers default to 30 seconds; lowering it to a few seconds in development makes failures surface faster.connectTimeoutMS— TCP connection timeout.socketTimeoutMS— how long a socket may wait for a response.maxIdleTimeMS— how long an idle pooled connection is kept.
directConnection
By default, a driver given a replica set member will discover the whole set and route operations accordingly. directConnection=true disables discovery and talks only to the single host you named. You need it when:
- You connect to a specific secondary for maintenance.
- The replica set advertises internal hostnames that your machine cannot resolve (common with Docker and Kubernetes port forwarding).
mongodb://localhost:27017/?directConnection=truedirectConnection=true cannot be combined with mongodb+srv:// or with multiple hosts.
Connecting from mongosh
The MongoDB Shell accepts the connection string as its first argument. Quote it so your shell does not interpret & or ?:
mongosh "mongodb://db1.example.com:27017,db2.example.com:27017/inventory?replicaSet=rs0&authSource=admin" \
--username app_userIf you pass --username without --password, mongosh prompts for the password, which keeps it out of your shell history. The SRV form works the same way:
mongosh "mongodb+srv://cluster0.abcde.mongodb.net/inventory" --username app_userOnce connected, confirm where you are:
db.getName()
db.runCommand({ connectionStatus: 1 })
db.hello()connectionStatus shows the authenticated user and roles, and hello shows whether you landed on a primary and which members the set contains.
Connecting from Node.js
Install the official driver with npm install mongodb, then:
const { MongoClient } = require("mongodb");
const uri = process.env.MONGODB_URI;
// e.g. mongodb+srv://cluster0.abcde.mongodb.net/inventory?retryWrites=true&w=majority
const client = new MongoClient(uri, {
auth: {
username: process.env.MONGODB_USER,
password: process.env.MONGODB_PASSWORD,
},
authSource: "admin",
maxPoolSize: 20,
serverSelectionTimeoutMS: 5000,
});
async function main() {
await client.connect();
const db = client.db("inventory");
const count = await db.collection("products").countDocuments();
console.log(`products: ${count}`);
}
main()
.catch(console.error)
.finally(() => client.close());Two practical points. First, create one MongoClient per process and reuse it; the client owns the connection pool, so creating one per request exhausts connections quickly. Second, options passed in the constructor are merged with URI options, which is a convenient way to keep secrets out of the string.
Connecting from Python with PyMongo
Install with pip install pymongo. For mongodb+srv:// URIs, PyMongo needs the dnspython package; recent PyMongo releases install it as a dependency, while older ones require pip install "pymongo[srv]".
import os
from pymongo import MongoClient
from pymongo.errors import ServerSelectionTimeoutError
client = MongoClient(
os.environ["MONGODB_URI"],
username=os.environ["MONGODB_USER"],
password=os.environ["MONGODB_PASSWORD"],
authSource="admin",
maxPoolSize=20,
serverSelectionTimeoutMS=5000,
)
try:
client.admin.command("ping")
print("connected")
db = client["inventory"]
print(db.products.count_documents({}))
except ServerSelectionTimeoutError as exc:
print(f"could not reach MongoDB: {exc}")Note that MongoClient connects lazily in the background. The constructor succeeding does not mean the server is reachable, which is why the ping command is a good first operation.
Building connection strings without typos
Long URIs with a handful of options are easy to get subtly wrong: a ? where an & should be, a password encoded twice, directConnection combined with SRV. If you would rather fill in fields than hand-assemble the string, Chat2DB has a free MongoDB Connection String Builder (opens in a new tab) that handles encoding and option formatting for you. Once the string works, you can paste it into Chat2DB (opens in a new tab) to browse collections and run queries with an AI assistant alongside a regular client.
Troubleshooting common connection errors
Authentication failed
MongoServerError: Authentication failed.Work through these in order:
- Wrong
authSource. Check which database the user lives in. From an admin session, rundb.getSiblingDB("admin").system.users.find({ user: "app_user" }, { user: 1, db: 1 }). Thedbfield is yourauthSource. - Unencoded special characters in the password. Encode them or pass credentials as separate options.
- Wrong password or user. Test the same credentials in mongosh with the
--usernameprompt so the shell does no URI parsing. - Mechanism mismatch. Rare on modern servers, but a user created with only SCRAM-SHA-1 credentials and a client forcing SCRAM-SHA-256 will fail.
Server selection timeout
MongoServerSelectionError: connection timed outor in Python, ServerSelectionTimeoutError. The driver could not find a suitable server within serverSelectionTimeoutMS. Common causes:
- Network access. A firewall, security group, or Atlas IP access list is blocking your client's IP. Test with
nc -vz db1.example.com 27017. - Replica set hostname mismatch. The seeds are reachable, but the set advertises hostnames your client cannot resolve, so discovery fails. Add
directConnection=truefor a single-node test, or fix the advertised hostnames in the replica set config. - TLS mismatch. The server requires TLS and the URI does not enable it, or vice versa.
- Read preference with no eligible member.
readPreference=secondaryon a set whose secondaries are all down will time out.
querySrv ENOTFOUND
Error: querySrv ENOTFOUND _mongodb._tcp.cluster0.abcde.mongodb.netThe SRV lookup failed. Check:
- The hostname is spelled correctly and the cluster still exists.
- Your DNS resolver can answer SRV queries. Test with
nslookup -type=SRV _mongodb._tcp.cluster0.abcde.mongodb.netordig SRV _mongodb._tcp.cluster0.abcde.mongodb.net. - A corporate network, VPN, or some home routers do not block or mangle SRV lookups. Switching to a public resolver is a quick test.
If SRV lookups are impossible on your network, you can use the standard mongodb:// format instead. Resolve the SRV record from a machine where it works, list the returned hosts and ports explicitly, and add tls=true, replicaSet, and authSource yourself, since those came from SRV and TXT before.
A checklist before you ship
- Pick the scheme deliberately:
mongodb+srv://for Atlas or DNS-managed clusters,mongodb://otherwise. - Percent-encode credentials, or better, pass them as separate options from environment variables.
- Set
authSourceexplicitly whenever the user is not defined in the default database. - Enable TLS for anything outside localhost, and do not ship
tlsAllowInvalidCertificates=true. - Use
w=majorityfor data you cannot afford to lose on failover. - Size
maxPoolSizeagainst the total number of application instances. - Reuse one client per process and lower
serverSelectionTimeoutMSin development so failures appear quickly.
Get these right once, put the string in configuration, and the connection layer will rarely be the thing you debug.
