Paxos Consensus Protocol

Imagine a bank replicates its account database across five data centers. This ensures no single failure erases customer balances. Every replica must apply the same sequence of updates in the same order. This must hold even when machines crash mid-transaction. It must also hold when the network drops or delays messages arbitrarily. This is the Consensus Problem in its most demanding form. Agreement must hold not in the friendly case, but in the adversarial case. In that case, any minority of participants may fail silently at the worst possible moment. The FLP Impossibility Result tells us something important. No deterministic algorithm can guarantee both safety and termination in a fully asynchronous system. This holds true even with just one faulty process. Paxos accepts this limit rather than fighting it. It guarantees safety always — the system never disagrees or corrupts its history. This holds no matter how badly the network behaves. It guarantees liveness only under sufficiently good conditions, such as a stable elected leader and bounded message delays. This asymmetric guarantee is precisely what real systems need. Correctness is non-negotiable, but progress can wait a few extra rounds during a storm. Paxos was introduced by Leslie Lamport. It is the ancestor of nearly every production consensus protocol in use today. Google's Chubby lock service, Apache ZooKeeper's ZAB protocol, Google Spanner's replicated log, and the log-replication core of Raft all descend from Paxos. Others were designed in direct response to it. Understanding Paxos means understanding why replicated logs, leader election, and quorum systems work the way they do. It is not just about knowing that they work. This article builds Paxos from first principles. It covers the roles of proposer, acceptor, and learner. It explains the two-phase protocol that lets a value survive crashes and restarts. It also gives a rigorous argument for why any two acceptors that decide, decide the same value. It closes by tracing the path toward Raft Consensus Algorithm and modern Replication and Consistency Models. These systems inherit Paxos's safety core. At the same time, they work hard to make its operation more comprehensible.

Key takeaways
  • Paxos solves consensus among unreliable processes: all non-faulty nodes agree on one value, safely, despite crashes and message loss.
  • Three roles — proposer, acceptor, learner — can be co-located on the same physical nodes; separating them clarifies the protocol's logic.
  • Consensus proceeds in two phases per round: Prepare/Promise (reserve a numbered round) then Accept/Accepted (propose a value in that round).
  • Safety rests on a single invariant: once a majority of acceptors accept a value in some round, every higher round is forced to propose that same value.
  • Paxos guarantees safety unconditionally; it only guarantees termination (liveness) when the network and a leader behave well enough, consistent with FLP Impossibility Result.
  • Multi-Paxos chains many single-value Paxos instances into a replicated log, forming the backbone that Raft Consensus Algorithm later re-engineered for teachability.
The core insight

Paxos achieves safe agreement by forcing every proposal round to first discover, and then re-propose, any value a majority may have already chosen.

The idea

Paxos separates the consensus problem into three logical roles. A proposer suggests a value it wants the system to agree on. An acceptor is a voting member that can promise not to accept old proposals and can accept new ones; agreement is defined in terms of what a majority (quorum) of acceptors accept. A learner simply finds out what value was chosen, typically by listening to acceptors or being told by the proposer that succeeded. In most deployments a single physical server plays all three roles simultaneously, but keeping the roles conceptually distinct is what makes the correctness argument tractable. Every proposal carries a strictly increasing round number (also called a ballot number or proposal number), unique to the proposer that issues it — typically constructed as `(counter, proposer_id)` so that ties never occur across different proposers. The protocol's central mechanism is that a higher round number can always interrupt a lower one, but any proposer using a higher round is obligated to re-propose whatever value a majority may already have locked in, rather than inventing a new value freely. This is the crux of the design: rounds provide liveness through retry (a stalled proposer can always try again with a bigger number), while the value-carrying obligation provides safety (no round can silently overwrite a decision already made). A value is considered chosen once a majority of acceptors have accepted it in the same round. Because any two majorities among nn acceptors must overlap in at least one acceptor (this is the Quorum Intersection property), no two different values can ever both be chosen — the overlapping acceptor cannot have accepted two different values in the round that chose them, and the protocol's rules ensure it cannot be tricked into doing so across rounds either. This single overlap fact, formalized below, is the entire safety proof of Paxos.

