Database Security

MongoDB Security Hardening: Authentication, Network, and Field Encryption

A deep-dive into securing MongoDB deployments: enabling authentication, configuring TLS, implementing field-level encryption, role-based access control, and audit logging for production environments.

August 15, 20257 min readShipSafer Team

MongoDB's flexibility and ease of setup make it popular, but that same ease-of-setup has historically led to catastrophic security breaches. The "MongoDB ransomware" campaigns of 2017 and 2019 compromised hundreds of thousands of databases running without authentication, exposed directly to the internet. Even today, Shodan regularly indexes thousands of unauthenticated MongoDB instances. This guide covers everything you need to deploy MongoDB securely.

The Authentication Problem: Never Run Without Auth

By default, older versions of MongoDB (pre-7.0) could start without requiring authentication. Modern deployments via Atlas or Docker images typically enforce authentication, but self-hosted deployments on bare metal or VMs still need explicit configuration.

Check if your instance is running without authentication:

# This should return an authorization error if auth is properly configured
mongosh --eval "db.adminCommand({ listDatabases: 1 })" mongodb://localhost:27017

If that command succeeds without providing credentials, authentication is disabled. Enable it in mongod.conf:

# /etc/mongod.conf
security:
  authorization: enabled
  javascriptEnabled: false  # Disable server-side JavaScript execution

Create the first admin user before enabling auth (you must do this while auth is still disabled or using localhost exception):

use admin
db.createUser({
  user: "admin",
  pwd: passwordPrompt(),
  roles: [
    { role: "userAdminAnyDatabase", db: "admin" },
    { role: "readWriteAnyDatabase", db: "admin" },
    "dbAdminAnyDatabase"
  ]
})

After creating the admin user, restart mongod with auth enabled. From this point, all connections require valid credentials.

SCRAM vs x.509 Authentication

MongoDB supports multiple authentication mechanisms. For human users and most application connections, SCRAM-SHA-256 is the appropriate choice. For machine-to-machine connections in high-security environments, x.509 certificate authentication eliminates shared passwords entirely.

To enforce SCRAM-SHA-256 (and disable the weaker SCRAM-SHA-1):

# mongod.conf
security:
  authorization: enabled
  authenticationMechanisms: SCRAM-SHA-256

For x.509 authentication, generate a certificate for each application client:

# Generate client certificate (example with openssl)
openssl req -new -nodes -newkey rsa:2048 \
  -keyout client.key \
  -out client.csr \
  -subj "/C=US/ST=CA/L=SF/O=MyApp/OU=API/CN=app-service"

openssl x509 -req -sha256 -days 365 \
  -in client.csr \
  -CA ca.crt \
  -CAkey ca.key \
  -CAcreateserial \
  -out client.crt

Create a MongoDB user whose username matches the certificate's subject DN:

use $external
db.createUser({
  user: "C=US,ST=CA,L=SF,O=MyApp,OU=API,CN=app-service",
  roles: [
    { role: "readWrite", db: "myapp" }
  ]
})

Connect using the certificate:

mongodb://localhost:27017/?authMechanism=MONGODB-X509&tls=true&tlsCertificateKeyFile=client.pem&tlsCAFile=ca.crt

Network Binding: Stop Listening on All Interfaces

MongoDB's default bindIp: 127.0.0.1 is safe for local development. But many production deployments change this to 0.0.0.0 to allow remote connections, then forget to add a firewall rule. Always prefer binding to specific interfaces:

# mongod.conf
net:
  port: 27017
  bindIp: 127.0.0.1,10.0.1.15  # Localhost + specific private IP
  tls:
    mode: requireTLS
    certificateKeyFile: /etc/ssl/mongodb/server.pem
    CAFile: /etc/ssl/mongodb/ca.crt
    allowConnectionsWithoutCertificates: false
    disabledProtocols: TLS1_0,TLS1_1

The tls.mode: requireTLS setting means MongoDB will refuse all connections that do not use TLS. Set disabledProtocols to prevent clients from negotiating older, weaker TLS versions.

If you must expose MongoDB to the network (for example, in a Kubernetes cluster), use a NetworkPolicy to restrict which pods can connect:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: mongodb-allow-app-only
spec:
  podSelector:
    matchLabels:
      app: mongodb
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: my-application
    ports:
    - protocol: TCP
      port: 27017

TLS Configuration in Detail

A complete TLS configuration for a replica set in mongod.conf:

net:
  tls:
    mode: requireTLS
    certificateKeyFile: /etc/mongod/ssl/mongod.pem
    CAFile: /etc/mongod/ssl/ca.crt
    # Require client certificates for intra-cluster communication
    clusterAuthMode: x509
    # Allow connections without client cert only for external clients using SCRAM
    allowConnectionsWithoutCertificates: true

replication:
  replSetName: "rs0"

security:
  clusterAuthMode: x509

Client connection string with full TLS verification:

// Node.js with Mongoose
mongoose.connect('mongodb://app-user:password@db.internal:27017/myapp', {
  tls: true,
  tlsCAFile: '/etc/ssl/ca.crt',
  tlsCertificateKeyFile: '/etc/ssl/client.pem',
  tlsAllowInvalidHostnames: false,  // Must be false in production
  tlsAllowInvalidCertificates: false  // Must be false in production
});

Field-Level Encryption (MongoDB 4.2+)

Client-Side Field Level Encryption (CSFLE) encrypts specific fields in your application before they are sent to MongoDB. The database server never sees the plaintext values — it stores only ciphertext. This provides protection even if the database server itself is compromised.

