JusDB
Free DB Audit
NoSQL Databases

How Clients Discover a Valkey Cluster: CLUSTER SLOTS vs SHARDS vs NODES, NAT and TLS Ports

See how Valkey cluster clients build their slot map, what CLUSTER SLOTS, SHARDS and NODES return, and which announce settings fix NAT, Kubernetes and TLS port problems.

JusDB Team
Published November 3, 2026
Updated November 10, 2026
17 min read

A Valkey Cluster client does not ask a proxy where a key lives. It downloads the slot map from one node, caches it, and corrects it when a node answers -MOVED 1649 10.0.1.13:6379. Whatever address a node publishes in that map is the address your application dials. Behind NAT, in Kubernetes, or on a TLS-only cluster, that address is often wrong: a private pod IP, port 0, a ? endpoint, or a plaintext port handed to a TLS client. The errors show up in the client, as go-redis logging dial tcp <ip>:0: connection refused or GLIDE returning AllConnectionsUnavailable. This guide covers Valkey 8.0 to 9.1.3: what CLUSTER SLOTS, CLUSTER SHARDS and CLUSTER NODES return, which client reads which, and the cluster-announce-* settings that control the answer.

TL;DR
  • Clients load the slot map at startup and reload it after a MOVED redirect. Its addresses come from each node's announce settings and the answering node's cluster-preferred-endpoint-type.
  • CLUSTER SLOTS is not deprecated: Valkey 8.0 reversed that, although the cluster specification still says otherwise. valkey-go uses CLUSTER SHARDS on Valkey 8 and later; Lettuce uses CLUSTER NODES.
  • CLUSTER SLOTS, CLUSTER NODES and MOVED give a TLS port only to a client connected over TLS. CLUSTER SHARDS lists both ports.
  • Behind NAT, use cluster-announce-client-ipv4 (8.0 and later) and cluster-announce-client-port or -tls-port (9.0 and later) for what clients see. Set cluster-announce-tls-port explicitly on TLS clusters.

Scope and review date: November 2026. This post covers self-managed Valkey Cluster 8.0 to 9.1.3 (released 26 October 2026) and the client versions named in the text. Behaviour was checked on 10 November 2026 against the valkey.io command pages, the release notes, and the server and client source at the tags named. Commands were not run in a lab, so rehearse them on staging.


How does a Valkey cluster client build and refresh its slot map?

The Valkey Cluster specification says clients fetch the full slot map at startup and when they receive a MOVED redirection. It recommends reloading the whole map rather than one slot, because a failover remaps every slot the old primary served at once.

A MOVED reply carries the slot and the owner's endpoint and port. The endpoint can be an IP, a hostname, or empty (-MOVED 3999 :6380), meaning "same host, this port". An ASK reply is a one-off redirect during slot migration: the client sends ASKING and the command to the target and leaves its map alone.

bash
# Which slot does the key hash to, and who answers for it?
valkey-cli -h 10.0.1.11 -p 6379 CLUSTER KEYSLOT user:1000
# (integer) 1649

# Without -c, valkey-cli shows the redirect instead of following it
valkey-cli -h 10.0.1.11 -p 6379 GET user:1000
# (error) MOVED 1649 10.0.1.13:6379

Interpretation: the slot is fixed by the key (CRC16 of user:1000 modulo 16384 is 1649). The address after it is what the answering node announces for the owner. If the application cannot reach that address, the redirect leads nowhere, even though the seed address works.

Client bootstrap and MOVED refresh in Valkey Cluster

Every address the client dials after step 1 comes from the topology reply

Cluster client bootstrap and MOVED refresh Step 1: the application's cluster client sends CLUSTER SLOTS or CLUSTER SHARDS to a seed node and caches the reply as its slot map. Step 2: each command goes straight to the node that the cached map lists as primary for the key's slot. Step 3: if that node no longer owns the slot, it replies with MOVED, the slot and the new endpoint, and the client reloads the whole map before retrying. Application cluster client Seed node any cluster node Slot owner primary for the slot 1 CLUSTER SLOTS or SHARDS 2 the command goes to the node the cached map names 3 -MOVED: reload the map, then retry

