JusDB
Free DB Audit
NoSQL Databases

Valkey 9.1 for Operators: What Changed and How to Upgrade

Plan a Valkey 8.x to 9.1 upgrade: the 9.0 and 9.1 changes to test, database-level ACLs, the CVEs fixed since 9.1.1, a rolling cluster runbook, and rollback limits.

JusDB Team
Published November 1, 2026
17 min read

Valkey 9.1.0 went GA on 19 May 2026, and the current patch is 9.1.3 (26 October 2026). If you run Valkey 8.0 or 8.1, the upgrade is still a rolling binary swap: replicas first, then a manual failover per shard. The work is in what changes underneath. A 9.x node writes RDB files with a VALKEY080 header that Valkey 8.0 cannot read at all. Releases 9.1.0 to 9.1.2 can reject some data written by 8.x and 9.0, which shows up as failed RDB loads and failed full syncs, because of a listpack encoding bug fixed in 9.1.3. You also skip everything Valkey 9.0 introduced. This guide covers the 9.0 and 9.1 changes to plan for, the CVEs fixed since 9.1.1, a rolling upgrade runbook, and how far you can roll back.

TL;DR
  • Target 9.1.3 or later. It fixes a listpack incompatibility that broke RDB loads and replication between 9.0 and 9.1.0 to 9.1.2, plus a cluster Pub/Sub heap overflow (GHSA-f4q9-j22m-xqq9).
  • 9.1.1 fixed CVE-2026-56684 (TLS use-after-free via CLIENT KILL) and CVE-2026-63639 (stream deserialization use-after-free via RESTORE). 8.1.9 and 8.0.10 carry the same fixes if you are not upgrading yet.
  • 9.1 adds database-level ACLs (db=, alldbs, resetdbs), moves Lua into a module, and redesigns how the main thread hands work to I/O threads.
  • Upgrade replicas first, check master_link_status:up and offsets, then run CLUSTER FAILOVER on an upgraded replica. Do not reshard during the window.
  • Hold off on 9.x-only features (field TTLs, cluster-databases above 1, db= rules) until you no longer need a way back to 8.x.

Scope and review date: November 2026. This post covers self-managed Valkey 8.0 and 8.1 standalone, Sentinel, and cluster deployments moving to Valkey 9.1, with a note on Amazon ElastiCache. Versions, defaults, and commands were checked against the Valkey release notes, documentation, and source tags on 9 November 2026. They were not run in a lab, so rehearse them on staging.


Which Valkey 9.1 release should you run?

The Valkey 9.1 announcement is dated 19 May 2026. Since then the GitHub releases page lists three patch releases, all marked upgrade urgency SECURITY:

ReleaseDateWhy it matters
9.1.019 May 2026First stable 9.1; includes fixes for CVE-2026-23479, CVE-2026-25243, and CVE-2026-23631
9.1.121 July 2026CVE-2026-56684 and CVE-2026-63639; stops writing the implicit alldbs ACL rule so older versions can parse ACL output
9.1.231 August 2026Lua debugger use-after-free (GHSA-fq2f-crmw-q97r), RDMA fix, several ACL bypass fixes
9.1.326 October 2026Cluster Pub/Sub heap overflow (GHSA-f4q9-j22m-xqq9); listpack fix for 9.0 to 9.1 compatibility

The listpack fix decides the target. Pull request #4819 explains that for entries whose encoded length is exactly 16383, 2097151, or 268435455 bytes, Valkey wrote one backlen width up to 9.0 and a narrower one in 9.1.0 through 9.1.2. A 16378-byte string element is enough to hit it. Those 9.1 releases reject such data on load, so a 9.1.0 replica doing a full sync from an 8.x or 9.0 primary can fail. Use 9.1.3 or a later patch.

bash
# Record the version of every node before you start
for h in node1 node2 node3; do
  valkey-cli -h "$h" INFO server | grep -E '^(valkey_version|redis_version|server_mode)'
done

Interpretation: track upgrades by valkey_version. redis_version stays at 7.2.4 on every Valkey release; the source calls it a Redis OSS compatibility version that "should never exceed 7.2.x".

What changes between Valkey 8.1 and 9.1?

An 8.x deployment skips the whole 9.0 release (GA 21 October 2025) as well as 9.1. Valkey has no feature compatibility version: behaviour changes arrive with the binary, and new data formats arrive when you use a new feature. Sources for the table are the Valkey 9.0 announcement, the 9.0 and 9.1 release notes, and the shipped valkey.conf and config.c at tags 8.1.11 and 9.1.3.