CSFLE requires MongoDB Enterprise or Atlas, and uses envelope encryption with a Key Management Service:

const { MongoClient, ClientEncryption } = require('mongodb');
const { ClientEncryptionProvider } = require('mongodb-client-encryption');

// AWS KMS configuration
const kmsProviders = {
  aws: {
    accessKeyId: process.env.AWS_ACCESS_KEY_ID,
    secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
  }
};

// Define the JSON schema for encrypted fields
const encryptedFieldsSchema = {
  "myapp.users": {
    bsonType: "object",
    encryptMetadata: {
      keyId: [new Binary(Buffer.from(dataKeyId, 'base64'), 4)]
    },
    properties: {
      ssn: {
        encrypt: {
          bsonType: "string",
          algorithm: "AEAD_AES_256_CBC_HMAC_SHA_512-Deterministic"
        }
      },
      creditCardNumber: {
        encrypt: {
          bsonType: "string",
          algorithm: "AEAD_AES_256_CBC_HMAC_SHA_512-Random"
        }
      }
    }
  }
};

const client = new MongoClient(uri, {
  autoEncryption: {
    keyVaultNamespace: 'encryption.__keyVault',
    kmsProviders,
    schemaMap: encryptedFieldsSchema
  }
});

The difference between Deterministic and Random encryption:

  • Deterministic: The same plaintext always produces the same ciphertext. Allows equality queries but leaks frequency information.
  • Random: Each encryption of the same plaintext produces different ciphertext. More secure but cannot be queried directly.

Use deterministic encryption for fields you need to query (email, SSN for lookup). Use random encryption for fields you only need to retrieve (credit card numbers, health data).

Role-Based Access Control

MongoDB's built-in roles provide a starting point, but production applications should use custom roles with minimal permissions:

// Create a custom role for the application
use admin
db.createRole({
  role: "appRole",
  privileges: [
    {
      resource: { db: "myapp", collection: "users" },
      actions: ["find", "insert", "update"]
    },
    {
      resource: { db: "myapp", collection: "orders" },
      actions: ["find", "insert", "update", "remove"]
    },
    {
      resource: { db: "myapp", collection: "products" },
      actions: ["find"]  // Read-only for products
    }
  ],
  roles: []  // No inherited roles
})

// Create application user with this role
use myapp
db.createUser({
  user: "app_service",
  pwd: passwordPrompt(),
  roles: [{ role: "appRole", db: "admin" }]
})

Avoid these built-in roles in application code:

  • dbAdmin — Grants schema management permissions
  • dbOwner — Grants all permissions on the database
  • root — Superuser. Never use in applications.
  • readWriteAnyDatabase — Grants access to all databases

Common built-in roles that are appropriate for specific use cases:

  • read — Read-only access to a specific database
  • readWrite — Read and write to a specific database (still too broad for most apps)
  • clusterMonitor — Read access to monitoring data (for your monitoring tools)
  • backup — Permission to perform backups

Audit Logging with mongod Audit Log

MongoDB Enterprise supports auditing of authentication, authorization, and CRUD operations. Enable it in mongod.conf:

auditLog:
  destination: file
  format: JSON
  path: /var/log/mongodb/auditLog.json
  filter: '{ atype: { $in: [ "authenticate", "authCheck", "createUser", "dropUser", "grantRolesToUser", "revokeRolesFromUser", "createIndex", "dropCollection", "dropDatabase" ] } }'

The filter field is a MongoDB query expression that limits which audit events are recorded. The example above captures authentication events, privilege changes, and DDL operations — a good baseline for compliance without logging every single query.

For higher compliance requirements (SOC 2, HIPAA), include find, insert, update, delete in the filter, but be prepared for significant log volume.

Ship audit logs to a centralized SIEM immediately. MongoDB Atlas has built-in audit log forwarding to S3, Datadog, and other destinations.

Replica Set Authentication

Internal communication between replica set members must also be authenticated. Use a keyfile or x.509 certificates:

# mongod.conf for replica set members
security:
  authorization: enabled
  keyFile: /etc/mongodb/keyfile  # For keyfile auth
  # OR
  clusterAuthMode: x509  # For x.509 auth (preferred)

Generate a keyfile (minimum 6 characters, maximum 1024 characters, base64-encoded):

openssl rand -base64 756 > /etc/mongodb/keyfile
chmod 400 /etc/mongodb/keyfile
chown mongodb:mongodb /etc/mongodb/keyfile

All replica set members must use the same keyfile. Without this, a rogue server could join the replica set and receive all replicated data.

Disabling Dangerous Features

JavaScript execution on the server side ($where queries, mapReduce, db.eval()) can be exploited for injection attacks. Disable it unless you have a specific need:

security:
  javascriptEnabled: false

Verify that your application does not use $where before disabling:

// BAD: $where executes JavaScript on the server
db.users.find({ $where: "this.email === req.body.email" })

// GOOD: Use standard query operators
db.users.find({ email: req.body.email })

Also disable the HTTP interface (disabled by default in MongoDB 3.6+, but verify):

net:
  http:
    enabled: false
    RESTInterfaceEnabled: false

The combination of strong authentication, TLS, network binding, field-level encryption for sensitive data, custom least-privilege roles, and audit logging provides a defense-in-depth posture that satisfies most compliance frameworks.

mongodb
database-security
nosql
encryption
rbac
tls

Check Your Security Score — Free

See exactly how your domain scores on DMARC, TLS, HTTP headers, and 25+ other automated security checks in under 60 seconds.