Lead: the endpoints in step 1 and in the MOVED reply come from what each node announces. An unreachable announced address breaks step 2 even when step 1 worked.

What do CLUSTER SLOTS, CLUSTER SHARDS and CLUSTER NODES return?

All three describe the cluster from the answering node's view. CLUSTER SLOTS returns slot ranges, each with a primary and replicas as arrays of endpoint, port, node ID and a metadata map. CLUSTER SHARDS (since 7.0) returns one map per shard, with a map per node that includes health and replication-offset. CLUSTER NODES returns one text line per node, and its page says clients should normally use CLUSTER SLOTS instead. Valkey 9.1.0 added availability-zone to SLOTS and SHARDS and a shard id to SHARDS.

bash
# The same cluster, three views. --json uses RESP3, so SHARDS maps become objects.
valkey-cli -h 10.0.1.11 -p 6379 --json CLUSTER SLOTS \
  | jq -c '.[] | [.[0], .[1], .[2][0], .[2][1]]'
valkey-cli -h 10.0.1.11 -p 6379 --json CLUSTER SHARDS \
  | jq -c '.[].nodes[] | [.endpoint, .ip, .port, ."tls-port", .health]'
valkey-cli -h 10.0.1.11 -p 6379 CLUSTER NODES | awk '{print $2, $3}'

Interpretation: the first query prints each slot range with its primary's endpoint and port. The second prints what a SHARDS client will dial, including failed or loading nodes. The third prints the address and flags column that Lettuce parses. If the three disagree on a node's address, your client libraries will disagree too.

Is CLUSTER SLOTS deprecated?

No. The Valkey 8.0.0-rc1 release notes list "Un-deprecate the CLUSTER SLOTS command (#536)". The cluster specification still says "the deprecated CLUSTER SLOTS", but the command page has no deprecation notice, so the specification is the stale one.

CLUSTER SLOTS, SHARDS and NODES side by side

Switch commands to see which fields a client gets and which port it is handed (Valkey 9.1.3)

CLUSTER SLOTS

FieldCLUSTER SLOTS
ShapeNested arrays, one per slot range
Endpoint to dialPreferred endpoint: IP, hostname, or null
PortOne port, matching the caller's connection (TLS or plaintext)
HostnameIn metadata, when it is not the preferred endpoint
Failed or empty replicasLeft out
Availability zoneIn metadata from 9.1.0, when set
Shard IDNot shown
Follows cluster-preferred-endpoint-typeYes

CLUSTER SHARDS

FieldCLUSTER SHARDS
ShapeOne map per shard, with a map per node
Endpoint to dialendpoint, plus separate ip and hostname
Portport and tls-port, each only if non-zero; the client picks
Hostnamehostname field, when announced
Failed or empty replicasListed, with health set to fail or loading
Availability zoneavailability-zone from 9.1.0, when set
Shard IDid on every shard from 9.1.0
Follows cluster-preferred-endpoint-typeYes, in endpoint

CLUSTER NODES

FieldCLUSTER NODES
ShapeText, one space-separated line per node
Endpoint to dialAlways the IP, as ip:port@cport
PortOne port, matching the caller's connection (TLS or plaintext)
HostnameAfter a comma: ip:port@cport,hostname
Failed or empty replicasListed, with flags such as fail or fail?
Availability zoneNot shown
Shard IDNot shown
Follows cluster-preferred-endpoint-typeNo

Lead: only CLUSTER SHARDS shows both ports and node health. CLUSTER SLOTS is the reply the server caches and most clients read, so test it first.

Which topology command does each client library use?

The three replies treat hostnames, failed nodes and TLS ports differently, so one cluster can work for one service and fail for another. This table comes from each client's source at the tag shown.

