RE:NODE

MongoDB URI format: mongodb+srv, hosts and options

By RE:NODE Team
16 min read


A MongoDB URI is mongodb://[user:password@]host1[:port1][,host2[:port2],...][/database][?options]. With nothing filled in, every official tool assumes mongodb://localhost:27017. The mongodb+srv:// form takes exactly one hostname and no port, asks DNS for the real host list, and switches TLS on by default. Several hosts separated by commas are a seed list for a replica set or a set of mongos routers, not a list of fallback servers, and the driver will connect to whichever member it decides is right - which is why directConnection exists.

That is the whole format. The rest of this reference covers what each piece does in practice: the defaults, the DNS records behind +srv, how the driver treats more than one host, the options that change behaviour, and where the string goes in PyMongo, the Node driver, mongosh, .NET configuration files and Compass. For a gentler walk through the URI parts and percent-encoding passwords, start with MongoDB connection strings. If you have a URI and the server says Authentication failed, go to the authSource troubleshooting guide instead; this post does not repeat either.

The format, piece by piece#

code
mongodb://                                   schemeshopapp:pw@                                  credentials (optional)db1.example.net:27017,db2.example.net:27017  host list/shop                                        default database (optional)?replicaSet=rs0&appName=api                  options (optional)

Joined together with nothing in between, that is mongodb://shopapp:pw@db1.example.net:27017,db2.example.net:27017/shop?replicaSet=rs0&appName=api.

The rules that are easy to forget:

  • The port is optional and defaults to 27017 for every host that does not give one. mongodb://db.example.net and mongodb://db.example.net:27017 are the same thing.
  • An IPv6 literal goes in square brackets: mongodb://[2001:db8::10]:27017. Without the brackets the colons in the address are read as a port separator and the URI fails to parse.
  • A Unix domain socket is written as its percent-encoded path in the host position: mongodb://%2Ftmp%2Fmongodb-27017.sock. mongod creates that socket by default on Linux and macOS, and it is a handy way to connect from the same machine without opening a TCP port at all.
  • The slash before ? is required whenever there are options. mongodb://host:27017/?tls=true is valid; drivers disagree on how they handle a missing slash, so do not rely on it.
  • Option names are case-insensitive (authsource and authSource are the same), values are not, and booleans are the lowercase strings true and false. Some drivers also accept 1, yes and friends, but that is not portable.
  • Options are joined with &. A few options may repeat, such as readPreferenceTags; for the rest, the last one wins or the driver raises an error, depending on the driver.

The default: localhost:27017

mongodb://localhost:27017 is the string in every tutorial, the default in Compass's new connection screen and what MongoClient() with no arguments connects to in PyMongo. It has one well-known trap. On Node.js 17 and later, localhost can resolve to the IPv6 address ::1 first, while a default mongod binds only to 127.0.0.1. The result is connect ECONNREFUSED ::1:27017 against a server that is running perfectly well. Newer Node releases try both address families and make it less common, but the robust fix is to write 127.0.0.1 instead of localhost in local URIs, or to add ::1 to net.bindIp in mongod.conf.

mongosh with no arguments does not use the bare default either. It connects to mongodb://127.0.0.1:27017/?directConnection=true&serverSelectionTimeoutMS=2000, and prints that string as its first line, which is a decent hint about which options are worth setting when you are only ever talking to one local server.

Which database the path names

The /database part is the default database for the client and the default for authSource when that option is absent. It does not restrict anything: a client connected with /shop can still open client["reports"] if the user has rights to it. Leave the path empty and the default database is driver-specific (often test or none at all), while authSource falls back to admin. That second default is the source of most authentication confusion, and the users and roles guide explains why users live where they do.

mongodb+srv:// and the DNS records behind it#