Valkey 8.1 and 9.1 side by side

Switch versions to see what your cluster, ACLs, and scripts will meet after the upgrade

Valkey 8.1

AreaValkey 8.1
Numbered databases in cluster modeDatabase 0 only
Hash field TTLsNot available; a TTL applies to the whole key
Moving slots between shardsKey-by-key migration
ACL scopeCommands, keys, and channels, in any database
Lua engineBuilt into the server
io-threadsFixed at startup
RDB formatREDIS0011 (RDB version 11)
SIGTERM on a cluster primaryNo failover option
TLS certificate rotationNo automatic reload setting

Valkey 9.1

AreaValkey 9.1
Numbered databases in cluster modecluster-databases, default 1, set at startup (9.0)
Hash field TTLsHEXPIRE, HSETEX, HGETEX and related commands (9.0); HGETDEL (9.1)
Moving slots between shardsAtomic slot migration with CLUSTER MIGRATESLOTS (9.0); valkey-cli support (9.1)
ACL scopeAdds db=, alldbs, and resetdbs per database
Lua engineA module, statically linked by default; make BUILD_LUA=no builds without it
io-threadsChangeable with CONFIG SET (9.0); lock-free queues (9.1)
RDB formatVALKEY080 (RDB version 80)
SIGTERM on a cluster primaryshutdown-on-sigterm failover promotes a synced replica first (9.0)
TLS certificate rotationtls-auto-reload-interval, default 0 (off)

Binary versus data: most rows change when a node restarts on 9.1, but the first two only matter once someone uses them. Using them is what makes rolling back to 8.x hard.

Behaviour changes worth a test

  • Authentication first. Valkey 9.0 moved the auth check in front of the command-exists, arity, and protected-mode checks (#1475). An unauthenticated client sending an unknown command now gets an auth error instead of an unknown-command error.
  • Stricter protocol parsing. 9.1.0-rc1 (and 9.0.5) reject requests without strict CRLF terminators with a protocol error instead of misparsing them. Hand-written RESP clients and health-check scripts are the usual suspects.

How do database-level ACLs work in Valkey 9.1?

Before 9.1, an ACL user who could run a command on a key could do it in any numbered database. The Valkey ACL documentation now lists three rules. db=<id>[,<id>...] allows only the listed databases and replaces any earlier list. alldbs allows every database and is the default for new users and selectors. resetdbs removes all database access.

Scoping a user to databases 0 and 1

Output from the Valkey ACL documentation: the second db= rule replaces the first list

valkey-cli: admin session
> ACL SETUSER alice on +@all ~* db=0,1 nopassOK> ACL LIST1) "user alice on nopass ~* resetchannels db=0,1 +@all"2) "user default on nopass ~* &* +@all"> ACL SETUSER alice db=2,3OK> ACL LIST1) "user alice on nopass ~* resetchannels db=2,3 +@all"2) "user default on nopass ~* &* +@all"

Not additive: db=2,3 removed databases 0 and 1. To widen access, repeat the full list. Output quoted from the ACL documentation.

A denied database returns a NOPERM error and an ACL LOG entry with the reason database. The 9.1 announcement shows the client side:

bash
> SELECT 2
(error) NOPERM No permissions to access database

Interpretation: new connections still land in database 0, even for users who cannot use it, so set the database in the client's connection options.

The docs list which commands are checked against the database list. SELECT, SWAPDB, MOVE, and COPY ... DB need access to every database id they name. FLUSHALL, CLUSTER MIGRATESLOTS, and CLUSTER CANCELSLOTMIGRATIONS need alldbs, because they touch every database. Selectors can give different rights per database:

bash
# Writes in databases 0 and 1, reads only in 2 and 3 (from the ACL docs)
ACL SETUSER bob on nopass (db=0,1 +@write +select ~*) (db=2,3 +@read +select ~*)
ACL LOG 10

Interpretation: a command runs if the root permissions or any selector allow it. Watch ACL LOG after tightening a user; denials show up there first.

Fix the 9.1.x ACL bypasses first

9.1.1 enforced db= on every COPY DB clause and on CLUSTER FLUSHSLOT. 9.1.2 made MOVE and COPY check access to the current database and closed a GEORADIUS ... STORE key-pattern bypass. Do not rely on database ACLs as a tenant boundary before 9.1.2.

What does moving Lua into a module change?

