March 17, 2026·6 min read·engineeringvalkeyacltlsscripting

Valkey 9.1 RC1 gives numbered databases an access policy

Valkey's first 9.1 candidate adds database-level ACLs and background TLS reload. It also separates the Lua engine into a module while preserving the public scripting API.

Permission to run GET does not answer which numbered database a user may read. Valkey 9.1's first release candidate adds that missing dimension to its access policy. Database-level ACLs let a user retain command permissions while restricting the databases in which those permissions apply.

The 9.1.0-rc1 release includes the change alongside automatic TLS reload and a modular Lua engine. Its tagged release notes date the candidate March 17. GitHub published the release March 16 at 23:42 UTC, shortly before the date recorded in those notes. This post follows the tagged date.

The release is a candidate, with low upgrade urgency in the project's notes. That status matters when interpreting the new capabilities. They are available for evaluation in this build, without a claim here that 9.1 has reached a final release.

I default to Valkey when I want Redis semantics and value its permissive licensing. Compatibility is part of that choice. Database ACLs are interesting because they add a restriction within the existing model, allowing more precise authority without requiring a different way to identify keys or issue ordinary commands.

Database access joins command and key permissions

Pull request #2309, by Daniil Kashapov, describes the database rules. The default is alldbs. A db= rule names permitted database numbers, and resetdbs clears database permissions. The default preserves existing access until an administrator declares a narrower policy.

The proposal gives an example granting access to databases 0 and 1 while allowing all commands and keys. It also supports selectors that associate different command sets with different database sets. That second capability matters when one identity needs write access in one database and read access in another.

This expands the set of conditions checked for an operation. Permission to issue the command remains necessary. Access to the database the command uses becomes necessary too. For commands that involve more than one database, checking only the connection's current database would leave the rule incomplete.

The implementation description names SELECT, SWAPDB, MOVE and COPY as operations requiring the relevant database permissions. MOVE needs access to its target as well as the source context. FLUSHALL requires all-database access, and the proposal applies the same requirement to named cluster operations that access all databases.

The distinction is useful for an administrator who wants to permit ordinary key access without granting authority over every database. Allowing a command cannot silently grant access to all the objects that command might name. The database rule constrains its actual scope.

Authentication does not select an authorized database

New connections still start on database 0. The ACL proposal explicitly separates authentication from database authorization. A user can connect and authenticate, then issue SELECT or perform operations that do not access the keyspace. Authentication does not imply permission to use database 0 for data access.

That behavior supports users whose allowed set excludes the initial database. Rejecting authentication merely because the connection starts on database 0 would make such a policy awkward to use. Instead, the application can authenticate and select a permitted database before issuing key operations.

The candidate's ACL tests exercise permissions beyond direct SELECT calls. They cover database switches inside transactions and changes to permissions between transaction queueing and execution. A queued operation cannot assume the policy observed when it entered the transaction remains sufficient at execution.

The same tests exercise EVAL, EVALSHA and function calls that attempt to select a restricted database. The expected result is a permission failure inside the script. That coverage matters because scripting is another route to the same keyspace, and database restrictions need to apply there as well as at the direct command interface.

Denied database access also appears in observability. The proposal extends ACL LOG, and the tagged tests verify an acl_access_denied_db counter. This distinguishes database-scope denial from an authentication failure or an ordinary command-permission error.

My preference is for an authorization system to express the object an operation can affect, alongside the operation itself. Numbered databases already distinguish keyspaces. Giving them explicit ACL rules makes that existing distinction useful in access policy, while retaining command and key restrictions as separate conditions.

TLS rotation can happen through a background reload

Pull request #3020, by Yang Zhao, introduces tls-auto-reload-interval. A value of zero disables automatic reload and remains the default. A positive value configures the checking interval in seconds. The tagged configuration file gives a daily interval as an example.

tls-auto-reload-interval 86400

This is an additional setting for a TLS-configured server, not a complete TLS configuration. The candidate configuration also requires the relevant certificate and key settings. Enabling a checking interval does not supply certificates or turn an otherwise unconfigured server into a TLS endpoint.

The reload implementation detects certificate changes through SHA-256 fingerprints. It uses inode and modification-time checks for CA directories and key files. When material has changed, a dedicated background worker parses the certificates and prepares TLS contexts. The main thread applies the prepared contexts in a separate phase.

That separation is the practical mechanism behind the background-reload claim. CPU-intensive certificate parsing moves to the worker, while applying the prepared state remains a main-thread operation. The proposal distinguishes this automatic path from startup TLS loading and runtime reload through CONFIG SET, which continue to use the synchronous configuration path.

The source describes change detection and context replacement, not automatic certificate issuance. An external process still needs to place valid replacement material where the configured paths point. The new behavior reduces the intervention required for Valkey to notice and use changed files.

Reload also needs to reject invalid material

The candidate separately includes pull request #2999, which validates certificate time bounds during TLS configuration. It rejects expired and not-yet-valid certificates. The proposal covers server certificates, server-side client certificates and configured CA material, including CA directories.

Keeping validation separate from change detection clarifies the behavior. A changed file is a reason to consider new material, but it is not proof that the material is usable. The release includes both the reload capability and checks designed to prevent invalid certificates from becoming an accepted configuration.

The release also adds authentication from certificate SAN URIs in pull request #3078. With tls-auth-clients-user URI, the server considers certificate URI entries and selects the first matching enabled Valkey user. The tagged configuration warns through its documented behavior that no match leaves the client unauthenticated as the default user.

This is another reason to distinguish identity mapping from authorization. Matching a certificate field selects a user. The user's command, key and database rules determine the subsequent permitted operations. A certificate identity is not itself an all-database grant.

Lua becomes a module without a new scripting API

Pull request #2858, by Ricardo Dias, extracts the Lua engine into a Valkey module. Its stated goals include the possibility of compatible engine upgrades and building a server without Lua. The public use of EVAL and FUNCTION or FCALL remains compatible according to the proposal.

The change moves the engine into src/modules/lua and adapts it to the module API. One specific implementation difference concerns serialization of replies. The previous implementation parsed RESP values directly from the client buffer, while the module implementation uses ValkeyModuleCallReply and its API.

The module is built with the server and loaded automatically at startup in the default build. A BUILD_LUA=no build option permits omitting the engine. This is a packaging and implementation boundary, with the proposal explicitly preserving the normal scripting interface for users of the default build.

The possibility of additional Lua engines is a stated future direction in that proposal. It should not be mistaken for evidence that this candidate already provides a choice of newer engine versions. What ships here is the modular structure that can support that work and the ability to build without the engine.

The part of this candidate that most changes application policy is database authorization. TLS reload addresses certificate operations, and Lua modularization changes how the server packages scripting. Database ACLs directly determine which keyspaces an authenticated client can use, including through transactions and scripts, while preserving unrestricted database access as the compatibility default.