16. Cheat sheet
Consul · 16. Cheat sheetA production server configuration
Section titled “A production server configuration”datacenter = "dc1"data_dir = "/opt/consul"node_name = "srv-1"server = truebootstrap_expect = 3bind_addr = "10.0.1.5"client_addr = "0.0.0.0"retry_join = ["10.0.1.5", "10.0.1.6", "10.0.1.7"]
ui_config { enabled = true }
performance { raft_multiplier = 1 # THE production setting. Default 5.}
encrypt = "3Qc0dLxJ1LEnaTBRnrPVL/BsFBs4gDLXCaLDF9nq6/Y="encrypt_verify_incoming = trueencrypt_verify_outgoing = true
tls { defaults { ca_file = "/etc/consul.d/tls/ca.pem" cert_file = "/etc/consul.d/tls/server.pem" key_file = "/etc/consul.d/tls/server-key.pem" verify_incoming = true verify_outgoing = true } internal_rpc { verify_server_hostname = true } # impersonation}
ports { http = -1 # no plaintext API https = 8501 grpc = 8502}
acl { enabled = true default_policy = "deny" down_policy = "extend-cache" enable_token_persistence = true tokens { agent = "..." dns = "..." }}
connect { enabled = true }
autopilot { cleanup_dead_servers = true server_stabilization_time = "10s"}
telemetry { prometheus_retention_time = "60s" disable_hostname = true}
enable_local_script_checks = true # NOT enable_script_checksenable_script_checks = false # RCE via the agent API
dns_config { allow_stale = true max_stale = "10s" use_cache = true cache_max_age = "10s" service_ttl = { "*" = "5s" }}A client agent
Section titled “A client agent”datacenter = "dc1"data_dir = "/opt/consul"retry_join = ["10.0.1.5", "10.0.1.6", "10.0.1.7"]encrypt = "..."performance { raft_multiplier = 1 }acl { enabled = true, default_policy = "deny", down_policy = "extend-cache", tokens { agent = "..." dns = "..." } }A service definition
Section titled “A service definition”service { id = "orders-8080" # UNIQUE PER NODE. Defaults to name! name = "orders" port = 8080 tags = ["v2", "primary"] meta = { version = "1.4.2", team = "commerce", protocol = "http" }
check { id = "orders-ready" http = "http://localhost:8080/ready" interval = "10s" timeout = "5s" success_before_passing = 2 failures_before_critical = 3 deregister_critical_service_after = "10m" }
connect { sidecar_service { proxy { mode = "transparent" } } }}Health check types
Section titled “Health check types”check { http = "http://localhost:8080/ready" interval = "10s" } # 2xx pass, # 429 warncheck { tcp = "localhost:5432" interval = "10s" } # weakcheck { udp = "localhost:53" interval = "10s" }check { grpc = "localhost:9090/svc.Health" grpc_use_tls = true }check { args = ["/check.sh","/data"] interval = "60s" } # 0/1/othercheck { docker_container_id = "abc" shell="/bin/sh" args=["/h.sh"] }check { ttl = "30s" } # app reportscheck { alias_service = "orders-db" } # CASCADE RISKcurl -X PUT localhost:8500/v1/agent/check/pass/<id> -d 'note'curl -X PUT localhost:8500/v1/agent/check/warn/<id>curl -X PUT localhost:8500/v1/agent/check/fail/<id>Detection time = interval × failures_before_critical.
<service>.service.consul healthy instances, shuffled <tag>.<service>.service.consul filtered by tag <service>.service.<dc>.consul another datacentre <node>.node.consul <query>.query.consul prepared query <service>.connect.consul mesh instances <service>.virtual.consul mesh virtual IPdig @127.0.0.1 -p 8600 orders.service.consuldig @127.0.0.1 -p 8600 orders.service.consul SRV # gives the PORTdig @127.0.0.1 -p 8600 primary.postgres.service.consul# systemd-resolved[Resolve]DNS=127.0.0.1:8600Domains=~consul
# dnsmasqserver=/consul/127.0.0.1#8600UDP caps the answer at ~25 records. The JVM caches DNS forever — set networkaddress.cache.ttl=0.
HTTP API
Section titled “HTTP API”# discovery — USE THE HEALTH ENDPOINT, not the catalogcurl "localhost:8500/v1/health/service/orders?passing"curl "localhost:8500/v1/health/service/orders?passing&tag=primary&dc=dc2"curl "localhost:8500/v1/health/service/orders?filter=Service.Meta.version==\"1.4.2\""curl "localhost:8500/v1/health/state/critical" # FIRST in an incident
# catalog — includes UNHEALTHY instancescurl localhost:8500/v1/catalog/servicescurl localhost:8500/v1/catalog/service/orderscurl localhost:8500/v1/catalog/nodescurl localhost:8500/v1/catalog/datacenters
# agentcurl localhost:8500/v1/agent/servicescurl localhost:8500/v1/agent/checkscurl localhost:8500/v1/agent/memberscurl localhost:8500/v1/agent/metrics?format=prometheuscurl -X PUT localhost:8500/v1/agent/service/register -d @svc.jsoncurl -X PUT localhost:8500/v1/agent/service/deregister/orders-1curl -X PUT localhost:8500/v1/agent/maintenance?enable=true\&reason=deploy
# consistency?stale any server, scales, works with NO leader ← discovery (the default for DNS)(none) the leader, without confirming leadership ← API default?consistent the leader, confirming leadership ← locks, CASX-Consul-Index: 128401 ← feed back for blocking queriesX-Consul-KnownLeader: trueX-Consul-LastContact: 12 ← ms of stalenessBlocking queries
Section titled “Blocking queries”curl -i "localhost:8500/v1/health/service/orders?passing"# X-Consul-Index: 128401curl "localhost:8500/v1/health/service/orders?passing&index=128401&wait=5m"Always send back the index. An index that goes backwards is a reset — start from 0.
KV store
Section titled “KV store”consul kv put config/orders/timeout 30consul kv get config/orders/timeoutconsul kv get -detailed config/orders/timeout # ModifyIndexconsul kv get -recurse config/orders/consul kv delete -recurse config/orders/consul kv export config/ > backup.jsonconsul kv import @backup.json
# check-and-setconsul kv put -cas -modify-index=128401 config/orders/timeout 60consul kv put -cas -modify-index=0 config/new "v" # create only
# HTTP — values are base64curl localhost:8500/v1/kv/config/orders/timeout | jq -r '.[0].Value' | base64 -dcurl -X PUT localhost:8500/v1/txn -d '[{"KV":{"Verb":"cas", ...}}]' # max 64512 KB per value. In memory on every server. Every write is a Raft commit. Secrets → Vault. Data → a database. Blobs → object storage.
Sessions and locks
Section titled “Sessions and locks”curl -X PUT localhost:8500/v1/session/create -d '{ "Name": "orders-leader", "Checks": ["serfHealth", "orders-ready"], "LockDelay": "15s", "TTL": "30s", "Behavior": "release"}'
curl -X PUT "localhost:8500/v1/kv/service/orders/leader?acquire=<session>"curl -X PUT "localhost:8500/v1/kv/service/orders/leader?release=<session>"curl -X PUT localhost:8500/v1/session/renew/<id>
consul lock service/orders/leader ./my-processconsul lock -n 3 service/workers ./worker # semaphoreA lock without a fencing token the resource checks is a strong suggestion. Use
ModifyIndex— it only increases:UPDATE r SET data=?, fence=? WHERE fence < ?
Kind = "service-defaults"Name = "orders"Protocol = "http" # REQUIRED for L7 featuresKind = "service-intentions"Name = "orders"Sources = [ { Name = "web", Action = "allow" }, { Name = "*", Action = "deny" },]Kind = "service-resolver"Name = "orders"DefaultSubset = "stable"Subsets = { stable = { Filter = "Service.Meta.version == \"1.4.2\"" } canary = { Filter = "Service.Meta.version == \"1.5.0\"" }}Failover = { "canary" = { Targets = [{ ServiceSubset = "stable" }] } }LoadBalancer = { Policy = "maglev" HashPolicies = [{ Field="header" FieldValue="x-session-id" }] }Kind = "service-splitter" # weights MUST sum to 100Name = "orders"Splits = [ { Weight = 95, ServiceSubset = "stable" }, { Weight = 5, ServiceSubset = "canary", RequestHeaders = { Set = { "x-canary" = "true" } } },]Kind = "service-router" # HTTP onlyName = "orders"Routes = [ { Match = { HTTP = { Header = [{ Name="x-beta", Exact="true" }] } } Destination = { Service = "orders", ServiceSubset = "canary" } }, { Match = { HTTP = { PathPrefix = "/" } } Destination = { Service = "orders", NumRetries = 3, RetryOnConnectFailure = true } },]Order: ROUTER → SPLITTER → RESOLVER.
consul config write orders.hclconsul config read -kind service-resolver -name ordersconsul config list -kind service-splitterconsul config delete -kind service-router -name orders
consul intention create -allow web ordersconsul intention check web ordersconsul connect envoy -sidecar-for orders
# the sidecar is the AUTHORITYcurl localhost:19000/config_dump | jqcurl localhost:19000/clusterscurl localhost:19000/certscurl localhost:19000/stats | grep upstream_rq_5xxconsul acl bootstrap # ONCE. Save the SecretID.consul acl policy create -name p -rules @p.hclconsul acl role create -name orders -policy-name pconsul acl token create -role-name ordersconsul acl token create -service-identity "orders"consul acl token create -node-identity "web-1:dc1"consul acl token listconsul acl token update -id anonymous -policy-name dns-read
export CONSUL_HTTP_TOKEN=...curl -H "X-Consul-Token: ..." https://localhost:8501/v1/kv/xservice "orders" { policy = "write" }service_prefix "" { policy = "read" }node_prefix "" { policy = "read" }key_prefix "config/orders/" { policy = "read" }session_prefix "" { policy = "write" }# deny beats write. Resources: agent node service key session event# query acl operator mesh peering keyringMigrate: default_policy = "allow" → collect “Permission denied” logs → fix → "deny".
DNS uses the anonymous token — deny-by-default breaks it.
Operations
Section titled “Operations”consul members consul members -detailedconsul infoconsul monitor -log-level=debugconsul rtt web-1 web-2 # measured latency
consul operator raft list-peersconsul operator raft remove-peer -id=<id> # REMOVE BEFORE ADDconsul operator raft transfer-leaderconsul operator autopilot state # read Failure Toleranceconsul operator autopilot get-config
consul snapshot save backup.snap # contains EVERY ACL TOKENconsul snapshot inspect backup.snapconsul snapshot restore backup.snap
consul maint -enable -service=orders -reason="deploy"consul maint -disable -service=ordersconsul leave # gracefulconsul force-leave -prune <node> # gone for good
consul keyring -install=<new> # 1consul keyring -list # VERIFYconsul keyring -use=<new> # 2consul keyring -remove=<old> # 3
consul reloadconsul validate /etc/consul.d/ 8300 TCP server RPC servers only 8301 TCP+UDP LAN gossip every agent — BOTH PROTOCOLS 8302 TCP+UDP WAN gossip servers only 8500 TCP HTTP API + UI 8501 TCP HTTPS API 8502 TCP gRPC (Envoy xDS) 8600 TCP+UDP DNS 19000 TCP Envoy sidecar adminMetrics
Section titled “Metrics”consul_autopilot_healthy == 0consul_autopilot_failure_tolerance == 0 # THE alertsum(consul_raft_state_leader) != 1 # zero or two leadersconsul_raft_commitTime # includes fsync — rises FIRSTconsul_raft_leader_lastContactconsul_raft_apply consul_kvs_applyconsul_serf_member_failed consul_serf_member_flapconsul_runtime_alloc_bytesconsul_client_rpc_exceededSizing
Section titled “Sizing” servers small 2 vCPU 8 GB · medium 4/16 GB · large 8/32 GB LOCAL SSD — every Raft commit is an fsync agents ~50 MB, negligible CPU
3 servers → tolerate 1 · 5 → tolerate 2 · NEVER an even number
DNS cached ~0.2 ms · uncached ~1-2 ms · KV write ~5-20 ms reads tens of thousands/s · writes several hundred/sFailure detection times
Section titled “Failure detection times” clean shutdown (deregister on SIGTERM) ~100 ms, no lost requests process crash (interval × failures) ~30 s machine death (gossip) ~5 s leader failure (raft_multiplier = 1) ~1 s (raft_multiplier = 5) ~5 sFifteen rules
Section titled “Fifteen rules”raft_multiplier = 1in production. The default of 5 is for laptops.- Three or five servers. Never an even number.
- Local SSD for servers. Every commit is an fsync.
idmust be unique per node — it defaults toname.- Use
/v1/health/service/X?passing, not the catalog endpoint. - Add hysteresis to checks, and set a timeout above your real p99.
- Set
deregister_critical_service_after, longer than your worst legitimate outage. - Never let one health endpoint call another service’s.
- Deregister on
SIGTERM. ~100 ms and no lost requests. consul keyring -listbetween every rotation step.verify_server_hostname = true, andenable_script_checks = false.- Store the bootstrap token before doing anything else.
- Remove a dead server before adding its replacement.
- A lock needs a fencing token the resource checks, or it is a suggestion.
- A dead address that persists is caching, not Consul. Compare
dig @127.0.0.1 -p 8600withdig.
That is Consul, and the twenty-second guide on this site. The next one is MinIO — object storage, and where all the data these systems point at actually lives.