100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
Messaging

Message TTL and Expiration

Learn how RabbitMQ expires messages using per-queue and per-message time-to-live settings, how expiration interacts with dead lettering, and the ordering subtleties that trip up new users.

ReliabilityIntermediate7 min readJul 10, 2026
Analogies

Setting Time-to-Live on Queues and Messages

RabbitMQ supports two ways to expire messages before they're ever consumed. A queue-level TTL, set via the x-message-ttl argument at queue declaration, applies uniformly to every message that arrives in that queue, counting from the moment each message is enqueued. A per-message TTL, set via the expiration field in message properties (as a string, in milliseconds) lets a single publisher vary the lifetime of individual messages within the same queue, for example giving a price-quote update a much shorter shelf life than an order confirmation flowing through the identical exchange and queue.

🏏

Cricket analogy: It is like a stadium setting a blanket gate-closure time for every ticket holder (queue TTL) versus a scalper printing individual tickets with different validity windows for different matches in the same series (per-message TTL).

How Expiration Actually Happens

A message doesn't expire the instant its TTL timer hits zero somewhere in the background; RabbitMQ checks expiration lazily, typically when the message reaches the head of the queue and is about to be delivered or when the queue is otherwise inspected. This means a message can technically remain in the queue past its nominal TTL if it's stuck behind older, still-unconsumed messages, since classic queues only guarantee FIFO expiry checking at the head. If the queue also has a DLX configured, an expired message is dead-lettered rather than silently vanishing, arriving with an x-death reason of 'expired' so downstream consumers of the dead-letter queue know exactly why it never made it to the original consumer.

🏏

Cricket analogy: It is like a bowler's spell limit only being enforced when the captain actually checks the over count at the end of an over, not the instant the limit is technically reached mid-delivery — the check happens lazily at a natural boundary, not continuously.

python
# Queue-level TTL: every message in this queue expires 60s after enqueue
channel.queue_declare(
    queue='quotes.live',
    durable=True,
    arguments={
        'x-message-ttl': 60000,
        'x-dead-letter-exchange': 'quotes.dlx',
    }
)

# Per-message TTL: this specific message expires in 5s regardless of queue default
channel.basic_publish(
    exchange='',
    routing_key='quotes.live',
    body='{"symbol": "RELIANCE", "price": 2945.10}',
    properties=pika.BasicProperties(expiration='5000'),
)

The expiration property must be a string, not an integer — pika.BasicProperties(expiration='5000') is correct, expiration=5000 will raise an error or be silently ignored depending on the client library. This is a common source of bugs when developers copy queue-argument style (integer milliseconds) into the message property.

Combining Queue and Message TTL

When both a queue-level TTL and a per-message TTL are present, RabbitMQ applies whichever is shorter for that specific message — a per-message TTL can only tighten the effective lifetime, never extend it beyond the queue's own limit. This makes queue TTL a useful safety ceiling: you can let individual publishers set aggressive short TTLs for latency-sensitive data while a queue-level TTL acts as a backstop ensuring nothing ever lingers indefinitely even if a publisher forgets to set expiration at all.

🏏

Cricket analogy: It is like a franchise capping every player contract at a maximum of 3 years (queue TTL) even if an individual player's negotiated deal says 5 years — the shorter of the two governing limits always wins, tightening but never extending.

Because expiration is checked lazily at the head of a classic queue, a short-TTL message stuck behind a long backlog of older messages can sit in the queue well past its nominal expiry time before RabbitMQ notices and dead-letters it. If precise, low-latency expiry matters, keep the queue shallow (small backlog) or use a dedicated queue per TTL tier rather than mixing wildly different TTLs in one deep queue.

  • Queue-level TTL (x-message-ttl) applies uniformly to every message enqueued; per-message TTL (expiration property) varies per message.
  • The expiration message property must be set as a string of milliseconds, not an integer.
  • When both TTLs are present, RabbitMQ applies whichever is shorter for that message.
  • Expiration is checked lazily, typically at the head of the queue, not the instant the timer hits zero.
  • A short-TTL message can sit past its nominal expiry if stuck behind older messages in a deep classic queue.
  • An expired message is dead-lettered (with x-death reason 'expired') if the queue has a DLX configured, instead of vanishing silently.
  • Use a dedicated queue per TTL tier if precise expiry timing matters for latency-sensitive data.

Practice what you learned

Was this page helpful?

Topics covered

#Messaging#RabbitMQStudyNotes#Database#MessageTTLAndExpiration#Message#TTL#Expiration#Setting#StudyNotes#SkillVeris