Using MongoDB TTL Indexes to Automatically Expire Documents
Learn how to create a TTL index, verify it works, and avoid common pitfalls when letting MongoDB delete old data automatically.
13 Dec 2025, 21:10 UTC

Quick answer: TTL indexes delete documents after a set time
MongoDB can automatically remove documents that are older than a specified number of seconds by creating a special index on a BSON Date field with the expireAfterSeconds option. The index itself does not store expiration data; it tells MongoDB’s background thread to scan for stale values and delete the matching documents.
Worked example: expire event logs after 24 hours
- Ensure the collection exists (or create it):
use mydb db.createCollection("event_logs") - Create the TTL index on a field that holds the event timestamp. The field must be a single BSON
Date(not an array or string):db.event_logs.createIndex( { "timestamp": 1 }, { expireAfterSeconds: 86400 } ) - Insert a test document whose timestamp is already older than the TTL window (e.g., 48 hours ago):
const oldTime = new Date(Date.now() - 2 * 86400); db.event_logs.insertOne({ _id: ObjectId(), message: "test expiry", timestamp: oldTime }); - Wait for the background TTL task to run (it executes roughly every 60 seconds). After waiting >60 seconds, check whether the document is gone:
// Replace with the _id you obtained from the insert db.event_logs.countDocuments({ _id: }) // Expected result: 0 (document removed)
How the mechanism works
When you create a TTL index, MongoDB stores the expireAfterSeconds value in the index specification. The internal TTL monitor wakes up every 60 seconds, scans the index for entries where the indexed date field is older than now() - expireAfterSeconds, and issues a delete for each matching document. Because the monitor works on the index, the scan is efficient even on large collections.
Verification steps
- Confirm the index exists and shows the expiration value:
db.event_logs.getIndexes() // Look for an object containing "expireAfterSeconds" : 86400 - Use
explain()to verify the index is being used for a query on the timestamp field:db.event_logs.find({ timestamp: { $lt: new Date() } }).explain("executionStats") // The winning plan should include an IXSCAN on the timestamp index. - Check the server log for TTL activity (if logging level includes
query): you will see lines like[TTLMonitor] removed 1 documents from mydb.event_logs.
Limits and constraints
- The indexed field must be a single BSON
Date. Arrays, strings, or nested documents are not allowed. - The index cannot be compound; you cannot combine the TTL field with other keys in the same index.
- TTL deletion is a background task that runs approximately every 60 seconds, so expiration is not immediate.
- Each insert or update that modifies the indexed date field incurs extra write overhead because the index must be updated.
- On heavily loaded clusters the TTL monitor’s scans can consume I/O; monitor
opcountersandwtstatistics if you notice performance impact.
Common mistakes and how to avoid them
- Using a non‑date field. If you mistakenly index a string or integer, the index is created but the TTL monitor ignores it, and documents never expire. Verify the field type with
db.collection.findOne({}, { timestamp: 1, _id: 0 })before creating the index. - Adding
expireAfterSecondsto an existing index without dropping it first. MongoDB will reject the command; you must drop the old index (db.collection.dropIndex({ timestamp: 1 })) then recreate it with the TTL option. - Expecting immediate removal. Remember the 60‑second interval; plan your tests accordingly.
- Relying on TTL for hard real‑time guarantees. If you need sub‑second expiration, consider a application‑level cleanup or a capped collection with a max size/age policy.
- Clock skew between application servers and MongoDB nodes. If your application writes timestamps based on local time that differs from server time, expiration may occur earlier or later than intended. Synchronize all hosts with NTP or a similar service.
Practical way to check the result in production
After deploying a TTL index, you can set up a simple health check:
- Insert a document with a timestamp set to
new Date(Date.now() - 2 * expireAfterSeconds). - Record its
_id. - After two minutes, run a count query for that
_id. - If the count is zero, the TTL mechanism is functioning; otherwise, verify the index definition, field type, and server time synchronization.
This test adds minimal load and gives confidence that automatic expiration works as expected.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.