How it works

Single-decree Paxos decides one value. It runs in two phases, each itself a request/response round between a proposer and the acceptors.

Phase 1 (Prepare / Promise). The proposer picks a round number nn higher than any it has used before, and sends `Prepare(n)` to a majority of acceptors. An acceptor that receives `Prepare(n)`:

  • If nn is greater than every round number it has seen so far, it replies `Promise(n, acceptedRound, acceptedValue)` — promising never again to accept any proposal numbered less than nn — and reports the highest-numbered value it has already accepted, if any.
  • Otherwise it ignores the request (or replies with a rejection carrying the higher round it already knows about, so the proposer can jump ahead).

Phase 2 (Accept / Accepted). If the proposer receives `Promise` replies from a majority, it must choose the value to propose:

  • If any acceptor reported a previously accepted value, the proposer must adopt the value from the highest `acceptedRound` among the replies — it is not free to substitute its own value.
  • Only if no acceptor reported any previously accepted value may the proposer use its own value.

It then sends `Accept(n, value)` to the same majority. An acceptor that receives `Accept(n, value)` accepts it (recording `acceptedRound = n`, `acceptedValue = value`) unless it has already promised a higher round number in Phase 1, in which case it rejects the request. Once a majority accepts (n,value)(n, value), that value is chosen and can be revealed to learners — typically by having acceptors notify learners directly, or by having the proposer collect enough `Accepted` messages and broadcast the decision itself. ```pseudocode

# --- Proposer --- function Propose(proposer_id, value_hint): n = next_round_number(proposer_id) # e.g., (counter++, proposer_id), globally unique & increasing promises = []

# Phase 1: Prepare send Prepare(n) to all acceptors wait for replies from a majority of acceptors, or timeout for each reply Promise(n, acc_round, acc_value) received: promises.append((acc_round, acc_value)) if reply is Reject(higher_n): n = higher_n + bump() # back off and retry with a bigger number goto Phase 1 if len(promises) < majority_count: retry with a larger n after backoff # no quorum reached -> contention or failures

# Determine the value to propose if any promise carries a non-null acc_value: value = value_with_highest(acc_round, promises) # MUST reuse this value else: value = value_hint # free to propose own value

# Phase 2: Accept send Accept(n, value) to the same majority wait for Accepted(n) replies, or timeout / Reject if majority of Accepted(n) received: value is CHOSEN notify learners(value) return value else: retry with a larger n after backoff

# --- Acceptor state (persisted to stable storage before replying) --- promised_round = -infinity accepted_round = -infinity accepted_value = null function OnPrepare(n): if n > promised_round: promised_round = n persist(promised_round) reply Promise(n, accepted_round, accepted_value) else: reply Reject(promised_round) function OnAccept(n, value): if n >= promised_round: promised_round = n accepted_round = n accepted_value = value persist(promised_round, accepted_round, accepted_value) reply Accepted(n) else: reply Reject(promised_round) ``` Two subtleties matter for correctness. First, an acceptor must persist its state to stable storage before replying — otherwise a crash-and-restart could forget a promise and violate safety. Second, the `OnAccept` check uses `n >= promised_round` rather than strict equality: an acceptor may `Accept` a proposal numbered exactly equal to its highest promise, since that promise was made for this very proposer's Phase 2 message.

Figure
Paxos roles and the two-phase message flow across a majority quorum
Proposer P Acceptor A1 Acceptor A2 Acceptor A3 Acceptor A4 (down) Learner L Prepare(n) Promise(n, ⟨rd,val⟩) Phase 2 (majority reached with A1, A2) Accept(n, value) Accepted(n) → chosen Majority = 3 of 4 acceptors — one crashed acceptor cannot block progress or safety

