Cold, warm, hot
Standby patterns form a spectrum, priced by how fast your spare can take over.
A cold standby is a machine image and a runbook, where nothing runs until disaster and failover takes hours. A warm one runs continuously and copies your data but serves nobody, so failover is seconds to minutes, mostly the time to notice and repoint. A hot one is fully caught up and already serving reads, so promoting it is nearly instant.
Build the classic arrangement from those. One primary does the work, a standby mirrors it, and a health check triggers the switch.
Make that switch one of two ways. Move a virtual address to the standby, fast but confined to one network. Or update DNS, which works anywhere and waits on cache lifetimes, and on clients that ignore them entirely.
Look at the productised version your cloud sells: a synchronous standby in a second data centre, with a DNS flip on failure that usually completes in a minute or two.
No spare at all
Drop the idea of a spare altogether and you get active-active, where every machine serves traffic and losing one simply means your balancer stops sending it requests. Failover becomes a non-event.
Watch the catch: state. A stateless web tier goes active-active almost for free. Two database machines both accepting writes must either divide the data or reconcile conflicts, a genuinely hard problem you should not take on casually.
Avoid the capacity trap that applies to either pattern. Machines running at 85 percent cannot absorb one of their number dying. Size so the survivors carry the full load, or your redundancy is decorative.
Worked example
Felipe's payments team runs Postgres active-passive with a warm standby and tests the promotion in staging: 40 seconds, clean. Then a real failover happens in production and their Java order service stays broken for 19 more minutes even though the database is healthy. The culprit is the JVM, which in their configuration caches successful DNS lookups forever, so every pooled connection keeps dialing the dead primary's IP. Nobody had tested failover against the actual application, only against psql. The fix is two lines, networkaddress.cache.ttl=5 plus a pool setting that retires connections after errors, and the next drill measures 52 seconds end to end, application included. Felipe adds a rule to the runbook: a database failover test that doesn't involve the app tests half the system.