A seed list URI (mongodb+srv://) replaces the host list with one DNS name. When the driver sees it, it makes two lookups before it opens a single connection.

hosts and portsdefault optionsnormal discoverymongodb+srv URIcluster0.example.netSRV lookup_mongodb._tcp.cluster0...TXT lookupcluster0.example.netHost list + optionstls=true impliedReplica set members:27017 each
What a driver does with a mongodb+srv URI

The SRV record lives at _mongodb._tcp.<hostname> and lists one target hostname and port per member. The TXT record lives at the hostname itself and holds URI options in query-string form, such as authSource=admin&replicaSet=rs0. Only three options are allowed there - authSource, replicaSet and loadBalanced - and there may be only one TXT record; more than one is an error. Anything written in the URI itself overrides the TXT record.

You can see exactly what a driver will see with ordinary DNS tools:

bash
$ dig +short SRV _mongodb._tcp.cluster0.example.net0 0 27017 shard-00-00.example.net.0 0 27017 shard-00-01.example.net.0 0 27017 shard-00-02.example.net.$ dig +short TXT cluster0.example.net"authSource=admin&replicaSet=rs0"

On Windows, nslookup -type=SRV _mongodb._tcp.cluster0.example.net does the same job. If the SRV query comes back empty, the error in your application will be querySrv ENOTFOUND (Node) or a ConfigurationError mentioning the DNS name (PyMongo), and no change to options will help.

The rules that follow from the design:

  • Exactly one hostname, and no port. The ports come from the SRV records.
  • The hostnames the SRV record returns must sit under the same parent domain as the name you queried. cluster0.example.net may point at shard-00-00.example.net, but not at db.othercompany.com; drivers reject those targets as a security measure. Older drivers also insisted on at least three dot-separated parts in the name, which newer releases relaxed - check your driver's changelog if a two-part name fails.
  • TLS is on by default. Add tls=false explicitly if you really mean an unencrypted connection, which with +srv you almost never do.
  • directConnection=true is not allowed with +srv, because the whole point is that the driver discovers several servers.
  • Two newer options tune the lookup: srvServiceName (default mongodb) changes the _mongodb part of the record name, and srvMaxHosts limits how many of the returned hosts are used, which is meant for large sets of mongos routers.

When connected to sharded clusters through mongos, drivers re-query the SRV record periodically and add or drop routers as the records change. For a replica set they do not need to, because the set itself reports its membership once the first connection is made.

mongodb+srv:// is worth using when someone maintains those records for you - a managed cluster whose members change, or your own replica set with records you publish in your DNS zone. It is not worth it for one server. A single database with an A record and mongodb://db.example.net:27017 is simpler, has no extra lookups and fails in fewer interesting ways. DNS records explained covers the A, TXT and SRV types if you want to publish them yourself.

Multiple hosts and replicaSet#

A comma-separated host list is a seed list. The driver connects to the hosts it was given, asks each one for the replica set's current view of itself (the hello command), and from then on works from what the server says rather than from the URI.

code
mongodb://db1.example.net:27017,db2.example.net:27017,db3.example.net:27017/shop?replicaSet=rs0

What that means in practice:

  • You do not have to list every member. One reachable member is enough for discovery, though listing all of them means start-up still works when the first one is down.
  • The order of hosts does not decide where writes go. Writes always go to the current primary; reads go where readPreference sends them.
  • replicaSet must match the set's actual name (the _id given to rs.initiate()). A member reporting a different set name is dropped from the topology, and if every member is dropped you get a server selection timeout rather than a clear message. db.hello().setName in mongosh tells you the real name.
  • The hostnames the driver ends up using are the ones in the replica set configuration, not the ones in your URI. If the set was initiated with localhost:27017 or with Docker-internal container names, a client outside that network discovers hostnames it cannot resolve, and times out after a successful first contact. Fix the member hostnames in the replica set configuration with rs.reconfig(); do not paper over it on the client.

For a sharded cluster the host list names mongos routers instead, there is no replicaSet option, and the driver spreads operations across the routers it can reach, preferring the ones with the lowest latency.

directConnection

directConnection=true tells the driver to talk only to the one host in the URI and skip discovery. It is allowed with a single host only, and never with +srv. Use it when:

  • You want to connect to a specific member, usually a secondary, for maintenance or for running a query without touching the primary.
  • The replica set's configured hostnames are unreachable from where you are, such as through an SSH tunnel to one member. SSH tunnels and port forwarding covers that setup.

The default matters, and it changed. In PyMongo 4 and the Node driver from 4.0, directConnection defaults to false, so a URI with a single host that happens to be a replica set member gets discovery: the driver finds the primary and sends writes there. PyMongo 3 behaved as if it were true for a single host. Code that relied on "connect to that box only" after an upgrade suddenly starts talking to a different server, so set it explicitly whenever the distinction matters. On a standalone server, which reports no replica set at all, both values end up in the same place.

loadBalanced=true is the third topology option, for clusters behind a load balancer that speaks the MongoDB protocol (used by some managed serverless offerings). It cannot be combined with replicaSet, directConnection=true or more than one host, and you will only ever see it in a URI a provider hands you.

The options that change behaviour#

The full list of URI options is long. These are the ones that appear in real connection strings and change what the client does.

OptionDefaultEffect
authSourcepath db, else adminDatabase holding the user
tlsfalse; true for +srvEncrypts the connection
retryWritestrueRetries a failed write once
wserver defaultWrite acknowledgement
readPreferenceprimaryWhich members serve reads
maxPoolSize100Connections per server
connectTimeoutMSdriver-specificTCP connect timeout
appNamenoneLabel in server logs
directConnectionfalseSkip topology discovery

authSource

Names the database where the user was created. The defaulting rules are above, and the troubleshooting is in the authentication guide. The only thing to add here is the command-line equivalent: mongosh accepts --authenticationDatabase, which is the same setting under another name.

tls

tls=true encrypts the connection and verifies the server's certificate against the system trust store, or against the CA named in tlsCAFile. ssl=true is the older alias and still works. The server has to be configured for TLS first; turning it on in the URI against a server that is not fails at the handshake and usually surfaces as a timeout. tlsAllowInvalidCertificates=true and tlsInsecure=true exist for testing and should never reach production.

retryWrites and w

retryWrites=true has been the default in drivers since the MongoDB 4.2 era. A write that fails with a network error or a primary election is retried once, safely, because the server tracks the operation's identity. Retryable writes need a replica set or sharded cluster; on a standalone server the driver simply does not retry. You will occasionally see retryWrites=false in strings written for old servers or unusual storage engines - leave it at the default unless a server error tells you otherwise.

w sets the write concern: w=1 means the primary acknowledged the write, w=majority means a majority of voting members did, and w=0 means nobody did and you will never hear about failures. Since MongoDB 5.0 the implicit server default for most replica sets is majority, so leaving w out of the URI is usually right. journal=true adds "and it is on disk in the journal", and wtimeoutMS stops a majority write from waiting forever when too few members are up. On a single server majority is satisfied by that server alone.

readPreference

Accepts primary, primaryPreferred, secondary, secondaryPreferred and nearest. Anything other than primary can return data that is slightly behind, because secondaries replicate asynchronously. maxStalenessSeconds (minimum 90) excludes secondaries that are too far behind, and readPreferenceTags=dc:east steers reads to members with matching tags. With a single host it has nothing to choose between.

maxPoolSize and connectTimeoutMS

maxPoolSize is per server, per client. One client talking to a three-member replica set can open up to three hundred connections at the default, and every process in your application has its own client. Set it deliberately; connection pools and limits has the arithmetic. minPoolSize keeps connections warm and maxIdleTimeMS closes idle ones.

connectTimeoutMS is the TCP connect timeout for each connection, and its default is one of the few values that differs between drivers: the shared specification says 10 seconds, PyMongo uses 20 and the Node driver 30. It is not the setting that makes a bad URI hang - that is serverSelectionTimeoutMS, 30 seconds everywhere. Newer drivers also support timeoutMS, a single client-side limit for the whole operation, which is intended to replace several of the older per-stage timeouts.

appName

A free-text label, up to 128 bytes, sent in the connection handshake. It shows in the server log for every connection and in db.currentOp(), so you can see which service opened which connections. It costs nothing; set it on every client.

Building URIs for common setups#

Most strings fall into one of a handful of shapes. Reading these side by side is quicker than reading the specification.

code
# Local developmentmongodb://127.0.0.1:27017/shop# One remote server, user in adminmongodb://shopapp:pw@db.example.net:27017/shop?authSource=admin&appName=api# Three-member replica set, reads to secondaries allowedmongodb://shopapp:pw@db1.example.net,db2.example.net,db3.example.net/shop  ?replicaSet=rs0&authSource=admin&readPreference=secondaryPreferred# One member only, through an SSH tunnel on local port 27018mongodb://shopapp:pw@127.0.0.1:27018/shop?directConnection=true&authSource=admin# Managed cluster published in DNSmongodb+srv://shopapp:pw@cluster0.example.net/shop?appName=api# Same cluster written out by hand (what +srv was doing for you)mongodb://shopapp:pw@shard-00-00.example.net:27017,shard-00-01.example.net:27017  ,shard-00-02.example.net:27017/shop?replicaSet=rs0&authSource=admin&tls=true

The line breaks in the replica set examples are for reading only; a real URI is one line with no spaces. The last example is the one to remember when DNS is the problem: converting +srv to a plain list means copying the hosts from the SRV record, the options from the TXT record, and adding tls=true yourself. Miss any of the three and the connection fails in a different way.

Where the string goes in each client#

Every official driver accepts the same URI, which is the point of having one. What differs is where the string is read from and what overrides what.

PyMongo

python
import osfrom pymongo import MongoClientclient = MongoClient(os.environ["MONGODB_URI"], appName="reports", maxPoolSize=10)db = client.get_default_database()   # the /database from the URI

Keyword arguments and URI options are interchangeable, and when the same option appears in both, the keyword argument wins. get_default_database() returns the database named in the path, and raises if there is none. To check a string without connecting, pymongo.uri_parser.parse_uri(uri) returns the parsed hosts, credentials and options as a dictionary and raises on anything malformed, including a +srv name with no records.

Node.js driver

javascript
import { MongoClient } from "mongodb";const client = new MongoClient(process.env.MONGODB_URI, { appName: "api" });const db = client.db(); // the /database from the URI

The second argument takes the same options in camelCase. client.db() with no name returns the URI's default database. Mongoose passes the string straight through with mongoose.connect(uri). The parsing is done by the mongodb-connection-string-url package, which the driver depends on and which you can use yourself to inspect a URI or mask its password before logging it.

mongosh

bash
$ mongosh 'mongodb://db1.example.net,db2.example.net/shop?replicaSet=rs0' \    -u shopapp -p --authenticationDatabase admin

Quote the URI with single quotes so the shell leaves & and $ alone. mongosh also accepts the replica set in the older --host rs0/db1.example.net:27017,db2.example.net:27017 form. Once connected, db.hello() shows setName, the member list and which one is primary, which settles any doubt about what the driver will discover.

.NET: appsettings.json and web.config

The C# driver takes the URI as a string, and in ASP.NET Core it belongs in the ConnectionStrings section:

appsettings.json
{  "ConnectionStrings": {    "Mongo": "mongodb://shopapp:pw@db.example.net:27017/shop?authSource=admin"  }}
Program.cs
var cs = builder.Configuration.GetConnectionString("Mongo");var url = new MongoUrl(cs);builder.Services.AddSingleton<IMongoClient>(new MongoClient(url));builder.Services.AddSingleton(sp =>    sp.GetRequiredService<IMongoClient>().GetDatabase(url.DatabaseName));

MongoUrl parses the string, and its DatabaseName gives you the path database. Register the client as a singleton for the same reason as every other driver: it is a pool. In production, set the value as the environment variable ConnectionStrings__Mongo rather than writing the password into the file; environment variables and secrets covers the pattern.

On .NET Framework the string lives in web.config or app.config and is read with ConfigurationManager.ConnectionStrings["Mongo"].ConnectionString. That file is XML, so every & between options has to be written &amp;:

web.config
<connectionStrings>  <add name="Mongo"       connectionString="mongodb://db.example.net:27017/shop?authSource=admin&amp;appName=legacy" /></connectionStrings>

A raw & there makes the whole configuration file fail to load, which tends to be reported as an application start-up error with no mention of MongoDB. The same idea for SQL Server strings is in SQL Server connection strings.

Compass

Paste the URI into the box on the new connection screen. "Advanced Connection Options" splits it into tabs: the General tab has the scheme switch between mongodb and mongodb+srv, the host list and a Direct Connection checkbox; Authentication holds the username, password and authentication database; TLS/SSL and Proxy/SSH have their own tabs. Edits in the tabs rewrite the string and the reverse, which makes Compass a reasonable way to see how a URI you were sent is being interpreted - including whether it parsed the host list the way you meant.

Mistakes that are about the format itself#

These are the errors caused by the shape of the string rather than by credentials or the network.

  • A port on a `+srv` URI. mongodb+srv://cluster0.example.net:27017 is rejected outright. Remove the port.
  • Several hosts on a `+srv` URI. Also rejected; +srv takes one name.
  • `directConnection=true` with several hosts. Rejected, because there is no single server to connect to directly.
  • A `replicaSet` name that does not match. Server selection times out after 30 seconds while every host was reachable. Compare with db.hello().setName.
  • Fallback thinking. Listing a production server and a backup server in one URI does not create failover between unrelated machines. Unless they are members of the same replica set, the driver sees two topologies that disagree and you get errors, not redundancy.
  • Spaces or line breaks. Copying a wrapped URI out of a document or a chat message brings a space or newline with it. Drivers report an invalid host or an unknown option, and the stray character is invisible in most logs.
  • Unencoded special characters in the password. Covered in detail in MongoDB connection strings; the short version is to encode once, or pass credentials as separate options.

On RE:NODE MongoDB hosting, each server is created with credentials generated for it and reached directly on the host and port shown on the plan. No SRV records are published for it and there is no proxy in front, so the right form is mongodb:// with that host and port, the generated user, and authSource pointing at the database the user lives in. The databases section of the guides shows where the panel lists those details. If your application runs on one of the app lines, such as Python, Node.js or C# / .NET, the URI goes in that server's environment variables on the Startup tab rather than in the code.

FAQ#

What is the default MongoDB URI?

mongodb://localhost:27017, with no credentials and no database. If localhost resolves to IPv6 on your machine and mongod listens only on IPv4, use mongodb://127.0.0.1:27017 instead.

Can I use mongodb+srv with an IP address?

No. +srv exists to look up SRV and TXT records for a DNS name, and an IP address has none. Use mongodb:// with the address and port.

Do I need to list every replica set member in the URI?

No. One reachable member is enough, because the driver learns the rest from the set. Listing two or three means the client can still start when one of them is down, which is why most production strings do.

Why did my app start writing to a different server after a driver upgrade?

Most likely directConnection changed default. Newer drivers discover the replica set from a single host and route writes to the primary. Add directConnection=true if you really mean that one server, and leave it off if you want normal replica set behaviour.

Does the order of options in the URI matter?

No. Options are a set of key-value pairs. What matters is that an option in the URI overrides the same option from a +srv TXT record, and that driver arguments passed in code usually override the URI.

What does replicaSet do on a single server?

On a standalone server it makes the connection fail, because the server reports no set name and the driver is waiting for a member of rs0. Only add replicaSet when the server really is a replica set member, even a one-member set.