To decide many values in sequence — the realistic case of a replicated log — systems run Multi-Paxos: a single elected proposer (the leader) runs Phase 1 once to reserve a round number for an entire range of log slots, then repeatedly runs only Phase 2 for each new log entry, skipping Phase 1 as long as it remains the recognized leader. This amortizes the expensive round-trip of Phase 1 across many decisions, reducing steady-state latency to one round trip per committed value.

Complexity & correctness

Message and round complexity. In the common case with no contention, single-decree Paxos takes exactly two round trips: Prepare/Promise, then Accept/Accepted, each fanning out to a majority of nn acceptors. This is O(n)O(n) messages per phase, so O(n)O(n) total messages and O(1)O(1) round trips when uncontested. Under contention — two proposers dueling with increasing round numbers — the protocol can, in principle, retry indefinitely (the classical "dueling proposers" livelock), which is exactly the manifestation of FLP Impossibility Result: no bound on termination can be guaranteed in a fully asynchronous, failure-prone system. Practical deployments break this livelock by electing a stable leader (via randomized backoff or a separate leader-election protocol), reducing steady state to O(1)O(1) round trips per decision.

Fault tolerance. With 2f+12f+1 acceptors, Paxos tolerates up to ff crash failures while still being able to form a majority quorum of f+1f+1. It does not tolerate Byzantine (malicious/arbitrary) failures without extension — the base protocol assumes acceptors follow the algorithm faithfully but may crash and restart.

Space. Each acceptor stores only O(1)O(1) state — its highest promised round and its single highest accepted `(round, value)` pair — regardless of how many proposers have competed, because the protocol only ever needs the latest promise and latest acceptance to answer future messages correctly.

Correctness proof sketch (safety). The goal is: if a value vv is chosen (accepted by a majority) in round nn, then any value chosen in any round n′>nn' > n is also vv. The proof is by strong induction on round number. Invariant P1 (quorum intersection). Any two majority sets of acceptors, out of a fixed total set, share at least one common acceptor. This is pure counting: if ∣Q1∣,∣Q2∣>n/2|Q_1|, |Q_2| > n/2 out of nn acceptors, then ∣Q1∩Q2∣≥∣Q1∣+∣Q2∣−n>0|Q_1 \cap Q_2| \ge |Q_1| + |Q_2| - n > 0. Invariant P2. If a proposer issues `Accept(n, v)`, then either no acceptor has accepted any value in a round less than nn, or vv is the value with the highest-numbered prior acceptance reported to that proposer in its Phase 1 promises. Invariant P2a. If `Accept(n, v)` can be issued, then vv equals the value of every value chosen (by a majority) in any round less than nn.

Base case: Let n0n_0 be the smallest round in which a value vv is chosen (accepted by majority Q0Q_0). By definition no earlier chosen value exists, so vacuously P2a holds for n0n_0.

Inductive step: Suppose P2a holds for all rounds less than nn, and suppose `Accept(n, v')` is issued by some proposer PP. We must show v′=vv' = v for the (unique, by induction hypothesis extended) value vv chosen in n0≤n′<nn_0 \le n' < n. By the Phase 1 rule, PP collected `Promise` replies from a majority QQ before issuing `Accept(n, v')`. By quorum intersection (P1), Q∩Q0≠∅Q \cap Q_0 \ne \emptyset: some acceptor aa is in both the majority that chose vv in round n0n_0 and the majority that promised PP in round nn. Because aa accepted vv at round n0≤n′<nn_0 \le n' < n, and aa replied to `Prepare(n)` with `Promise` (meaning nn is greater than every round aa has promised, hence greater than n0n_0 at the time it accepted), aa's reply must report `acceptedRound \ge n_0` and some accepted value. By the Phase 2 rule, PP is forced to adopt the value carried by the highest `acceptedRound` among all its promises — and since a chosen value's acceptance is reported by at least one acceptor in every subsequent majority (by repeated quorum intersection across every round between n0n_0 and nn), that highest-reported value must be vv. Hence v′=vv' = v. ■\blacksquare This gives the headline safety guarantee: at most one value is ever chosen, and it is chosen permanently — no failure pattern, message reordering, or duplicate delivery can unchoose it or cause a different value to be chosen afterward. Liveness (that some value eventually gets chosen) is explicitly not guaranteed in general, matching FLP; it requires eventual synchrony and typically a stable leader in practice.