From the application's side, nothing: pull request #2858 states that EVAL and FUNCTION/FCALL behave as before. The change is in packaging. 9.1.0-rc2 made static linking the default, so a standard build still has Lua with no loadmodule line. The 9.1.3 README documents make BUILD_LUA=no to build a server without Lua, which removes the scripting attack surface if you never use scripts.

bash
valkey-cli INFO "scripting engines"

Interpretation: the section starts with engines_count, engines_total_used_memory, and engines_total_memory_overhead, then a line per engine. Without Lua, engines_count is 0 and EVAL fails, so check that no client library or job queue uses scripts first.

The module move has a security history worth knowing. GHSA-fq2f-crmw-q97r, fixed in 9.1.2, was a use-after-free in the Lua debugger "introduced when the Lua scripting engine was moved into a Valkey module"; the 8.0 and 8.1 lines are not affected. If you cannot patch, the advisory's mitigation is to deny SCRIPT DEBUG with an ACL rule such as -script|debug, which leaves EVAL and EVALSHA working.

What changed in Valkey 9.1 I/O threading?

Valkey keeps command execution on the main thread. I/O threads read sockets, parse requests, and write replies. In the 8.x design the main thread assigned each job to a specific I/O thread. Pull request #3324, shipped in 9.1.0-rc2, replaces that with lock-free queues: one shared queue that any idle I/O thread pulls from, one response queue back to the main thread, and a private queue per thread for jobs that must run on that thread, such as freeing memory it allocated.

How the main thread and I/O threads exchange work in 9.1

Follow the numbers: I/O threads move bytes; commands still run on the main thread

Valkey 9.1 I/O threading model Step 1: the main thread puts read and write jobs on a shared job queue. Step 2: any idle I/O thread pulls a job and reads, parses, or writes. Step 3: it pushes the finished job onto a response queue. Step 4: the main thread takes finished jobs and executes the commands itself. Main thread runs every command Shared job queue idle threads pull I/O threads read, parse, write Response queue completed jobs 1 2 3 4 a private queue per I/O thread (not drawn) handles thread-specific jobs

Same limit, less overhead: the main thread no longer scans pending clients to find finished work, but a slow command still blocks every client. I/O threads do not help a hot KEYS or a long Lua script.

Valkey reports up to 17% higher throughput from the redesign in its own benchmarks; measure your workload. Two operational changes matter more. Since 9.0, io-threads can be changed at runtime (8.1 fixes it at startup). And 9.1 adds active-time counters, because a busy-polling thread shows near 100% CPU even when idle.

bash
# 9.x: change thread count without a restart, then read real thread load
valkey-cli CONFIG SET io-threads 6
valkey-cli INFO cpu | grep -E 'used_active_time_(main_thread|io_thread_[0-9]+)'

Interpretation: the counters are cumulative seconds of active work; sample twice and divide the difference by the interval. If used_active_time_main_thread grows almost as fast as wall-clock time, the main thread is the bottleneck and more I/O threads will not help.

Will Redis OSS clients and configs keep working on Valkey 9.1?

Clients, yes, for the commands they already use. The Valkey migration guide says Valkey is compatible with Redis OSS 7.2 and earlier, and the clients page says anything that speaks the Redis 7.2 wire protocol works. New commands (CLUSTERSCAN, MSETEX, HGETDEL, the field-TTL family) need client support before typed APIs expose them, though most clients can send raw commands. Using SELECT in cluster mode depends on your client allowing it; test that before raising cluster-databases.

Config files move forward without edits, but 9.x-only directives move one way. These exist in 9.1.3's valkey.conf and not in 8.1.11's:

ini
# 9.x-only directives or values: 8.x does not know them, so expect it to refuse to start
cluster-databases 1               # 9.0+, immutable, default 1
hash-seed example-seed-val        # 9.1, immutable; stable SCAN order across nodes
tls-auto-reload-interval 86400    # 9.1, default 0 (no automatic reload)
cluster-config-save-behavior sync # 9.1, or best-effort
log-format json                   # json value is new in 9.1; legacy and logfmt exist in 8.1
shutdown-on-sigterm failover      # failover flag is 9.0+

Interpretation: keep a copy of the 8.x config for rollback, and add these only after the burn-in. cluster-config-save-behavior best-effort keeps a node running when it cannot save nodes.conf; the config file warns that a restart may then load stale cluster state.

Which CVEs were fixed in Valkey 9.1.1 and later?

Every advisory below is published on the Valkey security advisories page. The fixes were also released for the 9.0, 8.1, and 8.0 lines, so patching 8.x is not a reason to rush the major upgrade.