ClientVersion readSlot map fromDetail that affects discovery
go-redisv9.23.0CLUSTER SLOTSReloads on MOVED or ASK
valkey-gov1.0.79CLUSTER SHARDS if the HELLO major version is 8 or moreUses tls-port on TLS; skips nodes not online
Lettuce7.9.0CLUSTER NODESUses the announced hostname whenever present
Jedisv8.0.2CLUSTER SLOTSNone noted
redis-py, valkey-pyv8.1.0, v6.2.0CLUSTER SLOTSaddress_remap rewrites addresses
ioredis, iovalkeyv6.0.0, v0.4.0CLUSTER SLOTSnatMap rewrites addresses
Valkey GLIDEv2.5.3CLUSTER SLOTSNames nodes by hostname metadata; skips ?
StackExchange.Redis3.4.31CLUSTER SLOTS since 3.2.0Earlier releases used CLUSTER NODES

The valkey-go rule is in cluster.go: useShards := v >= 8 || (preferShards && v >= 7). With extended-redis-compatibility yes the server reports 7.2.4, so valkey-go falls back to SLOTS. The StackExchange.Redis 3.2.0 notes record its move to SLOTS. Direct ClusterShards() calls in go-redis 9.20.0 and earlier break on the 9.1 shard id; see the changes that break Valkey client libraries.

How does cluster-preferred-endpoint-type change the address clients get?

cluster-preferred-endpoint-type takes ip (the default), hostname or unknown-endpoint. It sets the endpoint in CLUSTER SLOTS, CLUSTER SHARDS, MOVED and ASK, but not in CLUSTER NODES.

  • hostname: clients get cluster-announce-hostname. A node without one returns ?, which clients should treat as an unknown node. valkey-go and GLIDE drop such nodes.
  • unknown-endpoint: CLUSTER SLOTS returns null, telling the client to reuse the host it just used, with the returned port. It suits nodes behind a load balancer. In the 9.1.3 source, SHARDS and MOVED send an empty string here, though the SHARDS page says NULL.
bash
# The setting is per node, and each node formats its own replies.
for h in 10.0.1.11 10.0.1.12 10.0.1.13; do
  printf '%s ' "$h"
  valkey-cli -h "$h" -p 6379 CONFIG GET cluster-preferred-endpoint-type | tail -1
done

Interpretation: every node must print the same value, or a client refreshing from a different node gets IPs from one and hostnames from another. The setting is modifiable at runtime and clears the cached SLOTS reply. Set a hostname on every node before switching to hostname.

unknown-endpoint and go-redis

go-redis reads the null host as an error, so a ClusterClient fails with redis: nil. The fix, PR #4131, was merged on 26 October 2026, after v9.23.0, and is in no tagged release as of 10 November 2026.

Which cluster-announce settings control what clients see?

A node publishes its addresses over the cluster bus, and the other nodes repeat them to clients. The 9.1.3 valkey.conf lists the overrides under "CLUSTER DOCKER/NAT support". All are modifiable at runtime.

SettingSinceUsed byWhat it sets
cluster-announce-ip7.2Bus and clientsNode IP; clients see it unless a client IP is set
cluster-announce-port, -tls-port7.2Bus and clientsPlaintext and TLS client ports
cluster-announce-bus-port7.2BusBus port; default cluster-port or client port plus 10000
cluster-announce-hostname7.2ClientsHostname: letters, digits, - and ., up to 256 characters
cluster-announce-client-ipv4, -ipv68.0Clients onlyIP shown to IPv4 or IPv6 clients
cluster-announce-client-port, -tls-port9.0Clients onlyPort shown to plaintext or TLS clients

PR #2429 added the client ports so that clients "always get to see the configured port", while bus traffic keeps the non-client values. They travel in new ping extensions, so set them only once every node runs 9.0 or later.

bash
# What does this node announce? CONFIG GET accepts glob patterns.
valkey-cli -h 10.0.1.11 -p 6379 CONFIG GET 'cluster-announce-*'

Interpretation: empty strings and zero ports mean "use what the node detects". In the 9.1.3 source the client IP depends only on the connection's address family, so a node shows one IPv4 and one IPv6 address to all clients, not one per network.

CLUSTER SHARDS on a TLS-only cluster with hostname endpoints

Illustrative output: Valkey 9.1, port 0, tls-port 6380, cluster-preferred-endpoint-type hostname