Tradeoff to be honest about: Paxos as described is famously hard to map onto engineering artifacts — the roles blur, log compaction and membership changes are not specified by the base algorithm, and "which proposer is the leader right now" is left to auxiliary mechanisms. This gap is exactly what motivated Raft Consensus Algorithm, which keeps Paxos's safety core but bakes in leader election and log matching as first-class, more mechanically checkable rules.

Worked example

Consider 55 acceptors A1…A5A_1 \ldots A_5 (majority = 3). Two proposers, PP (client wants value `"X"`) and QQ (client wants value `"Y"`), race after a partial network partition.

StepActorActionAcceptor states after step
1PP`Prepare(n=1)` to A1,A2,A3A_1, A_2, A_3all three promise: `(promised=1, accepted=∅)`
2A1,A2,A3A_1,A_2,A_3reply `Promise(1, ∅, ∅)` to PPno prior value reported
3PPmajority reached, no prior value ⇒ free to use `"X"`. Sends `Accept(1, "X")` to A1,A2,A3A_1,A_2,A_3—
4A1A_1accepts: `(promised=1, accepted=1, val="X")`—
5network hiccup`Accept(1,"X")` to A2,A3A_2, A_3 is delayed/lostA2,A3A_2, A_3 still only `(promised=1)`
6QQunaware of PP, sends `Prepare(n=2)` to A2,A3,A4A_2, A_3, A_4
7A2A_22>12 > 1: promise, reply `Promise(2, ∅, ∅)` (has not yet seen `Accept(1,"X")`)`(promised=2)`
8A3A_3same, `Promise(2, ∅, ∅)``(promised=2)`
9A4A_4fresh, `Promise(2, ∅, ∅)``(promised=2)`
10QQmajority (3), none report prior value ⇒ free to propose `"Y"`. Sends `Accept(2, "Y")` to A2,A3,A4A_2, A_3, A_4
11A2,A3,A4A_2, A_3, A_4all promised ≤2, accept: `(promised=2, accepted=2, val="Y")`majority now holds `"Y"`
12A1A_1delayed `Accept(1,"X")` retried, but A1A_1 never promised past round 1 in this trace, so it would still accept a duplicate `Accept(1,"X")` — harmless, A1A_1 alone is not a majority`(accepted=1,val="X")` unchanged in effect
13—Result: `"Y"` is chosen (3 acceptors: A2,A3,A4A_2,A_3,A_4). `"X"` was never chosen (only 1 acceptor, not a majority)consensus = `"Y"`

Now suppose instead that step 5 had not lost the message — A2A_2 had already accepted `(1,"X")` before QQ's `Prepare(2)` arrived. Then in step 7, A2A_2's `Promise(2, ...)` reply would carry `acceptedRound=1, acceptedValue="X"`. At step 10, QQ is no longer free to propose `"Y"`: the rule forces QQ to adopt the highest-numbered reported value, which is `"X"` from A2A_2. QQ sends `Accept(2, "X")` instead, and the same value `"X"` ends up chosen — illustrating exactly the safety mechanism proved above: a higher round can steal the round number, but never the already-chosen value.

Common pitfalls & traps

