Introduction
Cosmos DB Time-to-Live (TTL) automatically deletes documents after a specified time period. When TTL doesn't work as expected, documents remain in the container indefinitely, increasing storage costs and potentially exposing stale data.
Symptoms
Documents not expiring:
```sql -- Documents with old timestamps still exist SELECT * FROM c WHERE c._ts < 1700000000
-- Documents should have been deleted but still present SELECT * FROM c WHERE c.ttl = 3600 AND c.lastModified < '2024-01-01' ```
TTL property ignored:
# Documents have TTL property but aren't being deleted
# _ts is old, ttl is set, but document still existsContainer TTL not working:
# Container has default TTL set
# But documents aren't being cleaned upCommon Causes
- 1.TTL not enabled on container - Default TTL not configured
- 2.Document missing TTL property - No per-document TTL override
- 3.Timestamp property missing - Custom timestamp property doesn't exist
- 4.TTL value too large - TTL set to -1 (never expire)
- 5.Time unit mismatch - Using milliseconds instead of seconds
- 6.No indexing on TTL - TTL path not indexed
- 7.Background process delay - TTL cleanup can take time
Step-by-Step Fix
- 1.Check logs for specific error messages
- 2.Verify configuration settings
- 3.Test network connectivity
- 4.Review recent changes
- 5.Apply corrective action
- 6.Verify the fix
Step 1: Check Container TTL Configuration
```bash # Get container default TTL az cosmosdb sql container show \ --account-name my-cosmos \ --resource-group my-rg \ --database-name my-db \ --name my-container \ --query '{DefaultTtl:resource.defaultTtl}'
# -1 = TTL disabled (documents never expire) # 0 = TTL disabled # > 0 = TTL enabled, default seconds ```
Step 2: Enable TTL on Container
```bash # Enable TTL with default of 1 hour (3600 seconds) az cosmosdb sql container update \ --account-name my-cosmos \ --resource-group my-rg \ --database-name my-db \ --name my-container \ --ttl 3600
# Disable TTL az cosmosdb sql container update \ --account-name my-cosmos \ --resource-group my-rg \ --database-name my-db \ --name my-container \ --ttl -1 ```
Step 3: Check Document TTL Property
```sql -- Check if documents have TTL property SELECT c.id, c.ttl, c._ts FROM c
-- TTL property must be a number (seconds) -- If missing, container default TTL applies -- If -1, document never expires (override) -- If null or missing, container default applies
-- Example: Document with 1 hour TTL { "id": "doc1", "ttl": 3600, "data": "..." } ```
Step 4: Add TTL Property to Documents
```csharp // Add TTL when creating documents var document = new { id = "doc1", data = "some data", ttl = 3600 // Expire in 1 hour };
await container.CreateItemAsync(document, new PartitionKey(document.id)); ```
-- Update existing documents to add TTL
UPDATE c SET c.ttl = 3600 WHERE c.category = 'temporary'Step 5: Check _ts System Property
```sql -- Cosmos DB uses _ts for TTL calculation -- _ts is last modified timestamp (Unix epoch seconds)
SELECT c.id, c._ts, c.ttl, DateTimeFromParts(1970,1,1) + Duration(c._ts * 1000) as LastModified FROM c
-- TTL expiration = _ts + ttl -- If _ts is missing or wrong, TTL won't work correctly ```
Step 6: Verify Custom Timestamp Property
```bash # If using custom timestamp for TTL, check it's properly formatted # Must be ISO 8601 format or Unix timestamp
# Using Path annotation in Java: @Container(containerName = "items") public class Item { @Id private String id;
@TimeToLive // Maps to ttl property private Integer timeToLive;
private String data; } ```
Step 7: Check for TTL Override
```sql -- Documents with ttl = -1 never expire SELECT * FROM c WHERE c.ttl = -1
-- These override container default TTL -- Update them to enable expiration: UPDATE c SET c.ttl = 3600 WHERE c.ttl = -1 ```
Step 8: Monitor TTL Deletion
```bash # TTL is a background process, deletions may not be immediate # Check container metrics for delete operations
az monitor metrics list \ --resource /subscriptions/SUB/resourceGroups/my-rg/providers/Microsoft.DocumentDB/databaseAccounts/my-cosmos \ --metric "DocumentCount" \ --interval PT1H
# Document count should decrease as TTL expires documents ```
Step 9: Force Immediate Deletion (Development)
```csharp // For testing, manually delete expired documents var query = container.GetItemQueryIterator<dynamic>( "SELECT * FROM c WHERE c._ts + c.ttl < GetCurrentDateTime()");
while (query.HasMoreResults) { var page = await query.ReadNextAsync(); foreach (var doc in page) { await container.DeleteItemAsync<dynamic>( doc.id.ToString(), new PartitionKey(doc.id.ToString())); } } ```
Step 10: Set Up TTL Monitoring
```bash # Alert when document count isn't decreasing as expected az monitor metrics alert create \ --name cosmos-ttl-alert \ --resource-group my-rg \ --scopes /subscriptions/SUB/resourceGroups/my-rg/providers/Microsoft.DocumentDB/databaseAccounts/my-cosmos \ --condition "avg DocumentCount > 100000" \ --window-size 1h
Cosmos DB TTL Behavior
| TTL Value | Behavior |
|---|---|
| -1 | Never expire (container or document level) |
| null | Use container default TTL |
| 0 | Expire immediately |
| > 0 | Expire after N seconds |
Verification
```bash # After enabling TTL, wait for background process # TTL cleanup can take up to a few minutes
# Check documents are being deleted az cosmosdb sql container show \ --account-name my-cosmos \ --resource-group my-rg \ --database-name my-db \ --name my-container \ --query 'resource.documentCount'
# Should decrease over time as documents expire
# Query for old documents az cosmosdb sql query \ --account-name my-cosmos \ --database-name my-db \ --container-name my-container \ --query-text "SELECT * FROM c WHERE c._ts < 1700000000"
# Should return fewer results over time ```
Related Issues
- [Fix Azure Cosmos DB Throttling](/articles/fix-azure-cosmos-db-throttling)
- [Fix Azure Cosmos DB Partition Hotspot](/articles/fix-azure-cosmos-db-partition-hotspot)
- [Fix Azure Cosmos DB SQL Query Slow](/articles/fix-azure-cosmos-db-sql-query-slow)
Related Articles
- [Technical troubleshooting: Fix Azure Aks Pod Crashloopbackoff Issue in Azure](azure-aks-pod-crashloopbackoff)
- [Technical troubleshooting: Fix Azure Api Management Policy Expression Runtime](azure-api-management-policy-expression-runtime-error)
- [Technical troubleshooting: Fix Azure App Configuration Feature Flag Not Refre](azure-app-configuration-feature-flag-not-refreshing)
- [Technical troubleshooting: Fix Azure App Service 503 Always On Disabled Issue](azure-app-service-503-always-on-disabled)
- [Technical troubleshooting: Fix Azure Application Gateway Err SSL Unrecognized](azure-application-gateway-err-ssl-unrecognized-name-alert)
<script type="application/ld+json"> { "@context": "https://schema.org", "@type": "TechArticle", "headline": "Fix Azure Cosmos DB TTL Not Expiring Documents", "description": "Troubleshoot Cosmos DB TTL not expiring documents. Check TTL configuration, timestamp properties, and container settings.", "url": "https://www.fixwikihub.com/fix-azure-cosmos-db-ttl-not-expiring", "publisher": { "@type": "Organization", "name": "FixWikiHub", "url": "https://www.fixwikihub.com" }, "author": { "@type": "Person", "name": "FixWikiHub Editorial Team" }, "datePublished": "2026-04-02T18:14:51.667Z", "dateModified": "2026-04-02T18:14:51.667Z" } </script>