AdvisoryIssueFixed inStopgap without patching
CVE-2026-56684TLS use-after-free when an authenticated client closes a connection, for example with CLIENT KILL; TLS deployments only9.1.1, 9.0.5, 8.1.9, 8.0.10, 7.2.14-client for users who do not need it; network isolation
CVE-2026-63639Crafted RESTORE payload with duplicate PEL entries causes a use-after-free in stream deserialization9.1.1, 9.0.5, 8.1.9, 8.0.10, 7.2.14Deny RESTORE with ACLs
GHSA-fq2f-crmw-q97rLua debugger use-after-free; 9.x only9.1.2Deny SCRIPT DEBUG
GHSA-jcj7-v34w-v9vvRDMA use-after-free via CLIENT KILL; RDMA builds with an RDMA listener only9.1.2, 9.0.6, 8.1.10, 8.0.11No RDMA listener
GHSA-f4q9-j22m-xqq9Heap overflow building cluster Pub/Sub messages over 4 GiB; cluster mode only9.1.3, 9.0.7, 8.1.11, 8.0.12, 7.2.15Deny scripting commands; keep proto-max-bulk-len at its default

How do you run a rolling Valkey cluster upgrade from 8.x to 9.1?

The Valkey cluster tutorial sets the order per shard: upgrade the replicas, wait for them to catch up, run CLUSTER FAILOVER on an upgraded replica, then upgrade the old primary.

Rolling upgrade: Valkey 8.x cluster to 9.1.3

Eight steps; steps 4 to 7 repeat per shard, and step 6 is a brief write pause

  1. Check cluster health and versions

    Every slot covered, no open slots, every node on the version you expect.

    valkey-cli --cluster check 10.0.0.11:6379
  2. Freeze topology changes

    No reshard, rebalance, or node additions until every node runs 9.1.3. Pause automation that does them.

  3. Back up data and config

    Copy the RDB, nodes.conf, the ACL file, and the 8.x valkey.conf from each primary.

    valkey-cli -h 10.0.0.11 BGSAVE
  4. Upgrade one replica of the shard

    Stop it, install 9.1.3, start it, and wait for the initial sync to finish.

    valkey-cli -h 10.0.0.21 INFO replication \ | grep -E 'master_link_status|offset'
  5. Compare replication offsets

    Run the same INFO replication on the primary. Under load the offsets may never match exactly; a small gap that closes in seconds is enough.

  6. Fail over to the upgraded replica

    Brief write pause

    Run it on the replica. The primary pauses clients until the replica catches up, then the replica takes over.

    valkey-cli -h 10.0.0.21 CLUSTER FAILOVER
  7. Upgrade the old primary

    Confirm role:master on the new primary first. The old one is now a replica; upgrade it like step 4.

    valkey-cli -h 10.0.0.21 ROLE
  8. Burn in before using 9.x features

    Keep field TTLs, cluster-databases above 1, db= rules, and atomic slot migration off until you no longer need 8.x.

Rollback point: until step 8 ends, a 9.1 primary can still feed an 8.x replica, so you can reverse the process shard by shard. Once 9.x-only data exists, rollback means restoring the step 3 backup.

The manual failover waits up to cluster-manual-failover-timeout (default 5000 ms) for the replica to catch up. If it times out, the primary resumes, so check offsets and retry.

bash
# After each shard
valkey-cli -h 10.0.0.21 CLUSTER NODES | grep master
valkey-cli --cluster check 10.0.0.21:6379

Interpretation: the check must report all 16384 slots covered before you start the next shard.

Sentinel and standalone deployments

The Valkey administration guide gives the same pattern for Sentinel: upgrade replicas one after the other, fail over to an upgraded replica, then upgrade the old primary. Upgrade the Sentinel processes one at a time too. For a standalone server, the guide starts the new version as a replica, waits for the sync, switches clients, and promotes it.

bash
valkey-cli -p 26379 SENTINEL FAILOVER mymaster
valkey-cli -p 26379 SENTINEL GET-MASTER-ADDR-BY-NAME mymaster

Interpretation: the second command must return an upgraded replica's address before you touch the old primary.

On Amazon ElastiCache, AWS announced Valkey 9.1 support for node-based clusters on 23 June 2026. The rollback advice below still applies.

How far can you roll back from Valkey 9.1 to 8.x?

