nats-io/nats-server · error

failed to parse message TTL: %w

Error message

failed to parse message TTL: %w

What it means

Each backed-up message carries a subject header from which an optional per-message TTL is extracted via getMessageTTL. This error is returned when that TTL header cannot be parsed, so the restore cannot decide whether the message is expired. It indicates malformed message headers in the backup.

Source

Thrown at server/stream_backup.go:528

			return nil, fmt.Errorf("failed to read message sequence %d: unexpected payload size", seq)
		}
		subj := hdr.Name
		mhdr := buf[:hdr.HeaderSize]
		msg := buf[hdr.HeaderSize : hdr.HeaderSize+hdr.PayloadSize]
		if seq <= lseq {
			return nil, fmt.Errorf("message sequence %d out of order", seq)
		}
		// We could have deleted messages since the last message we stored, if so
		// we should work out what the gap is and skip those sequences.
		if gap := seq - lseq - 1; gap > 0 {
			if err := store.SkipMsgs(lseq+1, gap); err != nil {
				return nil, fmt.Errorf("failed to process gap: %w", err)
			}
		}
		lseq = seq
		ttl, err := getMessageTTL(mhdr)
		if err != nil {
			return nil, fmt.Errorf("failed to parse message TTL: %w", err)
		}
		hdrTime := time.Unix(0, hdr.Timestamp)
		if ttl > 0 && time.Now().After(hdrTime.Add(time.Duration(ttl)*time.Second)) {
			// If the TTL has exceeded then there isn't much point in storing the message,
			// but we still need to preserve the sequence.
			if err := store.SkipMsgs(seq, 1); err != nil {
				return nil, fmt.Errorf("failed to process expired message sequence %d: %w", seq, err)
			}
			releaseRestoreBytes(storedSize)
			continue
		}
		if err = store.StoreRawMsg(subj, mhdr, msg, seq, hdr.Timestamp, ttl, false); err != nil {
			return nil, fmt.Errorf("failed to store message sequence %d: %w", seq, err)
		}
		releaseRestoreBytes(storedSize)
	}

	if !eob {

View on GitHub (pinned to 3a66a489d2)

Solutions

  1. Re-create the backup from the source stream so headers are re-serialized correctly
  2. Validate message headers on the source stream (fix or clear bad per-message TTLs before backing up)
  3. Ensure the same/compatible nats-server version is used for backup and restore
  4. If corruption is suspected, restore from an earlier known-good backup
Defensive patterns

Strategy: validation

Validate before calling

// on the source stream, validate per-message TTL headers before backup
msgs, _ := sub.Fetch(n)
for _, m := range msgs {
    if t := m.Header.Get("NATS-Message-TTL"); t != "" {
        if _, err := strconv.Atoi(t); err != nil { return fmt.Errorf("invalid TTL header %q", t) }
    }
}

Try / catch

if err != nil && strings.Contains(err.Error(), "failed to parse message TTL") {
    // backup headers are malformed; re-create backup from source
}

Prevention

When it happens

Trigger: getMessageTTL(mhdr) fails to parse the TTL value embedded in the message's header bytes during RestoreStreamV2; caused by malformed or non-numeric NATS-Message-TTL header data, corrupted header bytes, or headers written by incompatible tooling.

Common situations: Backups whose message headers were hand-edited or produced by third-party tools emitting invalid TTL header values, or corrupted archive bytes in the header region.

Understand the failure class

Related errors


AI-assisted analysis of nats-io/nats-server@3a66a489d2 (2026-09-02). Data as JSON: /api/errors/727bfbdafeff0ae1. Report an issue: GitHub.