sre@bastion:~
$ valkey-cli --tls --cacert ca.crt -h valkey-0.valkey-hl.prod.svc \ -p 6380 --json CLUSTER SHARDS \ | jq -c '.[].nodes[] | [.endpoint, .ip, .port, ."tls-port", .role]'["valkey-0.valkey-hl.prod.svc","10.244.1.17",null,6380,"master"]["valkey-3.valkey-hl.prod.svc","10.244.2.21",null,6380,"replica"]["valkey-1.valkey-hl.prod.svc","10.244.2.18",null,6380,"master"]["valkey-4.valkey-hl.prod.svc","10.244.3.30",null,6380,"replica"]["valkey-2.valkey-hl.prod.svc","10.244.3.12",null,6380,"master"]["valkey-5.valkey-hl.prod.svc","10.244.1.25",null,6380,"replica"]$ valkey-cli --tls --cacert ca.crt -h valkey-0.valkey-hl.prod.svc \ -p 6380 --json CLUSTER SHARDS | jq -c '.[0] | keys'["id","nodes","slots"]

Read the nulls: with port 0 the server omits port, so a parser that reads only port gets nothing. The shard id appears from 9.1.0, as the CLUSTER SHARDS page documents.

How do you set announce addresses behind NAT or in Kubernetes?

Nodes need addresses for each other on the bus, and clients need addresses they can reach. Inside one Kubernetes cluster both can be the pod IP. Outside clients, or per-node load balancers, need the client-only settings. The bus connects by IP, so a hostname helps clients (for example with certificate checks) but not nodes.

yaml
# StatefulSet container excerpt: announce the pod IP and a stable DNS name
env:
  - name: POD_IP
    valueFrom:
      fieldRef:
        fieldPath: status.podIP
  - name: POD_NAME
    valueFrom:
      fieldRef:
        fieldPath: metadata.name
command: ["valkey-server"]
args:
  - /etc/valkey/valkey.conf
  - --cluster-announce-ip
  - $(POD_IP)
  - --cluster-announce-hostname
  - $(POD_NAME).valkey-hl.prod.svc.cluster.local

Interpretation: Kubernetes expands $(POD_IP) and $(POD_NAME) in args, and valkey-server accepts config directives as --name value after the config file. A headless Service (here valkey-hl) makes each name resolve to its pod. An operator may already set these; see Valkey and Redis operators for Kubernetes compared.

Configuring announce settings behind NAT or a Kubernetes load balancer

Six steps on a running 9.x cluster, one node at a time, no restart needed

  1. Record both address sets per node

    Note the IP and bus port other nodes reach, and the IP and ports applications reach.

    valkey-cli -h 10.244.1.17 -p 6379 CLUSTER NODES \ | awk '{print $1, $2, $3}'
  2. Set the bus address, then check the cluster

    Only for node-to-node NAT. A wrong value cuts the node off and can start a failover, so wait for cluster_state:ok.

    valkey-cli -h 10.244.1.17 -p 6379 CONFIG SET \ cluster-announce-ip 192.0.2.17 \ cluster-announce-bus-port 16379 valkey-cli -h 10.244.2.18 CLUSTER INFO | grep state
  3. Set the client IP (8.0 and later)

    Use the address clients reach, such as a per-pod load balancer. Every IPv4 client will see it.

    valkey-cli -h 10.244.1.17 -p 6379 CONFIG SET \ cluster-announce-client-ipv4 203.0.113.10
  4. Set the client ports (9.0 and later)

    Set the TLS port explicitly on TLS clusters.

    valkey-cli -h 10.244.1.17 -p 6379 CONFIG SET \ cluster-announce-client-port 30079 \ cluster-announce-client-tls-port 30080
  5. Persist the values

    CONFIG SET is lost on restart. Update the config file or startup arguments, or run CONFIG REWRITE if the file is writable.

    valkey-cli -h 10.244.1.17 -p 6379 CONFIG REWRITE
  6. Verify from the application network

    Use the application's protocol and connect to every address returned.

    valkey-cli --tls --cacert ca.crt -h 203.0.113.10 \ -p 30080 --json CLUSTER SLOTS \ | jq -c '.[] | .[2][0:2]'

Main risk: step 2. The client-only settings in steps 3 and 4 never touch the bus, so a mistake there breaks clients but not the cluster. Undo with CONFIG SET to an empty string or 0.