There is no feature-compatibility switch, so the answer depends on the data. Three facts from the source and config files set the limits:

  • 8.0 cannot read a 9.x RDB file. The 8.0.12 loader accepts only the REDIS signature, and 9.x writes VALKEY080.
  • 8.1 can try, best effort. 8.1 has rdb-version-check, default strict. Set to relaxed, it attempts a newer RDB file and, as valkey.conf says, aborts if it meets unknown information.
  • A 9.x primary can feed an older replica. In rdb.h at 9.1.3, Valkey maps RDB 11 to 7.2 and RDB 80 to 9.0, and replication picks the format the replica supports. A hash with field TTLs cannot be written as RDB 11, so that sync fails.
bash
# Before any rollback: is there 9.x-only state?
valkey-cli CONFIG GET cluster-databases
valkey-cli ACL LIST | grep -E 'db=|resetdbs'
valkey-cli INFO commandstats | grep -E 'cmdstat_(hexpire|hpexpire|hsetex|hgetex)'

Interpretation: any cluster-databases value above 1 or any db= rule blocks a clean return to 8.x. Nonzero calls for the field-TTL commands mean some hashes may carry field TTLs. The counters reset on restart, so the reliable control is an application flag that keeps those commands unused during the burn-in. Explicit db= rules are 9.1 syntax; 9.1.1 only stopped writing the implicit alldbs.

Our reading, not a Valkey procedure

Valkey documents the upgrade order, not a downgrade. The rollback path above comes from the 9.1.3 source and config files. Rehearse it on staging, and keep the step 3 backup until the burn-in is over.

Frequently asked questions

Can I upgrade Valkey 8.1 directly to 9.1?

Yes. Valkey has no feature compatibility version, and the documented upgrade is a rolling one: upgrade replicas, fail over to an upgraded replica, then upgrade the old primary. Go straight to 9.1.3 or later, because 9.1.0 to 9.1.2 can reject some data written by 8.x and 9.0 during load or replication. Freeze resharding until every node is upgraded.

Which CVEs does Valkey 9.1.1 fix?

Valkey 9.1.1, released on 21 July 2026, fixes CVE-2026-56684, a TLS use-after-free reachable through CLIENT KILL, and CVE-2026-63639, a stream deserialization use-after-free reachable through a crafted RESTORE payload. Both are rated high. The same fixes shipped in 9.0.5, 8.1.9, 8.0.10, and 7.2.14.

How do I restrict a Valkey user to one database?

On Valkey 9.1 or later, add a db rule to the user, for example ACL SETUSER app on >secret +@all ~* db=0. The rule replaces any earlier list. Users without a db rule keep the default alldbs. Run 9.1.2 or later, because earlier 9.1 releases had COPY, MOVE, and FLUSHSLOT bypasses.

Do Redis clients work with Valkey 9.1?

Yes for existing commands. Valkey is compatible with Redis OSS 7.2 and earlier, and INFO still reports redis_version 7.2.4 for that reason. Clients need updates only to use new Valkey commands through typed APIs, such as CLUSTERSCAN, MSETEX, HGETDEL, or the hash field expiration commands. Check SELECT support before using numbered databases in cluster mode.

Can I roll back from Valkey 9.1 to 8.1?

Only with preparation. Valkey 9.x writes RDB version 80, which 8.0 cannot read and 8.1 loads only with rdb-version-check set to relaxed, on a best-effort basis. A 9.x primary can still feed an 8.x replica unless hashes carry field TTLs. Avoid 9.x-only features until the burn-in ends and keep a pre-upgrade backup.

Key takeaways

  • Upgrade to 9.1.3 or later; 9.1.0 to 9.1.2 can fail to load 8.x and 9.0 data, and every 9.1 patch so far is a security release.
  • Patch 8.x to 8.1.11 or 8.0.12 now if the major upgrade is months away; the CVE fixes were released for those lines too.
  • Use database-level ACLs only on 9.1.2 or later, and remember that a later db= rule replaces the earlier list.
  • Roll replicas first, fail over with CLUSTER FAILOVER, and keep resharding frozen until every node runs the same version.
  • Rollback stays cheap only while nobody uses field TTLs, extra cluster databases, or db= rules, so delay them until the burn-in ends.

Plan your Valkey 9.1 upgrade with JusDB

JusDB's Valkey support team can review your 8.x deployment against the 9.0 and 9.1 changes, check client libraries and ACL rules, and write a rolling upgrade runbook with a rehearsed rollback path for your Sentinel or cluster topology.

Bring your node versions, topology, ACL file, and maintenance windows. 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