The most surprising thing about etcd’s static bootstrap is that it’s not about "bootstrapping" in the sense of dynamically discovering peers; it’s about pre-defining the entire cluster membership from day one.
Let’s see etcd in action with a static bootstrap. Imagine we’re setting up a three-node etcd cluster: etcd-0, etcd-1, and etcd-2.
First, on each node, we need to generate a unique peer URL and client URL. For etcd-0:
ETCD_NAME=etcd-0
ETCD_INITIAL_CLUSTER="etcd-0=http://10.0.1.10:2380,etcd-1=http://10.0.1.11:2380,etcd-2=http://10.0.1.12:2380"
ETCD_INITIAL_CLUSTER_STATE=new
ETCD_LISTEN_PEER_URLS="http://10.0.1.10:2380"
ETCD_LISTEN_CLIENT_URLS="http://10.0.1.10:2379"
ETCD_ADVERTISE_CLIENT_URLS="http://10.0.1.10:2379"
ETCD_INITIAL_ADVERTISE_PEER_URLS="http://10.0.1.10:2380"
ETCD_DATA_DIR="/var/lib/etcd"
ETCD_NAME=etcd-0
etcd \
--name ${ETCD_NAME} \
--data-dir ${ETCD_DATA_DIR} \
--listen-peer-urls ${ETCD_LISTEN_PEER_URLS} \
--listen-client-urls ${ETCD_LISTEN_CLIENT_URLS} \
--advertise-client-urls ${ETCD_ADVERTISE_CLIENT_URLS} \
--initial-advertise-peer-urls ${ETCD_INITIAL_ADVERTISE_PEER_URLS} \
--initial-cluster ${ETCD_INITIAL_CLUSTER} \
--initial-cluster-state ${ETCD_INITIAL_CLUSTER_STATE}
You’d repeat this on etcd-1 and etcd-2, adjusting ETCD_NAME and the IP addresses in ETCD_LISTEN_PEER_URLS, ETCD_LISTEN_CLIENT_URLS, ETCD_ADVERTISE_CLIENT_URLS, and ETCD_INITIAL_ADVERTISE_PEER_URLS to match their respective IPs (e.g., 10.0.1.11 for etcd-1). The key is that ETCD_INITIAL_CLUSTER is identical on all nodes, listing all members and their peer addresses.
This process sets up a cluster where each node knows about all other nodes from the very beginning. When etcd-0 starts, it sees etcd-1 and etcd-2 in its initial-cluster configuration and attempts to connect to their peer URLs (http://10.0.1.11:2380 and http://10.0.1.12:2380). Once a quorum (more than half of the nodes) is established, the cluster forms.
The problem this solves is providing a deterministic, single-pass cluster initialization for environments where you can pre-define all participating nodes. This is common in single-cloud Kubernetes control planes or dedicated etcd clusters where IPs are static and known. It avoids the complexity of discovery services or manual peer addition for the initial setup.
Internally, etcd uses the initial-cluster and initial-cluster-state=new flags to bootstrap a new cluster. The initial-advertise-peer-urls tells other etcd nodes how to reach this specific instance for peer communication, while listen-peer-urls is what this instance listens on. Similarly, advertise-client-urls tells clients how to reach this node, and listen-client-urls is where etcd listens for client requests. The initial-cluster-state=new signals that this is the very first time these members are forming a cluster.
When a new member joins an existing cluster (not the initial bootstrap), you’d typically use etcdctl member add and set initial-cluster-state=existing. The static bootstrap is specifically for the creation of a brand-new cluster where all nodes are known upfront.
The most common pitfall with static bootstrap is an inconsistent ETCD_INITIAL_CLUSTER definition across nodes, or incorrect ETCD_INITIAL_ADVERTISE_PEER_URLS which prevents nodes from finding each other. If you define etcd-0 as http://10.0.1.10:2380 on node etcd-0 but http://192.168.1.10:2380 on node etcd-1, they won’t be able to establish peer connections, and the cluster will fail to form.
Once your etcd cluster is up and running, the next logical step is to explore how to perform rolling updates without downtime.