Why do clients get the wrong port on a TLS cluster?

The server picks the port by the protocol of the connection it sees. In the 9.1.3 cluster.c, shouldReturnTlsInfo() returns connIsTLS() for the caller, and SLOTS, NODES, MOVED and ASK report TLS ports only to TLS clients. That causes three common faults:

  • TLS terminated in front of Valkey. The node sees plaintext and hands out plaintext ports, which the application then dials with TLS. Announce the proxy's port with cluster-announce-client-port.
  • A SHARDS parser that reads only port. TLS-only nodes have no port, and with both open a TLS client dials the plaintext listener. valkey-go picks tls-port; Lettuce 7.9.0's SHARDS parser reads only port, though its default refresh uses NODES.
  • Only cluster-announce-port set on a TLS cluster. See below.
The documentation and the source disagree

valkey.conf and the cluster tutorial say that with tls-cluster yes and no cluster-announce-tls-port, cluster-announce-port is the TLS port. deriveAnnouncedPorts() in the 9.1.3 cluster_legacy.c uses cluster-announce-tls-port or else the real tls-port, and cluster-announce-port only for plaintext. 7.2.15, 8.0.12 and 8.1.11 do the same. Set cluster-announce-tls-port explicitly.

bash
# Same node, two protocols: the ports in the replies should differ
valkey-cli -h 10.0.1.11 -p 6379 --json CLUSTER SLOTS | jq -c '.[0][2][0:2]'
valkey-cli --tls --cacert ca.crt -h 10.0.1.11 -p 6380 --json CLUSTER SLOTS \
  | jq -c '.[0][2][0:2]'

