Valkey 9.1.3 shipped on 26 October 2026, and parts of valkey.io have not caught up. The cluster specification still gives the Redis-era failover delay. The TLS page says an expired certificate fails to load, a check reverted before 9.1.0 shipped. The admin page says the server listens on every interface with no authentication, while the shipped valkey.conf binds to loopback. valkey.io inherited years of Redis text, and pages get fixed quickly once someone reports them. Until then, a runbook, an alert, or a security review built on the page inherits the error. This post lists 12 gaps re-checked on 10 November 2026 against the live pages and the 9.1.3 source, each with what the page says, what the server does, and what it means for operators.
- Failover: the cluster spec overstates the election delay and still describes a vote lock that 8.1.0 removed. It also calls
CLUSTER SLOTSdeprecated, which 8.0.0 reversed. - Security: 9.1.3 loads expired TLS certificates, the official Docker image runs with protected mode off, and a malformed
AUTHreachesMONITORwith the password in clear text. - Observability: the latency monitor page lists 16 events, but 9.1.3 emits 27 names.
CLIENT LISTdocuments a field name and a filter unit that the server does not use. - Memory and scripts: the 44-byte embstr limit is gone in 9.1.3, and EVAL scripts are evicted from a 500-entry cache without
SCRIPT FLUSH. - Before you rely on a page, check it against the release notes and source for your exact patch, then report the gap to
valkey-io/valkey-doc.
Scope and review date: November 2026. This post covers Valkey 9.1.3, with notes for 9.0.7 and the 9.2.0-rc2 release candidate, plus valkey-search 1.2.1 and valkey-bloom 1.0.1. Each doc claim was read on valkey.io on 10 November 2026 and compared with the 9.1.3 source tag, its valkey.conf, and the release notes. The commands were checked against the source and documentation, not run in a lab, so rehearse them on staging.
How do you check a Valkey doc claim against the server?
valkey.io has no version switcher. Topic pages come from Markdown in the valkey-io/valkey-doc repository. Command pages add syntax, "Since" and History from the server's src/commands/ JSON. A page may describe an older release, the newest GA release, or a 9.2 release candidate without saying which, so the source tag for your patch is the reference.
# Fetch the exact source for the version you run
git clone --depth 1 --branch 9.1.3 https://github.com/valkey-io/valkey.git valkey-9.1.3
cd valkey-9.1.3
grep -n 'createBoolConfig("protected-mode"' src/config.c
grep -nE '^(bind|protected-mode) ' valkey.confInterpretation: src/config.c holds the compiled default, the value used when the server starts with no config file. valkey.conf holds what packaged installs usually ship. When the two differ, a page that says "by default" can be right for one install and wrong for another.
Checking a valkey.io claim before it goes into a runbook
Follow the numbers; step 6 is optional but helps the next operator
Release notes first: most gaps in this post are visible in one line of 00-RELEASENOTES at the tag, which is faster to search than the source.
As of 10 November 2026 the newest tags are 9.2.0-rc2 (4 November), 9.1.3 (26 October), and 9.0.7 (27 October), per the GitHub releases page. Anything only in a 9.2 tag is release-candidate behaviour, even when a page already describes it.
What do the cluster and signals pages get wrong about failover?
Four gaps on the cluster specification and the signals page change how long a failover takes and how a planned shutdown behaves. Our failover timing and graceful shutdown post works through the timings in detail.
The replica election delay
The cluster spec gives DELAY = 500 milliseconds + random delay between 0 and 500 milliseconds + REPLICA_RANK * 1000 milliseconds. Two changes replaced that. PR #2449 (9.0.0-rc2) makes the base delay min(cluster-node-timeout / 30, 500), with the random part and the per-rank penalty scaled from it. PR #2227 (9.1.0-rc2) lets the best-ranked replica start its election immediately.
# In the 9.1.3 source tree
grep -n "min(server.cluster_node_timeout / 30, 500)" src/cluster_legacy.c
grep -n "best ranked replica and can initiate" src/cluster_legacy.cInterpretation: at the default cluster-node-timeout of 15000 ms the base is still 500 ms, so the old formula holds only at defaults and only for replicas that are not best ranked. At 3000 ms the base is 100 ms. Budgets built from the page overstate the election wait.
# What the replica actually waited, from its own log
grep -E "Start of election delayed|best ranked replica" /var/log/valkey/valkey.logInterpretation: each attempt logs the computed delay, rank, and offset. Calibrate client timeouts from a staging drill's log, not the formula.
The vote lock that no longer exists
The spec still says a primary that voted for one replica "can no longer vote for another replica of the same primary for a period of NODE_TIMEOUT * 2", and repeats it as a voting rule. PR #1356 removed that limit in 8.1.0-rc1, and the 8.0.10 release notes list it as backported. In 9.1.3 the voter refuses only if it already voted in the current epoch. A replica's own retry timer still applies, but a second election for the same shard is no longer blocked by the voters for twice the node timeout.
CLUSTER SLOTS is not deprecated
The spec mentions "the deprecated CLUSTER SLOTS". The 8.0.0-rc1 release notes say "Un-deprecate the CLUSTER SLOTS command. (#536)", and 9.1.0 added an availability-zone field to its reply.
grep -n "deprecated_since" src/commands/cluster-slots.json # no output on 9.1.3
grep -n -A1 '"9.1.0"' src/commands/cluster-slots.jsonInterpretation: no deprecated_since key means the server does not report the command as deprecated. There is no reason to rewrite a working client away from CLUSTER SLOTS. CLUSTER SHARDS changed shape in 9.1, as our client breaking changes post explains.
SIGTERM can fail over a cluster primary
The signals page describes SIGTERM as a fixed save-and-exit sequence and advises a manual FAILOVER or CLUSTER FAILOVER before shutting down a primary. It never mentions shutdown-on-sigterm or shutdown-on-sigint, which take the same flags as SHUTDOWN. One of those flags is failover, added with SHUTDOWN FAILOVER in 9.0.0. The 9.1.3 valkey.conf says it promotes a replica that is fully synchronized after waiting up to shutdown-timeout (default 10 seconds). If no replica qualifies, no failover happens.
valkey-cli CONFIG SET shutdown-on-sigterm failover
valkey-cli CONFIG GET shutdown-on-sigterm
valkey-cli CONFIG REWRITEInterpretation: systemd and Kubernetes stop processes with SIGTERM. At default, the cluster waits for automatic failover after the node timeout; with failover, the exiting primary hands over first. Rehearse it on staging before a rolling restart.
What valkey.io says and what Valkey 9.1.3 does
Switch tabs to compare the page with the server, row by row
valkey.io says
| Behaviour | What the page says |
|---|---|
| Replica election delay | 500 ms + random 0 to 500 ms + rank x 1000 ms |
| Vote for another replica of the same primary | Blocked for NODE_TIMEOUT x 2 |
CLUSTER SLOTS | Deprecated |
| SIGTERM on a cluster primary | Save and exit; fail over manually first |
| Expired TLS certificate at load | Load fails immediately |
| Default listening address | All interfaces, no authentication |
AUTH in MONITOR output | "Excluded" and "included" on the same page |
| Largest embedded string | 44 bytes |
Valkey 9.1.3 does
| Behaviour | What the server does |
|---|---|
| Replica election delay | Base of min(node timeout / 30, 500) ms; best-ranked replica starts at once |
| Vote for another replica of the same primary | One vote per epoch only; lock removed in 8.1.0 |
CLUSTER SLOTS | Supported; un-deprecated in 8.0.0 |
| SIGTERM on a cluster primary | shutdown-on-sigterm failover can promote a synced replica |
| Expired TLS certificate at load | Loads; expiry is only reported in INFO tls |
| Default listening address | Depends on the install: loopback, all interfaces, or Docker with protected mode off |
AUTH in MONITOR output | Included and redacted, except malformed forms |
| Largest embedded string | Header, key, expire, and value up to 128 bytes in total |
Every row changed: each one traces to a release-note line or a function in the 9.1.3 source tag, linked in the sections of this post.
Does Valkey 9.1 refuse to load an expired TLS certificate?
No. Under "TLS material validation", the TLS page says the server checks that "Certificates are within their valid time period". If a check fails, "the load operation will fail immediately". That check arrived in 9.1.0-rc1 (#2999). The 9.1 release notes then list, under 9.1.0-rc2 behaviour changes: "Revert strict TLS certificate validation at config load as it is a breaking change, deferred to next major version (#3572)". In 9.1.3, tls.c reads a certificate's end date only for the expiry telemetry added by #2913.
valkey-cli --tls --cacert /etc/valkey/tls/ca.crt INFO tlsInterpretation: look at tls_server_cert_expires_in_seconds, tls_client_cert_expires_in_seconds, and tls_ca_cert_expires_in_seconds. In 9.1.3 they clamp to 0 for an expired certificate and also read 0 when no certificate is loaded. Check the matching _serial field, which reads none when nothing is loaded, before you treat 0 as expired.
# Fails (non-zero exit) if the certificate expires within 14 days
openssl x509 -in /etc/valkey/tls/valkey.crt -noout -enddate -checkend 1209600Interpretation: on 9.1.3 nothing stops a reload from serving an expired certificate, so alert on the INFO field or this check. Our security hardening post covers TLS auto-reload and certificate-based ACL users.
Does Valkey listen on every interface by default?
It depends on how you start it. The administration page says "By default, Valkey does not require any authentication and listens to all the network interfaces". The installation page says the same, and the security page calls binding all interfaces "the default configuration". In 9.1.3 there are three defaults:
| How the server starts | bind | protected-mode |
|---|---|---|
No config file (compiled default, src/server.h) | * -::* | yes |
Shipped valkey.conf | 127.0.0.1 -::1 | yes |
Official valkey/valkey Docker image | * -::* (no config file) | no (patched in the image build) |
The 9.1 Dockerfile rewrites the protected-mode default in config.c to 0, because Docker ports are not exposed unless published. The security page also misdescribes the trigger: in 9.1.3, protected mode refuses non-loopback clients whenever the default user has no password, whatever bind says.
CONFIG GET on a package install and on the Docker image
Illustrative output, shaped like valkey-cli and based on the 9.1.3 valkey.conf and Dockerfile
Read your own values: a container with a published port, protected mode off, and a nopass default user accepts unauthenticated clients from any network that can reach it. Set bind, protected-mode, and ACL users explicitly.
# From a host that should not reach the server
valkey-cli -h 10.0.1.15 PINGInterpretation: a protected 9.1.3 server answers -DENIED Running in protected mode because protected mode is enabled and no password is set for the default user. A PONG here is the finding to act on.
Does MONITOR show AUTH passwords?
The MONITOR page says both "AUTH is excluded from the command's output" and "AUTH, HELLO, EVAL, EVAL_RO, EVALSHA and EVALSHA_RO are included in the command's output". The second is right: AUTH has no SKIP_MONITOR flag in 9.1.3, so it reaches MONITOR with its arguments redacted. Malformed commands are the exception.
/* src/acl.c at 9.1.3 */
void authCommand(client *c) {
/* Only two or three argument forms are allowed. */
if (c->argc > 3) {
addReplyErrorObject(c, shared.syntaxerr);
return;
}
/* Always redact the second argument */
redactClientCommandArgument(c, 1);Interpretation: AUTH user pass extra returns a syntax error before redaction runs, so the password reaches every MONITOR client and the commandlog in clear text. PR #5180 fixes this for AUTH, HELLO, and MIGRATE. It is in 9.2.0-rc2 only, not in 9.1.3, and the PR says every Valkey release is affected.
# Keep MONITOR away from application users
valkey-cli ACL SETUSER app -monitor
valkey-cli ACL DRYRUN app MONITORInterpretation: the dry run should report that the user has no permission to run monitor. Treat MONITOR captures and COMMANDLOG GET output as sensitive until you run a release with the fix. Our post on secrets in logs, MONITOR, and COMMANDLOG covers the rest of that surface.
Which latency events and advice do the latency docs miss?
The latency monitor page lists 16 events. The 9.1.3 source emits 27 event names. Twelve are missing: seven cluster-config-* events for nodes.conf writes (open, write, fsync, rename, unlink, close, dir-fsync), plus eviction-lazyfree, expire-del, command-unblocking, while-blocked-cron, and module-acquire-GIL. One listed event, aof-rewrite-diff-write, is never emitted in 9.1.3. Only the LATENCY DOCTOR analysis still refers to it.
# In the 9.1.3 source tree: every event name the server records
grep -ho 'latencyAddSampleIfNeeded("[^"]*"' src/*.c | sort -u
grep -n '"fast-command" : "command"' src/server.cInterpretation: the first command prints 25 names, and the second adds command and fast-command. A dashboard built from the page's list never shows a cluster-config-fsync stall from a slow disk.
valkey-cli CONFIG SET latency-monitor-threshold 100
valkey-cli LATENCY LATEST
valkey-cli COMMANDLOG GET 10 large-replyInterpretation: LATENCY LATEST shows every event over the threshold, including those missing from the page. The LATENCY DOCTOR page, and the advice text in 9.1.3's latency.c, still point to slowlog-log-slower-than. Since 8.1.0 (#1294) that is an alias for commandlog-execution-slower-than, one of three COMMANDLOG types, and the doctor never mentions the large-request and large-reply logs (1 MB defaults). Our latency spike runbook uses all three.
Which command pages give the wrong field name, unit, or limit?
The syntax block comes from the server's JSON; these errors are in the hand-written prose.
CLIENT LIST field name and MAXAGE unit
The CLIENT LIST page documents a lib-version field, but 9.1.3's networking.c emits lib-ver=, as the page's own example output shows. The page also says the MAXAGE filter (added in 8.1.0) takes milliseconds. The server compares it with commandTimeSnapshot() / 1000 - client->ctime, which is seconds, and the CLIENT KILL page says seconds.
# Clients connected for at least 5 minutes (300 seconds, not 300000)
valkey-cli CLIENT LIST MAXAGE 300 | grep -o 'lib-name=[^ ]* lib-ver=[^ ]*' | sort | uniq -cInterpretation: this counts long-lived connections per library and version. A millisecond value such as 300000 selects clients older than about 3.5 days, and with CLIENT KILL MAXAGE it decides which connections you drop. Our connection storm post uses these filters.
The embstr limit and the sorted set encoding
The OBJECT ENCODING page says embstr strings go "up to the hardcoded limit of OBJ_ENCODING_EMBSTR_SIZE_LIMIT or 44 bytes". That constant does not exist in 9.1.3. shouldEmbedStringObject() in object.c embeds when the object header, embedded key, optional expire, and value add up to 128 bytes or less. The page also lists btree as the "normal sorted set encoding". The B+ tree arrives in 9.2.0-rc1 (#4359), and 9.1.3 reports skiplist.
valkey-cli SET session:42 "$(head -c 60 /dev/zero | tr '\0' 'x')"
valkey-cli OBJECT ENCODING session:42
valkey-cli OBJECT ENCODING leaderboardInterpretation: on 9.1.3 a 60-byte value with a short key reports embstr, which the 44-byte rule rules out, and a large sorted set reports skiplist. Our per-key memory savings post traces how the limit moved across releases.
Can EVAL scripts leave the cache without SCRIPT FLUSH?
Yes. The EVAL introduction says "The only way to flush the script cache is by explicitly calling the SCRIPT FLUSH command". It also says scripts are meant to stay in the cache until a restart. Since 8.0.0, eval.c keeps scripts that arrive through EVAL in a 500-entry list and evicts the oldest when a new one arrives (LRU_LIST_LENGTH 500, a port of Redis #13108). Scripts loaded with SCRIPT LOAD are exempt (#637). Valkey 7.2.15 has no such limit.
valkey-cli INFO stats | grep evicted_scriptsInterpretation: a rising evicted_scripts counter means more than 500 distinct scripts arrive through EVAL, often because values are built into the script text. Those clients get NOSCRIPT on a later EVALSHA. Use SCRIPT LOAD or Functions instead. Our EVAL to Functions migration post covers the move.
Does valkey-search return partial results by default?
Yes in cluster mode, although one page says no. The search overview says that when a cross-shard query times out, "by default an error is returned", and that SOMESHARDS overrides this. The search configurables page lists search.enable-partial-results with a default of yes, meaning SOMESHARDS applies unless the query says otherwise. The FT.SEARCH page agrees with the configurables page. So does the valkey-search 1.2.1 source, where prefer_partial_results defaults to true.
valkey-cli CONFIG GET search.enable-partial-results
# Ask for complete results or an error on one query
valkey-cli FT.SEARCH idx:products "@category:{shoes}" ALLSHARDSInterpretation: at the default, a slow or unreachable shard can leave a result incomplete instead of failing it. Use ALLSHARDS where a missing row would be wrong, such as inventory checks. Our valkey-search coordinator post explains when the cross-shard path is active at all.
How do you verify a behaviour and report a doc gap upstream?
Treat a valkey.io page as a lead and confirm it for your patch before it goes into a runbook. This is the routine behind every item above.
Verifying a Valkey behaviour before you rely on it
Seven steps, none of which touch production data; step 5 runs on staging
-
Record the exact version
Read
valkey_version, notredis_version, which stays at 7.2.4 for compatibility.valkey-cli INFO server | grep -E \ '^(valkey_version|server_name):' -
Search the release notes at that tag
Look for the setting or command name from your version up to the newest patch.
curl -s https://raw.githubusercontent.com/\ valkey-io/valkey/9.1.3/00-RELEASENOTES \ | grep -n -i "tls" -
Read the source at the same tag
Defaults live in
src/config.c; command history lives insrc/commands/*.json.git clone --depth 1 --branch 9.1.3 \ https://github.com/valkey-io/valkey.git -
Read the effective config
The running value beats the page and the file.
valkey-cli CONFIG GET shutdown-on-sigterm -
Reproduce on staging
Use the same patch, config, and client library.
-
Write it into the runbook
Record the behaviour with the version checked, so the next upgrade triggers a recheck.
-
Report the gap upstream
Open an issue or a pull request with the page URL, the quoted sentence, and the source line.
gh issue create --repo valkey-io/valkey-doc \ --title "client-list: MAXAGE is seconds" \ --body-file maxage-gap.md
Main risk: a behaviour read from source but not reproduced is still an inference. Mark it that way in the runbook until step 5 has run.
Where to send the report depends on the page:
- Topic pages (
/topics/...) have an "Update this Page on GitHub!" link that openstopics/<name>.mdinvalkey-io/valkey-doc. - Command prose lives in
commands/<name>.mdin the same repository. - Command syntax, "Since", and History come from
src/commands/<name>.jsonin valkey-io/valkey, so a wrong History line is a server pull request.
Quote the sentence, name the version, and link the source line and release-note entry. These smaller errors make good first pull requests:
| Page | What it says | What 9.1.3 does |
|---|---|---|
| HELLO | Examples reply server: redis, version 7.2.4 or 255.255.255 | Replies valkey and the real version unless extended-redis-compatibility is yes (default no) |
| Memory optimization | Hash fields "can't have an associated time to live" | HEXPIRE and the other field TTL commands exist since 9.0.0 (see our hash field expiration post) |
| Distributed locks | Says to enable fsync=always, and that fsync every second is the default | The setting is appendfsync always, and appendonly defaults to no (see our distributed locks post) |
| BF.RESERVE | "use the options: NONSCALING or SCALING <expansion rate>" | valkey-bloom 1.0.1 accepts EXPANSION; SCALING returns an error (see our bloom filter sizing post) |
Cluster tutorial and valkey.conf | With tls-cluster yes and no cluster-announce-tls-port, cluster-announce-port is the TLS port | deriveAnnouncedPorts() announces the real tls-port, so set cluster-announce-tls-port behind NAT (see our topology discovery post) |
| CLUSTER SHARDS | A NULL endpoint means unknown-endpoint | Sends an empty string; CLUSTER SLOTS is the reply that sends NULL |
| Cluster tutorial | valkey-cli --cluster import "moves all the keys" of the source | Scans database 0 only and migrates to database 0; deletes source keys unless --cluster-copy is set (see our multi-database migration post) |
| valkey-cli | VALKEYCLI_HOST and VALKEYCLI_PORT with no version label | Only in 9.2 release candidates; 9.1.3 reads VALKEYCLI_AUTH, _HISTFILE, _RCFILE, and _CLUSTER_YES (see our valkey-cli post) |
Search valkey-io/valkey-doc pull requests for the page name first; a fix may be on main but not yet on the site. The 12 gaps above were still on both on 10 November 2026.
Frequently asked questions
Is the Valkey documentation accurate for Valkey 9.1?
Mostly, but not everywhere. On 10 November 2026 we found pages that describe Redis-era behaviour, behaviour reverted before 9.1.0, or 9.2 release-candidate features, with no version label. Examples are the cluster failover delay, TLS expiry checks, and the OBJECT ENCODING embstr limit. Check important claims against the release notes and source for your exact patch.
Does Valkey 9.1 reject expired TLS certificates?
No. Strict certificate validation at load was added in 9.1.0-rc1 and reverted in 9.1.0-rc2 as a breaking change, deferred to the next major version. Valkey 9.1.3 loads an expired certificate and reports its remaining lifetime in INFO tls, clamped to 0. Alert on that field or an openssl check instead.
Does Valkey bind to all interfaces by default?
Only without a config file. The compiled default binds all interfaces with protected mode on, and the shipped valkey.conf binds 127.0.0.1 and ::1. The official Docker image starts without a config file and patches protected mode off. Set bind, protected-mode, and ACL users explicitly on every install.
Is CLUSTER SLOTS deprecated in Valkey?
No. Valkey 8.0.0 un-deprecated CLUSTER SLOTS (PR #536), and Valkey 9.1.0 added an availability-zone field to its reply. The cluster specification page still calls it deprecated. Clients that route with CLUSTER SLOTS do not need to move to CLUSTER SHARDS, which changed shape in 9.1.
Where do I report an error in the Valkey docs?
Open an issue or a pull request in the valkey-io/valkey-doc repository on GitHub. Topic pages link to their Markdown file with "Update this Page on GitHub!". Command syntax and History come from src/commands JSON files in the valkey-io/valkey repository, so fix those there.
Key takeaways
- valkey.io has no version switcher, so check every claim you rely on against the release notes and source tag for your exact patch.
- Re-derive failover timing from the 9.0 and 9.1 election changes and your own logs, and consider
shutdown-on-sigterm failoverfor planned restarts. - On 9.1.3, monitor TLS expiry yourself, set
bindandprotected-modeexplicitly on every install including Docker, and keepMONITORaway from application users. - Read
lib-verand passMAXAGEin seconds, ignore the 44-byte embstr rule, and watchevicted_scripts. - Send each gap you confirm to
valkey-io/valkey-docwith the quote, the version, and the source line.
Check your Valkey runbooks with JusDB
JusDB's Valkey support team can review your failover, TLS, and access settings against the patch you run, check runbooks and alerts built from documentation pages, and plan the changes with you.
Bring your Valkey versions, topology, config files, and the runbooks you want checked. Your team approves every production change.