PitfallWhy it breaks PaxosFix
Reusing round numbers across proposersTwo proposers with the same nn can race and violate the "nn strictly increasing, globally unique" assumption, breaking the safety proof's use of promisesEmbed a unique proposer ID in the low bits of every round number
Acceptor replies before persisting to diskA crash-restart can forget a promise, allowing a stale acceptor to later accept a conflicting valuePersist `promised_round`/`accepted_round`/`accepted_value` to stable storage before sending any reply
Proposer substitutes its own value despite a reported prior acceptanceDirectly violates Invariant P2/P2a — this is the single most common implementation bug and can silently lose committed dataAlways adopt the value from the highest `acceptedRound` among Promise replies; never override it
Treating "received Promise from a majority" as decisionA quorum of promises only reserves the round; the value is not chosen until a quorum accepts in Phase 2Only declare a value chosen after a majority of `Accepted` replies for the same round
Assuming Paxos guarantees terminationContention between competing proposers can livelock forever (dueling proposers), consistent with FLPElect a stable leader with randomized backoff so only one proposer normally runs Phase 2 repeatedly
Skipping Phase 1 without ever having run it as the current leaderMulti-Paxos's optimization to skip Prepare is only safe once a leader owns an uncontested round number for that log rangeRun Phase 1 for a full round on leader election before accepting client writes with the fast path

Pioneers & history

Paxos originated from Leslie Lamport's whimsical framing of the problem as the parliamentary procedure of a fictional Greek island, "The Part-Time Parliament" — a paper so playfully written that its seriousness was initially missed by reviewers and readers alike. Lamport later republished a plainer version, "Paxos Made Simple," to correct the reception. The protocol is now considered one of the most important and most subtly difficult results in distributed computing, spawning an entire cottage industry of "Paxos made X" papers (live, simple, moderately complex, practical) attempting to make it more approachable to engineers.

Origins · 1 minLeslie B. Lamport and how this was discovered
LB
Leslie B. Lamport
born 1941 · Distributed systems theorist at Microsoft Research; formalized logical clocks, Byzantine generals, and the Paxos consensus algorithm

History
1989
— Lamport circulates "The Part-Time Parliament" internally, describing what becomes known as Paxos.
1998
— The paper is finally published in ACM Transactions on Computer Systems after years of the review committee treating its framing as satire.
2001
— Lamport publishes "Paxos Made Simple," a stripped-down and widely cited exposition.
2006
— Google's Chubby lock service paper describes a production Multi-Paxos deployment underpinning distributed locking at scale.
2014
— Diego Ongaro and John Ousterhout publish Raft, explicitly designed to preserve Paxos's guarantees while improving understandability.

Where this matters

Paxos and its Multi-Paxos extension underlie some of the most widely used distributed infrastructure in existence. Google's Chubby lock service and Spanner database use Paxos-replicated logs to keep metadata and transaction ordering consistent across data centers spanning continents. Apache ZooKeeper, used for coordination in Kafka, HBase, and countless distributed systems, runs ZAB, a protocol built on the same quorum-and-round principles as Paxos, tailored for a primary-backup log. Cloud object stores and distributed databases (CockroachDB, YugabyteDB, TiDB) use Paxos-family protocols, often via the Raft variant, to replicate ranges of key-value data with strong consistency guarantees. Understanding Paxos is prerequisite to understanding Raft Consensus Algorithm (a deliberately more teachable re-derivation of the same guarantees) and to reasoning correctly about Replication and Consistency Models used throughout modern cloud databases and coordination services.

Ask the tutor: How does Raft's leader-election term mechanism map onto Paxos's round numbers, and why does that mapping make Raft easier to reason about?

Is Paxos the same as the general consensus problem?

No. The consensus problem is the abstract specification — all correct processes must agree on one proposed value, and that value must have actually been proposed. Paxos is one concrete algorithm satisfying this specification under crash failures and asynchronous networks. Other algorithms, such as Raft or ZAB, satisfy the same specification with different internal mechanics, different engineering tradeoffs, and different degrees of implementation clarity. Understanding the consensus problem first (its safety and liveness requirements, and the impossibility results bounding it) is what lets you evaluate Paxos as one particular solution rather than as the definition of consensus itself.

