# How to Safely Query a Production MongoDB From Your Phone

> Read-only users, IP Access List strategies, secondary reads and bounded queries: a practical setup for checking production MongoDB from a phone.

Source: https://bussolaformongo.app/blog/query-production-mongodb-from-phone-safely/ · 2026-10-06

Being able to check production from your phone is great right up to the moment a stray tap updates
the wrong document, or a broad filter scans a hundred million records during peak traffic. This
guide sets up a **safe way to query a production MongoDB from your phone**: what to configure in the
database, on the network, in the connection string and in the habits you bring to a small screen.

## What can go wrong

It helps to name the risks before fixing them:

- **Accidental writes.** Small screens and autocorrect make mistakes easier.
- **Expensive queries.** An unindexed filter or an unbounded count on a big collection competes with
  your real traffic.
- **Network exposure.** Opening the database to "anywhere" so a phone can connect.
- **A lost or shared device** holding credentials with too much power.

Each fix below addresses at least one of these.

## 1. Create a read-only user just for your phone

Don't reuse your application's database user, and never your admin account. Create a dedicated user
with read-only privileges, so the worst a mistake can do is read.

In **Atlas**, go to *Database Access → Add New Database User*, choose password authentication and
pick the built-in role **Only read any database** (`readAnyDatabase`). To scope it further, use *Specific Privileges*
and grant `read` only on the databases you actually need.

On a self-hosted deployment, use `mongosh`:

```js
db.getSiblingDB('admin').createUser({
  user: 'phone-readonly',
  pwd: passwordPrompt(),
  roles: [
    { role: 'read', db: 'shop' },
    { role: 'read', db: 'billing' },
  ],
});
```

`passwordPrompt()` keeps the password out of your shell history. Use a long generated password; you
will paste it once into the connection string and store it in the phone's Keychain.

If you sometimes need to fix data from the phone, create a **second** user with `readWrite` on a
single database and keep it as a separate connection, so that "edit mode" is a conscious choice
rather than the default.

## 2. Allow your phone in without opening the cluster to the world

Atlas only accepts connections from addresses in the project's **IP Access List**. Phones move
between Wi-Fi networks and carrier IPs, which tempts people to add `0.0.0.0/0`. Better options:

- **Temporary entries.** Atlas lets you add an IP access entry that expires automatically (within a week at most). When you
  need to connect, add your phone's current public IP with a short expiry and forget about cleanup.
- **A VPN with a fixed exit IP.** Route the phone through a VPN whose exit address never changes,
  such as a small server you control or a dedicated IP from a VPN provider, and allow only that
  address. One stable entry, no exposure.
- **Private networking.** If your cluster is only reachable through peering or a private endpoint,
  the phone needs a VPN into that network anyway; the same fixed-exit setup applies.

Whatever you choose, keep TLS on (it's mandatory on Atlas) and verify that a connection from an
unlisted network actually fails.

## 3. Read from a secondary when you can

Most phone checks don't need the very latest write. Sending them to a secondary keeps load off the
primary that serves your application. Add a read preference to the connection string:

```
mongodb+srv://phone-readonly:<password>@cluster0.example.mongodb.net/?readPreference=secondaryPreferred
```

`secondaryPreferred` reads from a secondary and falls back to the primary if none is available.
Secondaries can lag slightly behind, so for "did this write just land?" questions, switch back to
the default primary read.

If your Atlas cluster has **analytics nodes**, you can isolate ad-hoc reads completely by targeting
them with a tag:

```
?readPreference=secondary&readPreferenceTags=nodeType:ANALYTICS
```

## 4. Bound every query

A phone is the worst place to discover that a filter isn't indexed. A few habits keep queries cheap:

- **Filter on indexed fields** such as `_id`, foreign keys, status plus date, whatever your indexes
  cover. A filter on an unindexed field over a big collection is a collection scan.
- **Always use a limit** and a projection. You can't read a thousand documents on a phone anyway.
- **Set `maxTimeMS`** so the server aborts anything that runs too long instead of letting it compete
  with production traffic.
- **Prefer estimated counts** on large collections. `estimatedDocumentCount` reads collection
  metadata; `countDocuments` with a filter has to examine matching documents.
- **Check the plan** of any query you'll run regularly: `explain()` showing `COLLSCAN` instead of
  `IXSCAN` means it needs an index before it belongs on a phone.

## 5. Make the client part of the safety net

The app you use matters as much as the database settings. Look for a client that:

- stores connection strings in the **iOS Keychain**, bound to the device and not synced;
- lets you **label environments** so production looks different from staging;
- has a **read-only mode** that hides write actions entirely, rather than relying on you to be careful;
- saves **only the fields you changed** and detects conflicts when you do write, instead of
  replacing whole documents;
- applies **timeouts** to every operation, so a weak signal can't leave a request hanging.

[Bussola](/) was designed around this list: production connections open in read-only mode by
default, edits are saved as targeted `$set` and `$unset` operations with a conflict check against
the original values, and connection strings never leave the Keychain.

## 6. Protect the device

Your phone now holds a database credential, so treat it like a laptop with production access:

- a strong passcode and Face ID or Touch ID;
- automatic OS updates;
- Find My enabled, so you can erase it remotely;
- if the phone is lost, **rotate the password** of the phone user in Atlas or with
  `db.changeUserPassword()`. A dedicated user makes this a two-minute job that doesn't affect your
  applications.

## 7. Keep a trail

Separate users also give you separate trails. On self-hosted MongoDB Enterprise and on dedicated
Atlas tiers you can enable **database auditing** to record authentication and operations per user;
even without it, logs and Atlas metrics make it easy to see what the `phone-readonly` user did.

## A setup you can copy

| Layer | Setting |
| --- | --- |
| Database user | Dedicated `phone-readonly` user with `read` on specific databases |
| Network | Temporary IP access entries, or a fixed-IP VPN allowed explicitly |
| Connection string | `readPreference=secondaryPreferred` (or analytics nodes) |
| Queries | Indexed filters, limits, projections, `maxTimeMS` |
| Client | Keychain storage, read-only mode for production, timeouts |
| Device | Passcode and biometrics, Find My, rotate password if lost |

None of this takes more than half an hour, and it turns "I'll check when I'm back at my desk" into a
safe two-minute look from wherever you are.