Interpretation: plaintext should return plaintext ports and TLS should return TLS ports. An internal TLS port where you expected the NAT-mapped one means cluster-announce-tls-port is missing. 9.0.0-rc1 also fixed "CLUSTER SLOTS/NODES showing wrong port after updating port/tls-port" (#2186), so on 8.x restart a node after changing those at runtime.

What do topology problems look like in the client?

Most of these appear after the first connection succeeds, because the seed address is reachable and the announced ones are not.

SymptomLikely causeFix
Connects, then times out to pod or 10.x IPsNodes announce internal addressescluster-announce-client-ipv4, or address_remap or natMap
dial tcp <ip>:0: connection refusedTLS-only node, plaintext port readgo-redis v9.20.0 or later for PubSub; read tls-port
Nodes missing from the map? endpoints: no hostname setSet a hostname on every node
AllConnectionsUnavailable in GLIDEEmpty hostname metadataGLIDE with PR #5373 (2.2.9 or later)
TLS handshake or certificate name errorsPort protocol mismatch, or IP endpointsFix ports; use hostname endpoints

The go-redis port 0 bug affected PubSub on TLS-only clusters from v9.18.0, per the v9.20.0 notes. Announcing a hostname also changes Lettuce and GLIDE even with the endpoint type left at ip, so make sure it resolves everywhere first. For zone labels, see multi-AZ Valkey placement and zone-aware clients.

How do you test topology discovery for each client?

Test from the application network, over its protocol, against every node. The SLOTS page says the reply is deterministic across nodes, so identical replies mean one shared view.

bash
# 1. Every node should give the same CLUSTER SLOTS reply
for h in 10.0.1.11 10.0.1.12 10.0.1.13 10.0.1.14 10.0.1.15 10.0.1.16; do
  printf '%s ' "$h"
  valkey-cli --tls --cacert ca.crt -h "$h" -p 6380 CLUSTER SLOTS | md5sum
done

# 2. Every advertised endpoint must answer PING over the same protocol
valkey-cli --tls --cacert ca.crt -h 10.0.1.11 -p 6380 --json CLUSTER SLOTS \
  | jq -r '.[] | .[2:][] | "\(.[0]) \(.[1])"' | sort -u \
  | while read -r host port; do
      printf '%s:%s ' "$host" "$port"
      valkey-cli --tls --cacert ca.crt -h "$host" -p "$port" PING
    done

Interpretation: step 1 passes with one checksum for all nodes; a different one points to a node with another endpoint type or announce setting. In step 2 every line must end in PONG. Then, on staging, run CLUSTER FAILOVER on a replica and check each client:

  • go-redis: the ClusterClient reloads after the failover; PubSub works on TLS-only clusters (v9.20.0 or later).
  • valkey-go: it dials tls-port on TLS, and still uses SHARDS unless compatibility mode is on.
  • Lettuce: announced hostnames resolve; on 6.x, enable adaptive refresh triggers (7.0 enables them by default).
  • Remap users (redis-py, valkey-py, ioredis, iovalkey): the new primary's address is in the remap.
  • GLIDE and StackExchange.Redis: GLIDE is past the empty-hostname fix; StackExchange.Redis is retested after moving to 3.2.0 or later.

Frequently asked questions

Is CLUSTER SLOTS deprecated in Valkey?

No. Valkey 8.0 reversed the earlier deprecation of CLUSTER SLOTS, and the Valkey 8.0.0-rc1 release notes list it under un-deprecated commands. The command page has no deprecation notice. The cluster specification page still calls it deprecated, which is out of date. Most cluster clients, including go-redis, Jedis, redis-py and GLIDE, build their slot maps from it.

Should a Valkey client use CLUSTER SHARDS or CLUSTER SLOTS?

Either works if the client parses it correctly. CLUSTER SHARDS returns both plaintext and TLS ports, node health and, from Valkey 9.1, a shard ID. CLUSTER SLOTS returns one port matching the caller's connection, hides failed and empty replicas, and is cached by the server. Parsers for both must ignore unknown fields.

What does cluster-announce-client-ipv4 do in Valkey?

It sets the IPv4 address a Valkey 8.0 or later node shows to clients in CLUSTER SLOTS, CLUSTER SHARDS, CLUSTER NODES and MOVED replies. The cluster bus keeps using cluster-announce-ip or the detected address. Every IPv4 client sees the same value, so it suits setups where all clients reach the node the same way.

What does a question mark endpoint mean in CLUSTER SLOTS?

It means the node uses cluster-preferred-endpoint-type hostname but has no cluster-announce-hostname. The Valkey documentation says clients should treat it as an unknown node. valkey-go and GLIDE drop such nodes from their slot maps, so their slots look unserved. Set a hostname on every node before switching the endpoint type.

Key takeaways

  • Clients dial whatever the nodes announce, so test discovery from the application network over its protocol.
  • CLUSTER SLOTS is supported in Valkey 8.0 and later. Know which of SLOTS, SHARDS and NODES each client reads.
  • Keep cluster-preferred-endpoint-type identical on all nodes, and keep go-redis off unknown-endpoint until a release includes PR #4131.
  • Use bus settings for node-to-node NAT and the client-only settings for what applications see.
  • On TLS clusters, set cluster-announce-tls-port explicitly and compare plaintext and TLS replies.

Review Valkey cluster connectivity with JusDB

JusDB's Valkey Cluster team can review your announce settings, endpoint types and TLS ports against the client libraries your services use, plan the changes node by node, and run the discovery and failover tests above on staging.

Bring your Valkey versions, network layout, TLS setup and client library inventory. Your team approves every production change.

Share this article

Database engineering notes

Articles like this one, in your inbox. No spam, unsubscribe anytime.

JusDB Team

Official JusDB content team

Keep reading

Moving a Multi-Database Valkey Instance into a 9.x Cluster

Move a Valkey instance that uses SELECT 1 to 15 into a 9.x cluster: keep or remap databases, copy each one, verify counts per database, and keep a way back.

Redis18 minNov 10, 2026
Read

Valkey Keyspace Notifications in Production: Expired Events, hexpired and Cluster Mode

Run Valkey keyspace notifications in production: why expired and hexpired events lag the TTL, how to subscribe on every cluster node, and when a stream is safer.

Redis20 minNov 10, 2026
Read

Valkey GLIDE in Production: AZ-Affinity Reads and Migrating from Legacy Clients

Configure Valkey GLIDE AZ-affinity reads on servers and clients, learn what GLIDE 2.5.3 does not support, and migrate from redis-py, Jedis, ioredis, or go-redis one service at a time.

Redis20 minNov 10, 2026
Read