Does Paxos tolerate Byzantine failures, like malicious nodes lying about their state?

No, not in its basic form. Classic Paxos assumes acceptors are "crash-stop": they may fail by halting, and may restart and rejoin, but they never deviate from the protocol while running — they don't lie about their promised round, forge messages, or intentionally violate the rules. Tolerating arbitrary, malicious deviation requires a different family of protocols, generally called Byzantine fault-tolerant (BFT) consensus, exemplified by PBFT and its descendants, which need 3f+13f+1 replicas to tolerate ff Byzantine failures rather than the 2f+12f+1 that plain Paxos needs for crash failures.

Why does an acceptor need to persist its state to stable storage before replying?

Because the safety proof relies on an acceptor never "forgetting" a promise or a prior acceptance. If an acceptor crashes after promising round nn but before writing that promise to disk, and then restarts and forgets it made that promise, it might later accept a lower-numbered proposal it should have rejected — reintroducing exactly the conflict the protocol is designed to prevent. Persisting `promised_round`, `accepted_round`, and `accepted_value` before sending any reply ensures a restarted acceptor resumes with full knowledge of every commitment it has made, preserving the quorum-intersection argument across crashes.

What is "dueling proposers" and how do real systems avoid it?

Dueling proposers is a livelock: proposer PP completes Phase 1 with round nn, but before it finishes Phase 2, proposer QQ runs Phase 1 with round n+1n+1, invalidating PP's in-flight Accept messages. PP retries with round n+2n+2, invalidating QQ's Phase 2, and so on forever — a direct real-world manifestation of the FLP Impossibility Result's limit on guaranteed termination. Real systems avoid this in practice, though never in theory, by electing a single stable leader (using randomized timeouts and backoff so simultaneous leader attempts become statistically rare) so that only one proposer typically runs Phase 2 repeatedly across many decisions.

How does Multi-Paxos differ from running single-decree Paxos repeatedly?

Running single-decree Paxos independently for each log slot would require two full round trips (Prepare/Promise then Accept/Accepted) per decision, doubling latency and message volume. Multi-Paxos instead has an elected leader run Phase 1 once, reserving its round number across an entire range of future log slots, and then runs only Phase 2 for each new client command, referencing the already-reserved round. This reduces steady-state commit latency to a single round trip per decision, at the cost of needing an explicit (if informal) leader-election and lease mechanism that base Paxos does not specify.

Why did Raft get invented if Paxos already solves consensus?

Paxos's safety guarantees are airtight, but the base algorithm intentionally under-specifies practical concerns: how leaders are elected, how logs are kept consistent and compacted, and how cluster membership changes safely. Engineers building real systems on Paxos had to invent these mechanisms themselves, often incompatibly, leading to notoriously subtle bugs. Raft Consensus Algorithm was designed explicitly for understandability: it decomposes the problem into leader election, log replication, and safety as separate, more mechanically verifiable pieces, while preserving the same fundamental safety guarantee proved for Paxos via quorum intersection.

Does Paxos require an odd number of acceptors?

It is not strictly required, but it is the efficient choice. With nn acceptors, majority size is ⌊n/2⌋+1\lfloor n/2 \rfloor + 1, and fault tolerance is f=⌈n/2⌉−1f = \lceil n/2 \rceil - 1. Going from 2f+12f+1 (odd) to 2f+22f+2 (even) acceptors does not increase the number of tolerable failures ff at all — it only adds an acceptor without adding fault tolerance, while increasing quorum size and message overhead. Production systems therefore almost always deploy an odd number of acceptors, such as 3 or 5, to get the maximum fault tolerance per unit of replication cost.

Before this

Where this leads