Loading learning content...
In RIP, time is everything. Four invisible timers run constantly on every router, governing when routes are advertised, when they become suspect, when they're considered dead, and when they're finally purged from memory. These timers are the heartbeat of RIP—and understanding them is essential for predicting network behavior.
Misunderstanding RIP timers leads to some of the most puzzling troubleshooting scenarios: routes that should have disappeared but persist, convergence that takes far longer than expected, or networks that flap between states without apparent cause. The timer system is elegant in design but subtle in operation.
This page dissects each timer, explains how they interact, and demonstrates how to tune them for specific network requirements. By the end, you'll understand exactly why RIP converges at the speed it does and how to optimize that behavior.
By the end of this page, you will understand the four core RIP timers (Update, Invalid, Hold-down, Flush), how they interact in various failure scenarios, why default values are what they are, and how to safely tune timers for specific requirements.
RIP relies on four distinct timer mechanisms, each serving a specific purpose in maintaining routing table accuracy and network stability.
| Timer | Default Value | Purpose | Scope |
|---|---|---|---|
| Update Timer | 30 seconds | Periodic routing table broadcast | Global (per router) |
| Invalid Timer | 180 seconds | Mark route as potentially unreachable | Per route |
| Hold-down Timer | 180 seconds | Ignore updates for unstable routes | Per route |
| Flush Timer | 240 seconds | Remove route from routing table | Per route |
Understanding Timer Relationships
These timers don't operate in isolation—they form a state machine governing route lifecycle:
[Route Learned] → Update Timer resets Invalid Timer
Every 30s, route is re-advertised
[No Update for 180s] → Invalid Timer expires
Route marked "possibly down"
Hold-down Timer starts
[No Update for 240s] → Flush Timer expires
Route removed from table
Memory freed
Why Multiple Timers?
A single timer would be simpler but wouldn't provide the nuanced behavior needed:
RIP timers aren't exact. Implementation jitter (±15%) is intentionally added to prevent synchronized updates from multiple routers. If all routers sent updates at exactly the same second, network congestion would spike periodically. Random jitter spreads the load.
The Update Timer (also called the Route Update Timer or Periodic Timer) controls how frequently a router advertises its complete routing table to neighbors.
Default: 30 seconds
Operation:
123456789101112131415161718192021222324252627282930313233343536373839404142434445
# Pseudo-code for RIP Update Timer import randomimport threading class RIPRouter: def __init__(self): self.update_interval = 30 # seconds self.jitter_range = 0.15 # ±15% self.routing_table = {} self.interfaces = [] self.running = True def calculate_next_update_time(self): """Add random jitter to prevent synchronization.""" base = self.update_interval jitter = random.uniform(-self.jitter_range, self.jitter_range) return base * (1 + jitter) def update_timer_loop(self): """Main update timer thread.""" while self.running: # Wait for next update time (with jitter) wait_time = self.calculate_next_update_time() time.sleep(wait_time) # Send periodic updates self.send_periodic_update() def send_periodic_update(self): """Send complete routing table to all neighbors.""" update_packet = self.construct_rip_response() for interface in self.interfaces: if interface.rip_enabled: # Apply split horizon per interface filtered_packet = self.apply_split_horizon(update_packet, interface) interface.send(filtered_packet) print(f"[{time.time():.1f}] Sent periodic update with {len(update_packet.routes)} routes") # Example jitter calculation# Base interval: 30 seconds# Jitter ±15%: random between 25.5 and 34.5 seconds# Updates from different routers spread across 9-second windowTriggered Updates
In addition to periodic updates, RIP sends triggered updates when topology changes:
When:
- A route's metric changes significantly
- A route becomes unreachable (metric 16)
- A new route is learned
- An interface goes down/up
Behavior:
- Don't wait for Update Timer
- Send immediately (within 1-5 seconds, random delay)
- Include only changed routes (not full table)
- Rate-limited to prevent storms
Triggered updates dramatically improve convergence for failures. Instead of waiting up to 30 seconds for a neighbor to notice a missing update, the failure information propagates immediately.
Update Timer Trade-offs
If you reduce the Update Timer, you must proportionally reduce Invalid, Hold-down, and Flush timers. Otherwise, routes expire normally while being refreshed rapidly—an inconsistent configuration that causes unpredictable behavior.
The Invalid Timer (also called the Route Timeout or Expiration Timer) tracks how long since a route was last refreshed. If no update is received within this period, the route is considered suspect.
Default: 180 seconds (6 × Update Timer)
Operation:
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950
# Invalid Timer management for RIP routes class RouteEntry: def __init__(self, destination, metric, next_hop): self.destination = destination self.metric = metric self.next_hop = next_hop self.invalid_timer = 180 # seconds self.state = "valid" def refresh(self, new_metric, new_next_hop): """Called when update received for this route.""" # Only refresh if update is from current next-hop # or provides a better metric if new_next_hop == self.next_hop or new_metric < self.metric: self.metric = new_metric self.next_hop = new_next_hop self.invalid_timer = 180 # Reset timer! self.state = "valid" print(f"Route to {self.destination} refreshed, timer reset") def tick(self, elapsed_seconds): """Called periodically to decrement timer.""" if self.state == "valid": self.invalid_timer -= elapsed_seconds if self.invalid_timer <= 0: self.expire() def expire(self): """Called when invalid timer reaches zero.""" print(f"Route to {self.destination} EXPIRED after 180s") self.metric = 16 # Mark unreachable self.state = "invalid" # Trigger immediate update to poison this route trigger_update(self.destination, metric=16) # Timeline example:# T+0s: Route learned, metric=2, invalid_timer=180# T+30s: Update received, invalid_timer reset to 180# T+60s: Update received, invalid_timer reset to 180 # T+90s: Neighbor fails! No more updates...# T+120s: No update, invalid_timer = 150# T+150s: No update, invalid_timer = 120# T+180s: No update, invalid_timer = 90# T+210s: No update, invalid_timer = 60# T+240s: No update, invalid_timer = 30# T+270s: No update, invalid_timer = 0 → EXPIRED! # Total time to expire: 180 seconds after last valid updateWhy 180 Seconds (6 × 30)?
The Invalid Timer is set to 6 times the Update Timer to tolerate temporary update losses:
Scenario: Updates sent at T+0, T+30, T+60, T+90, T+120, T+150
If updates at T+30, T+60, T+90 are lost:
- T+0: Route learned, timer = 180
- T+30: Lost! Timer = 150
- T+60: Lost! Timer = 120
- T+90: Lost! Timer = 90
- T+120: Received! Timer = 180 (saved!)
Three consecutive lost updates don't cause route expiration
Because: 3 × 30s = 90s < 180s timeout
But 6 consecutive lost updates would cause expiration
This provides resilience against:
The 6:1 ratio between Invalid Timer and Update Timer is a well-tested balance. It tolerates 5 consecutive lost updates while ensuring stale routes don't persist indefinitely. Deviating from this ratio requires careful consideration of your network's error characteristics.
The Hold-down Timer is the most subtle of RIP's timers. Its purpose is to prevent routing loops by temporarily ignoring potentially incorrect routing updates after a route fails.
Default: 180 seconds
When Hold-down Starts:
Hold-down Behavior:
During hold-down, the router refuses to accept new routes to the destination unless:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364
# Hold-down Timer prevents accepting bad routes after failure class RouteEntry: def __init__(self, destination, metric, next_hop): self.destination = destination self.metric = metric self.original_metric = metric # Remember pre-failure metric self.next_hop = next_hop self.state = "valid" # valid, holddown, garbage self.holddown_timer = 0 def enter_holddown(self): """Called when route becomes unreachable.""" self.original_metric = self.metric # Save for comparison self.metric = 16 self.state = "holddown" self.holddown_timer = 180 print(f"Route {self.destination}: Entering hold-down (180s)") def process_update_during_holddown(self, new_metric, source): """ During hold-down, we're very picky about what updates we accept. This prevents accepting stale/looped information. """ if self.state != "holddown": return self.normal_update(new_metric, source) # Case 1: Update from original next-hop if source == self.next_hop: print(f"Accepting update from original next-hop") if new_metric < 16: self.exit_holddown(new_metric, source) return True # Case 2: Significantly better route (from different source) if new_metric < self.original_metric: print(f"Accepting superior route: {new_metric} < original {self.original_metric}") self.exit_holddown(new_metric, source) return True # Case 3: Equal or worse route from different source print(f"IGNORING route with metric {new_metric} during hold-down") return False # Rejected! def exit_holddown(self, metric, next_hop): """Transition back to valid state.""" self.metric = metric self.next_hop = next_hop self.state = "valid" self.holddown_timer = 0 self.reset_invalid_timer() # Why hold-down is critical:# ===========================# Without hold-down:# R3 loses link to Net-X, tells R2 "Net-X metric 16"# But R1 (further away) still has old info "Net-X metric 3"# R1 tells R2 "Net-X metric 3"# R2 thinks: "Great! R1 has a path!" → WRONG (it's my old info echoed back)# # With hold-down:# R2 is in hold-down for Net-X original metric was 2# R1's "metric 3" is WORSE than original 2# R2 ignores R1's update → Correct behavior!Hold-down's Role in Preventing Loops
Consider this scenario:
Network: R1 <---> R2 <---> R3 <---> Network X
Initially:
R3: X = 1 (direct)
R2: X = 2 (via R3)
R1: X = 3 (via R2)
R3-X link fails:
R3: X = 16 (unreachable), poisons route
R2: Receives poison, X = 16, enters hold-down
But R1 still has: X = 3 via R2
R1 sends update to R2: "I know X with metric 3!"
Without hold-down:
R2 thinks: "R1 knows a path to X!"
R2 updates: X = 4 via R1
R2 tells R3: "X = 4"
Routing loop R2 <-> R1 forms!
With hold-down:
R2 is in hold-down for X (original metric = 2)
R1's metric 3 > original 2
R2 ignores R1's update
Loop prevented!
The flip side of hold-down is that legitimate new paths may be ignored for 180 seconds. If a faster alternate path becomes available during hold-down, the router won't use it until hold-down expires. This is the price paid for loop prevention.
The Flush Timer (also called Garbage Collection Timer or Route Removal Timer) determines when a route is completely purged from the routing table.
Default: 240 seconds (8 × Update Timer)
Relationship to Invalid Timer:
Flush Timer = Invalid Timer + 60 seconds
240 = 180 + 60
This 60-second gap serves a purpose:
- Route goes invalid at 180s
- Route is poisoned (metric 16) for next 60s
- Poison is advertised in periodic updates
- After 240s, route is removed entirely
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273
# Complete route lifecycle with all timers class RouteEntry: def __init__(self, destination, metric, next_hop): self.destination = destination self.metric = metric self.next_hop = next_hop # Timer values (in seconds) self.INVALID_TIMEOUT = 180 self.HOLDDOWN_DURATION = 180 self.FLUSH_TIMEOUT = 240 # Active timers self.invalid_timer = self.INVALID_TIMEOUT self.flush_timer = self.FLUSH_TIMEOUT self.holddown_timer = 0 self.state = "valid" # valid, holddown, garbage def tick(self, elapsed): """Called every second to update timers.""" if self.state == "valid": self.invalid_timer -= elapsed if self.invalid_timer <= 0: self.transition_to_holddown() elif self.state == "holddown": self.holddown_timer -= elapsed self.flush_timer -= elapsed if self.flush_timer <= 0: self.transition_to_garbage() # Note: holddown_timer expiring doesn't remove the route # It just allows accepting new routes with any metric elif self.state == "garbage": # Route is already removed from forwarding table # Waiting for final cleanup pass def transition_to_holddown(self): """Route times out, enter hold-down state.""" print(f"[{self.destination}] Invalid timer expired → hold-down") self.state = "holddown" self.metric = 16 # Mark unreachable self.holddown_timer = self.HOLDDOWN_DURATION # flush_timer continues counting from its initial 240s def transition_to_garbage(self): """Flush timer expired, prepare for removal.""" print(f"[{self.destination}] Flush timer expired → removing") self.state = "garbage" # Route will be removed from table entirely # No longer advertised (even as metric 16) # Timeline visualization:# # T+0s T+30s T+60s T+90s T+120s T+150s T+180s T+210s T+240s# |--------|--------|--------|--------|--------|--------|--------|--------|# Last update received here# |# VALID STATE | HOLDDOWN STATE | REMOVED# (invalid_timer | (metric=16, |# counting down) | being poisoned) |# | |# invalid_timer=0 flush_timer=0# at T+180s at T+240s # During holddown (T+180 to T+240):# - Route still in table but marked unreachable# - Route advertised with metric 16 (poison)# - New routes subject to holddown rulesWhy Keep Routes After They're Invalid?
The 60-second gap between Invalid (180s) and Flush (240s) serves critical purposes:
1. Poison Distribution
The invalidated route continues to be advertised with metric 16. This ensures:
2. Hold-down Protection
The route entry must exist to track hold-down state. If removed immediately:
3. Table Consistency
Other protocol components (SNMP, CLI show commands, monitoring) may reference the route. Gradual removal prevents race conditions.
| Timer Event | Route State | Metric | In Routing Table? | Advertised? |
|---|---|---|---|---|
| Route learned | Valid | Learned value | Yes | Yes (actual metric) |
| Updates received | Valid (refreshed) | May update | Yes | Yes (actual metric) |
| Invalid timer expires | Hold-down | 16 | Yes (not used) | Yes (metric 16) |
| Flush timer expires | Garbage/Removed | N/A | No | No |
Dead routes consume memory until flushed. In networks with route flapping or many transient destinations, this can accumulate. The flush timer ensures eventual cleanup, but during instability, memory usage may spike temporarily.
Understanding how timers interact in realistic scenarios is essential for troubleshooting. Let's walk through several common situations.
123456789101112131415161718192021222324252627
# Scenario: Router R2 loses connectivity to neighbor R3# R2 had learned routes via R3 to networks X, Y, Z # Timeline (assuming link fails at T+0): # T+0s: Link R2-R3 goes down# R2 detects (interface down trap or keepalive failure)# TRIGGERED UPDATE: R2 immediately poisons routes X, Y, Z# Neighbors learn within 1-5 seconds (triggered update delay) # T+1-5s: Triggered updates propagate# All routers now know X, Y, Z unreachable via R2# Routes enter hold-down everywhere # T+30s: R2's periodic update confirms poison# Routes still in hold-down # T+180s: Hold-down expires on all routers# New routes to X, Y, Z would now be accepted# (If alternate paths exist, they may already be installed) # T+240s: Flush timer expires# Routes X, Y, Z removed from R2's table# Memory freed, no more advertisements # Total convergence time: ~1-5 seconds (thanks to triggered updates)# Compare to worst case without triggered updates: ~30 seconds1234567891011121314151617181920212223
# Scenario: Link becomes flaky, dropping packets intermittently# Updates may or may not get through # Timeline (link starts having 50% packet loss at T+0): # T+0s: Route to X via R3, metric 2, invalid_timer = 180# T+30s: Update from R3 LOST, invalid_timer = 150# T+60s: Update from R3 received! invalid_timer = 180 (reset)# T+90s: Update from R3 LOST, invalid_timer = 150# T+120s: Update from R3 LOST, invalid_timer = 120# T+150s: Update from R3 received! invalid_timer = 180 (reset)# T+180s: Update from R3 LOST, invalid_timer = 150# T+210s: Update from R3 LOST, invalid_timer = 120# T+240s: Update from R3 LOST, invalid_timer = 90# T+270s: Update from R3 LOST, invalid_timer = 60# T+300s: Update from R3 LOST, invalid_timer = 30# T+330s: Update from R3 LOST, invalid_timer = 0 → EXPIRED! # Result: With 50% loss, route eventually times out# But it takes much longer than 180s because occasional updates reset timer # This is why 6x multiplier (180s = 6 × 30s) is important:# Survives 5 consecutive losses, fails on 6th1234567891011121314151617181920212223242526272829
# Scenario: Link fails, then recovers while in hold-down# Original route: X via R3, metric 2 # T+0s: Link fails, route poisoned (metric 16)# Enter hold-down, holddown_timer = 180 # T+30s: Hold-down active, holddown_timer = 150# R4 advertises: X metric 5 (alternate path!)# Hold-down check: 5 > original 2 → REJECTED# R2 stays with metric 16 # T+60s: Link R2-R3 recovers!# R3 sends update: X metric 1 (direct connection)# Hold-down check: R3 is original next-hop → ACCEPTED# Route transitions back to valid: X via R3, metric 2# Hold-down cancelled # Note 1: Alternate path via R4 was rejected during hold-down# because metric 5 > original metric 2# This prevented potential loop # Note 2: Update from original next-hop (R3) immediately accepted# because next-hop is authoritative for its own routes # Alternative ending:# If R4 offered metric 1 (better than original 2):# T+30s: R4 advertises X metric 1# Hold-down check: 1 < original 2 → ACCEPTED!# Switch to new path immediately| Scenario | Update Timer | Invalid Timer | Hold-down | Total Convergence |
|---|---|---|---|---|
| Clean link failure (triggered update) | N/A | Not used | 180s (precautionary) | 1-5 seconds |
| Clean link failure (no triggered update) | 30s | 180s | 180s | ~30 seconds |
| Missed updates (link degrading) | 30s | 180s max (resets) | 180s when triggered | Variable (depends on loss pattern) |
| Recovery during hold-down | N/A | Cancelled | Cancelled if accept route | Immediate on valid update |
The difference between convergence in seconds versus minutes often comes down to whether triggered updates are properly configured and network conditions allow them to propagate. Ensure your network can deliver triggered updates reliably for best convergence.
While RIP's default timers work well for most networks, specific requirements may warrant adjustment. Tuning must be done carefully and consistently across all routers.
| Scenario | Adjustment Direction | Recommended Ratios | Risk |
|---|---|---|---|
| Fast convergence required | Reduce all timers | Update:Invalid:Holddown:Flush = 1:6:6:8 | Increased CPU, bandwidth, flapping |
| Unreliable WAN links | Increase invalid timer | Invalid ≥ 8 × Update | Slower failure detection |
| Large routing tables | Increase update timer | Reduce update frequency | Slower good news, stale routes |
| Mixed vendor network | Use defaults | 30:180:180:240 | None (safest choice) |
12345678910111213141516171819202122232425262728293031323334353637383940
# Cisco IOS Timer Configuration # View current timersRouter# show ip protocols Routing Protocol is "rip" Sending updates every 30 seconds, next due in 12 seconds Invalid after 180 seconds, hold down 180, flushed after 240 # Configure custom timers (must be done on ALL routers!)Router(config)# router ripRouter(config-router)# timers basic 30 180 180 240# ^ ^ ^ ^# | | | +-- flush timer# | | +------ hold-down timer# | +----------- invalid timer# +--------------- update timer # Fast convergence example (aggressive, for small stable networks)Router(config-router)# timers basic 10 60 60 80# Update every 10 seconds# Invalid after 60 seconds (6 × 10)# Hold-down for 60 seconds# Flush after 80 seconds (8 × 10)# WARNING: Increases bandwidth 3x, more sensitive to transients # Conservative example (for unreliable links)Router(config-router)# timers basic 30 270 270 360# Update every 30 seconds (default)# Invalid after 270 seconds (9 × 30) - tolerate 8 lost updates# Hold-down for 270 seconds - longer loop protection# Flush after 360 seconds (12 × 30) # Linux (quagga/frr) configuration# In ripd.conf:router rip timers basic 30 180 180 240 # VerificationRouter# show ip rip | include timers Update time 30 sec, invalid 180 sec, holddown 180 sec, flush 240 secCritical Rules for Timer Tuning
1. All Routers Must Match
If Router A expects updates every 10 seconds but Router B sends updates every 30 seconds, Router A will timeout routes prematurely. Timer mismatches cause constant route flapping.
2. Maintain Timer Ratios
Recommended ratios:
Invalid = 6 × Update (tolerate 5 lost updates)
Hold-down = Invalid (or slightly less)
Flush = Invalid + (2 × Update) (allow poison distribution)
3. Consider Jitter Impact
With shorter timers, jitter (±15%) becomes more significant:
Shorter timers with jitter may cause timing issues at boundaries.
4. Test Before Production
Timer changes should be tested in lab environments first. Simulate link failures, packet loss, and high CPU load to verify behavior.
If you need significantly faster convergence than RIP's defaults provide, consider whether RIP is the right protocol. OSPF converges in seconds with proper tuning. EIGRP converges almost instantly. Heavily-tuned RIP may still not meet requirements while introducing instability.
We've thoroughly examined RIP's timer mechanisms—the invisible machinery that governs route validity and network convergence. Let's consolidate the key insights:
What's Next
Now that we understand RIP's fundamental mechanisms, we'll examine RIPv2 improvements. The original RIP (v1) had significant limitations—no subnet mask support, no authentication, broadcast-only updates. RIPv2 addressed these while maintaining backward compatibility.
You now understand the complete timer system that governs RIP operation. You can predict convergence times, diagnose timing-related issues, and evaluate whether timer tuning is appropriate for specific requirements. Next, we'll explore how RIPv2 enhanced the protocol for modern networking.