{"id":168374,"date":"2026-06-04T00:09:06","date_gmt":"2026-06-03T21:09:06","guid":{"rendered":"https:\/\/computingforgeeks.com\/?p=168374"},"modified":"2026-06-04T10:43:10","modified_gmt":"2026-06-04T07:43:10","slug":"install-valkey-ubuntu","status":"publish","type":"post","link":"https:\/\/computingforgeeks.com\/install-valkey-ubuntu\/","title":{"rendered":"Install Valkey on Ubuntu 26.04 \/ 24.04 (Redis Replacement)"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Valkey is a drop-in replacement for Redis, and on Ubuntu the install is a three-way choice with very different version outcomes. The Ubuntu archive gives you a Canonical-supported package that lags upstream, the official Docker image gives you the latest release in seconds, and a source build gives you that same latest release as a native service. This guide shows how to install Valkey on Ubuntu all three ways, then proves it works by wiring it into a real application: a Python API that caches a slow database query, rate-limits requests, and stores sessions, with the live command traffic captured straight off the server.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><em>The latency and throughput numbers here were measured on a 2-vCPU Ubuntu 26.04 test box in June 2026, on Valkey 9.1.0 built from source.<\/em><\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Beyond the install, the guide covers a tested cache-aside integration with real latency numbers, the new database-level ACLs in the 9.1 line, a live Redis to Valkey migration with no downtime, and a throughput benchmark measured on the test box. If you are coming from Redis, you already know most of the commands. Every standard Redis command, the RESP wire protocol, and the on-disk formats are identical, so existing clients connect without code changes.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Prerequisites<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>An Ubuntu 26.04 or 24.04 LTS server with sudo access (the commands are identical on both; version differences are noted where they matter)<\/li>\n<li>Outbound internet access for package downloads<\/li>\n<li>Around 512 MB of free RAM for a cache instance; more if you plan to hold a large dataset<\/li>\n<li>Familiarity with Redis concepts helps but is not required. If you have run <a href=\"https:\/\/computingforgeeks.com\/install-redis-ubuntu-2604\/\" target=\"_blank\" rel=\"noreferrer noopener\">Redis on Ubuntu<\/a> before, this will feel familiar<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">Pick an install method<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">There is no first-party Valkey apt or dnf repository. The project ships through distribution packages, an official Docker image, and source tarballs. That matters because the version you get depends entirely on which path you take, and most guides on the web pick one path and never mention the trade-off. Here is what each method actually gave on the test boxes:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Method<\/th><th>Version installed<\/th><th>Native service<\/th><th>Best for<\/th><\/tr><\/thead><tbody>\n<tr><td>apt on Ubuntu 24.04 (universe)<\/td><td>7.2.12<\/td><td>Yes (systemd)<\/td><td>Distro-supported, older line<\/td><\/tr>\n<tr><td>apt on Ubuntu 26.04 (main)<\/td><td>9.0.3<\/td><td>Yes (systemd)<\/td><td>Canonical-supported, current-ish<\/td><\/tr>\n<tr><td>Docker <code>valkey\/valkey<\/code><\/td><td>Latest (9.1 line)<\/td><td>Container<\/td><td>Fastest to the newest release<\/td><\/tr>\n<tr><td>Build from source<\/td><td>Latest (9.1 line)<\/td><td>Yes (systemd)<\/td><td>Newest release, native, with TLS<\/td><\/tr>\n<\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">The short version: on 24.04 the archive package is from the Redis 7.2 era and predates the 9.x feature set. On 26.04 it jumps to the 9.0 line and moves into <code>main<\/code>, so Canonical security support applies. For the absolute latest, Docker and a source build are the only first-party options. The screenshot below shows the same box reporting three different versions depending on how Valkey was installed:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"920\" height=\"380\" src=\"https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/06\/wm-valkey-install-methods-versions-ubuntu-2604.png\" alt=\"apt, Docker and source-built Valkey version comparison on Ubuntu 26.04\" class=\"wp-image-168368\" title=\"\" srcset=\"https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/06\/wm-valkey-install-methods-versions-ubuntu-2604.png 920w, https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/06\/wm-valkey-install-methods-versions-ubuntu-2604-300x124.png 300w, https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/06\/wm-valkey-install-methods-versions-ubuntu-2604-768x317.png 768w\" sizes=\"auto, (max-width: 920px) 100vw, 920px\" \/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Install from the Ubuntu repositories<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">This is the simplest path and the right one if you want a systemd-managed service with security updates from Ubuntu. The package set split into a daemon and a tools package. Install both:<\/p>\n\n\n\n<pre class=\"wp-block-code code\"><code>sudo apt update\nsudo apt install -y valkey-server valkey-tools<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><code>valkey-server<\/code> is the daemon, <code>valkey-tools<\/code> ships <code>valkey-cli<\/code>. The old <code>valkey<\/code> metapackage no longer exists on 26.04, so install the two explicitly. The service starts and enables itself. Confirm the version and that it answers:<\/p>\n\n\n\n<pre class=\"wp-block-code code\"><code>valkey-server --version\nsystemctl is-active valkey-server\nvalkey-cli ping<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">On Ubuntu 26.04 this reports version 9.0.3 and a <code>PONG<\/code>, with the package coming from the <code>resolute\/main<\/code> component. On Ubuntu 24.04 the same command installs 7.2.12 from <code>universe<\/code>, which is wire-compatible with Redis 7.2 but predates the 9.x performance and ACL work. If you only need a cache or session store and want zero maintenance, the archive package is enough. If you need the newest engine, use Docker or a source build below.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The configuration file lives at <code>\/etc\/valkey\/valkey.conf<\/code> and the service is <code>valkey-server<\/code>, so <code>sudo systemctl restart valkey-server<\/code> applies any change you make.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Run the latest Valkey with Docker<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The official image is the fastest way to the current release. It is also the cleanest way to run a specific version alongside whatever the distro ships. Pull and run it, mapping the default port:<\/p>\n\n\n\n<pre class=\"wp-block-code code\"><code>docker run -d --name valkey -p 6379:6379 valkey\/valkey:9.1<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Check the running version from inside the container and confirm it responds:<\/p>\n\n\n\n<pre class=\"wp-block-code code\"><code>docker exec valkey valkey-server --version\ndocker exec valkey valkey-cli ping<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This returns version 9.1.0 and <code>PONG<\/code>. One detail worth knowing: <code>valkey-cli INFO server<\/code> reports both a <code>valkey_version<\/code> (9.1.0) and a <code>redis_version<\/code> (7.2.4). The Redis version is the compatibility level Valkey advertises to clients that check it, which is how unmodified Redis tooling keeps working. For persistence, mount a volume at <code>\/data<\/code>. If you run all your services this way, the same pattern as the <a href=\"https:\/\/computingforgeeks.com\/how-to-run-redis-in-podman-docker-container\/\" target=\"_blank\" rel=\"noreferrer noopener\">Redis container guide<\/a> applies, swapping the image name.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Build Valkey from source with systemd<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">A source build gives you the latest release as a native service, with TLS compiled in. Install the build dependencies first:<\/p>\n\n\n\n<pre class=\"wp-block-code code\"><code>sudo apt install -y build-essential pkg-config libssl-dev libsystemd-dev curl<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\"><code>libsystemd-dev<\/code> matters more than it looks, and skipping it is the single most common reason a hand-built Valkey fails to start under systemd. More on that in a moment. Detect the latest release tag from the GitHub API so this does not go stale, then download and unpack it:<\/p>\n\n\n\n<pre class=\"wp-block-code code\"><code>VER=$(curl -fsSL https:\/\/api.github.com\/repos\/valkey-io\/valkey\/releases\/latest | grep -oP '\"tag_name\":\\s*\"\\K[^\"]+')\necho \"Building Valkey ${VER}\"\ncd \/usr\/local\/src\nsudo curl -fsSL \"https:\/\/github.com\/valkey-io\/valkey\/archive\/refs\/tags\/${VER}.tar.gz\" -o \"valkey-${VER}.tar.gz\"\nsudo tar xzf \"valkey-${VER}.tar.gz\"\ncd \"valkey-${VER}\"<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Build with TLS and systemd notification support, then install the binaries to <code>\/usr\/local\/bin<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code code\"><code>sudo make BUILD_TLS=yes USE_SYSTEMD=yes -j\"$(nproc)\"\nsudo make install<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Create a dedicated system user and the directories the service needs:<\/p>\n\n\n\n<pre class=\"wp-block-code code\"><code>sudo useradd --system --home-dir \/var\/lib\/valkey --shell \/usr\/sbin\/nologin valkey\nsudo mkdir -p \/etc\/valkey \/var\/lib\/valkey \/var\/log\/valkey\nsudo chown -R valkey:valkey \/var\/lib\/valkey \/var\/log\/valkey<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Create the configuration file at <code>\/etc\/valkey\/valkey.conf<\/code> with a sane cache-oriented baseline:<\/p>\n\n\n\n<pre class=\"wp-block-code code\"><code>bind 127.0.0.1 -::1\nprotected-mode yes\nport 6379\nsupervised systemd\ndaemonize no\npidfile \/run\/valkey\/valkey.pid\ndir \/var\/lib\/valkey\nlogfile \/var\/log\/valkey\/valkey.log\nloglevel notice\nlog-format json\nsave 900 1\nsave 300 10\nappendonly yes\nappendfsync everysec\nmaxmemory 256mb\nmaxmemory-policy allkeys-lru<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The <code>log-format json<\/code> directive is new in the 9.1 line and makes the logs parseable by tools like Loki or the Elastic stack without custom regex. Now create the unit file at <code>\/etc\/systemd\/system\/valkey.service<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code code\"><code>[Unit]\nDescription=Valkey in-memory data store\nAfter=network-online.target\nWants=network-online.target\n\n[Service]\nType=notify\nExecStart=\/usr\/local\/bin\/valkey-server \/etc\/valkey\/valkey.conf\nUser=valkey\nGroup=valkey\nRuntimeDirectory=valkey\nRuntimeDirectoryMode=0750\nRestart=on-failure\nLimitNOFILE=65535\n\n[Install]\nWantedBy=multi-user.target<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Reload systemd, then enable and start the service:<\/p>\n\n\n\n<pre class=\"wp-block-code code\"><code>sudo systemctl daemon-reload\nsudo systemctl enable --now valkey\nsystemctl is-active valkey\nvalkey-cli ping<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">This should report <code>active<\/code> and <code>PONG<\/code>. Here is the gotcha promised earlier. The unit uses <code>Type=notify<\/code>, which means systemd waits for the daemon to send a readiness signal before declaring the service started. Valkey only sends that signal if it was compiled with <code>USE_SYSTEMD=yes<\/code>. Build it without that flag and the server runs fine, the log even says &#8220;Ready to accept connections&#8221;, but systemd never gets the signal, times out after 90 seconds, kills the process, and loops. If you hit a start timeout on a source build, rebuild with <code>USE_SYSTEMD=yes<\/code> (and make sure <code>libsystemd-dev<\/code> was installed first) or change the unit to <code>Type=simple<\/code>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Secure the basics<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Valkey listens only on localhost by default, which is correct for a cache that sits next to your application. Keep it that way unless you have a reason not to. The defaults that matter are already in the config above: <code>bind 127.0.0.1<\/code> and <code>protected-mode yes<\/code>. If an application on another host genuinely needs access, require authentication rather than opening it up blind. Set a password:<\/p>\n\n\n\n<pre class=\"wp-block-code code\"><code>valkey-cli CONFIG SET requirepass 'a-long-random-secret'\nvalkey-cli -a 'a-long-random-secret' --no-auth-warning ping<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">To persist it, add <code>requirepass<\/code> to the config file and restart. If you do expose the port, open it on the firewall only to the application host:<\/p>\n\n\n\n<pre class=\"wp-block-code code\"><code>sudo ufw allow from 10.0.1.20 to any port 6379 proto tcp<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">For traffic that leaves the host, enable TLS. The 9.1 line added automatic certificate reloading, so a renewed certificate is picked up without a restart. Point the <code>tls-port<\/code>, <code>tls-cert-file<\/code>, and <code>tls-key-file<\/code> directives at your certificate and connect with <code>valkey-cli --tls<\/code>. For a cache talking to a co-located app over loopback, plain localhost is fine and TLS is unnecessary overhead.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Use Valkey as a cache in a real app<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">This is where Valkey earns its place. The pattern is cache-aside: the application checks Valkey first, and only falls through to the slow source of truth on a miss, writing the result back so the next request is fast. To make the speedup honest rather than theoretical, the demo below runs the slow path as a real Postgres query that takes half a second (a <code>pg_sleep<\/code> stands in for an expensive aggregation or an upstream API call), and the fast path as a real Valkey lookup.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The application is a small FastAPI service. It connects to Valkey with the standard <code>redis<\/code> Python client, unchanged, which is the drop-in compatibility claim demonstrated rather than asserted. Here is the core of it:<\/p>\n\n\n\n<pre class=\"wp-block-code code\"><code>import time, psycopg2, redis\nfrom fastapi import FastAPI, Request, HTTPException\n\n# redis-py talks to Valkey unchanged (RESP2\/RESP3 wire-compatible)\ncache = redis.Redis(host=\"127.0.0.1\", port=6379, db=0, decode_responses=True)\n\ndef db():\n    return psycopg2.connect(host=\"127.0.0.1\", dbname=\"shop\",\n                            user=\"shopapp\", password=\"shoppass\")\n\napp = FastAPI()\nCACHE_TTL = 60\n\n@app.get(\"\/products\/{pid}\")\ndef get_product(pid: int):\n    t0 = time.perf_counter()\n    key = f\"product:{pid}\"\n    cached = cache.hgetall(key)\n    if cached:\n        ms = (time.perf_counter() - t0) * 1000\n        return {\"source\": \"valkey-cache\", \"elapsed_ms\": round(ms, 2), \"product\": cached}\n    # MISS: pg_sleep(0.5) makes this a genuinely expensive query\n    conn = db(); cur = conn.cursor()\n    cur.execute(\"SELECT id,name,price,stock,pg_sleep(0.5) FROM products WHERE id=%s\", (pid,))\n    row = cur.fetchone(); cur.close(); conn.close()\n    if not row:\n        raise HTTPException(404, \"product not found\")\n    product = {\"id\": row[0], \"name\": row[1], \"price\": str(row[2]), \"stock\": row[3]}\n    cache.hset(key, mapping=product)\n    cache.expire(key, CACHE_TTL)\n    ms = (time.perf_counter() - t0) * 1000\n    return {\"source\": \"postgres-db\", \"elapsed_ms\": round(ms, 2), \"product\": product}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Set up the Python environment and start it:<\/p>\n\n\n\n<pre class=\"wp-block-code code\"><code>python3 -m venv .venv && . .venv\/bin\/activate\npip install fastapi \"uvicorn[standard]\" redis psycopg2-binary\nuvicorn app:app --host 127.0.0.1 --port 8000<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Now hit the same product twice. The first request misses the cache and pays the full Postgres cost. The second is served from Valkey:<\/p>\n\n\n\n<pre class=\"wp-block-code code\"><code>curl -s http:\/\/127.0.0.1:8000\/products\/1\ncurl -s http:\/\/127.0.0.1:8000\/products\/1<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The numbers on the test box tell the whole story. The first call returned in 519 milliseconds from <code>postgres-db<\/code>; the second returned in 0.71 milliseconds from <code>valkey-cache<\/code>. That is the same data served roughly 730 times faster, and it is measured, not estimated:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"920\" height=\"300\" src=\"https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/06\/wm-valkey-cache-aside-latency-ubuntu-2604.png\" alt=\"Cache-aside latency Postgres 519ms versus Valkey 0.71ms on Ubuntu 26.04\" class=\"wp-image-168369\" title=\"\" srcset=\"https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/06\/wm-valkey-cache-aside-latency-ubuntu-2604.png 920w, https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/06\/wm-valkey-cache-aside-latency-ubuntu-2604-300x98.png 300w, https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/06\/wm-valkey-cache-aside-latency-ubuntu-2604-768x250.png 768w\" sizes=\"auto, (max-width: 920px) 100vw, 920px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">To see that Valkey is genuinely doing the work and not some local trick, run <code>valkey-cli monitor<\/code> in a second terminal while the app serves traffic. Every command the application issues streams past in real time:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"920\" height=\"300\" src=\"https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/06\/wm-valkey-cli-monitor-live-traffic-ubuntu-2604.png\" alt=\"valkey-cli MONITOR showing live HSET EXPIRE and INCRBY commands from the app\" class=\"wp-image-168370\" title=\"\" srcset=\"https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/06\/wm-valkey-cli-monitor-live-traffic-ubuntu-2604.png 920w, https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/06\/wm-valkey-cli-monitor-live-traffic-ubuntu-2604-300x98.png 300w, https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/06\/wm-valkey-cli-monitor-live-traffic-ubuntu-2604-768x250.png 768w\" sizes=\"auto, (max-width: 920px) 100vw, 920px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">You can read the cache-aside cycle directly: an <code>HGETALL<\/code> that misses, the <code>HSET<\/code> that fills the cache after the database read, the <code>EXPIRE<\/code> that sets the 60-second TTL, then a second <code>HGETALL<\/code> that hits. The <code>INCRBY<\/code> and <code>EXPIRE<\/code> on the <code>rl:<\/code> key are the rate limiter, covered next.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Rate limiting and sessions on the same instance<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The same Valkey instance does double duty. A fixed-window rate limiter is four lines: increment a per-client counter, set an expiry on the first hit, reject once it crosses the limit.<\/p>\n\n\n\n<pre class=\"wp-block-code code\"><code>@app.get(\"\/limited\")\ndef limited(request: Request):\n    key = f\"rl:{request.client.host}\"\n    count = cache.incr(key)\n    if count == 1:\n        cache.expire(key, 10)      # 10-second window\n    if count > 5:                  # 5 requests per window\n        raise HTTPException(429, f\"rate limit exceeded, retry in {cache.ttl(key)}s\")\n    return {\"ok\": True, \"request\": count, \"limit\": 5}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Fire seven quick requests and the sixth gets refused with a 429 and a real retry hint, because the counter and its TTL live in Valkey:<\/p>\n\n\n\n<pre class=\"wp-block-code code\"><code>req 5 -> HTTP 200  {\"ok\":true,\"request\":5,\"limit\":5}\nreq 6 -> HTTP 429  {\"detail\":\"rate limit exceeded, retry in 10s\"}<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Sessions are just keys with a TTL: <code>cache.set(token, user, ex=30)<\/code> on login, <code>cache.get(token)<\/code> on each request, and the session expires on its own when the TTL runs out. No cron, no cleanup job.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Drop it into your existing stack<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">You rarely write the client code by hand. Because Valkey speaks the Redis protocol, the cache backend in every major framework points at it unchanged. Use the same host, port, and (optional) password you configured above:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Stack<\/th><th>Where it plugs in<\/th><\/tr><\/thead><tbody>\n<tr><td>Django<\/td><td><code>CACHES<\/code> with <code>BACKEND<\/code> = <code>django.core.cache.backends.redis.RedisCache<\/code>, <code>LOCATION<\/code> = <code>redis:\/\/127.0.0.1:6379<\/code><\/td><\/tr>\n<tr><td>Laravel<\/td><td><code>.env<\/code>: <code>CACHE_STORE=redis<\/code>, <code>REDIS_HOST=127.0.0.1<\/code>, <code>REDIS_PORT=6379<\/code><\/td><\/tr>\n<tr><td>Node.js<\/td><td><code>ioredis<\/code> or <code>node-redis<\/code>: <code>new Redis(6379, '127.0.0.1')<\/code><\/td><\/tr>\n<tr><td>Spring Boot<\/td><td><code>spring.data.redis.host=127.0.0.1<\/code>, <code>spring.data.redis.port=6379<\/code><\/td><\/tr>\n<\/tbody><\/table><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">None of these know or care that the server is Valkey rather than Redis. The full demo application, including the rate limiter, session endpoints, and a Compose file, is published as a companion repo at <a href=\"https:\/\/github.com\/c4geeks\/valkey-demo\" target=\"_blank\" rel=\"noreferrer noopener\">github.com\/c4geeks\/valkey-demo<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Isolate tenants with database-level ACLs<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The 9.1 line added a genuinely useful access-control feature: you can scope a user to specific numbered databases. Before this, an ACL user with key access could touch every logical database on the instance. Now you can hand each tenant its own database and forbid the rest, which makes a single Valkey safe to share across tenants. Create a user allowed only on database 1:<\/p>\n\n\n\n<pre class=\"wp-block-code code\"><code>valkey-cli ACL SETUSER tenant1 on '>tenantpass' '+@all' '~*' resetdbs db=1<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The <code>resetdbs<\/code> keyword clears the default &#8220;all databases&#8221; grant, and <code>db=1<\/code> allows exactly one. Now that user can work in database 1 but is refused everywhere else:<\/p>\n\n\n\n<pre class=\"wp-block-code code\"><code>valkey-cli --user tenant1 --pass tenantpass -n 1 SET tenant:greeting \"hello from db1\"\nvalkey-cli --user tenant1 --pass tenantpass -n 0 SET tenant:greeting \"should fail\"<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The write to database 1 returns <code>OK<\/code>. The write to database 0 is rejected with <code>NOPERM No permissions to access database<\/code>. The default user keeps its <code>alldbs<\/code> grant, so your admin connection is unaffected:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"920\" height=\"280\" src=\"https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/06\/wm-valkey-db-level-acl-multitenant-ubuntu-2604.png\" alt=\"Valkey 9.1 database-level ACL denying a tenant access to database 0\" class=\"wp-image-168371\" title=\"\" srcset=\"https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/06\/wm-valkey-db-level-acl-multitenant-ubuntu-2604.png 920w, https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/06\/wm-valkey-db-level-acl-multitenant-ubuntu-2604-300x91.png 300w, https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/06\/wm-valkey-db-level-acl-multitenant-ubuntu-2604-768x234.png 768w\" sizes=\"auto, (max-width: 920px) 100vw, 920px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">This is not a substitute for separate instances when tenants are truly untrusted, but for internal multi-tenant separation it removes a real footgun.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Migrate from Redis to Valkey with no downtime<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Because Valkey forked from Redis and kept the replication protocol, you can migrate a live Redis instance by making Valkey a replica of it, letting it sync, then promoting it. No dump files, no application downtime. With a Redis instance running on port 6390 and an empty Valkey on 6391, point one at the other:<\/p>\n\n\n\n<pre class=\"wp-block-code code\"><code>valkey-cli -p 6391 REPLICAOF 127.0.0.1 6390<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Valkey connects, pulls a full copy of the dataset, and then streams ongoing changes. Watch the link come up and confirm the keys arrived:<\/p>\n\n\n\n<pre class=\"wp-block-code code\"><code>valkey-cli -p 6391 INFO replication | grep master_link_status\nvalkey-cli -p 6391 DBSIZE\nvalkey-cli -p 6391 GET user:1<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Once <code>master_link_status<\/code> reads <code>up<\/code>, the replica holds the same key count as the source, and every type came across intact: strings, hashes, and lists all replicated. When you are ready to cut over, point your application at Valkey and promote it to a standalone primary:<\/p>\n\n\n\n<pre class=\"wp-block-code code\"><code>valkey-cli -p 6391 REPLICAOF NO ONE<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The full sequence on the test box ran source database size of four keys, link up, matching size on the replica, the <code>user:1<\/code> value intact, then a clean promotion to primary:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"920\" height=\"410\" src=\"https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/06\/wm-valkey-redis-migration-keys-preserved-ubuntu-2604.png\" alt=\"Live Redis to Valkey migration via REPLICAOF preserving all keys\" class=\"wp-image-168373\" title=\"\" srcset=\"https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/06\/wm-valkey-redis-migration-keys-preserved-ubuntu-2604.png 920w, https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/06\/wm-valkey-redis-migration-keys-preserved-ubuntu-2604-300x134.png 300w, https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/06\/wm-valkey-redis-migration-keys-preserved-ubuntu-2604-768x342.png 768w\" sizes=\"auto, (max-width: 920px) 100vw, 920px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">One caveat on compatibility: standard data structures, Lua scripts, pub\/sub, streams, and ACLs all carry over cleanly. What does not are the proprietary closed-source Redis modules (RediSearch, RedisJSON, RedisTimeSeries, RedisBloom), which are not part of Valkey. If your application leans on those specific modules, audit that first. For the far more common case of Redis as a cache, session store, queue, or rate limiter, the migration is genuinely a drop-in. Existing pages like the <a href=\"https:\/\/computingforgeeks.com\/monitor-redis-prometheus-grafana\/\" target=\"_blank\" rel=\"noreferrer noopener\">Redis monitoring with Prometheus and Grafana<\/a> setup keep working against Valkey, because the metrics exporter speaks the same protocol.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Tune Valkey for production throughput<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Before tuning anything, get a baseline from the box you will actually run on. <code>valkey-benchmark<\/code> ships with the install. On the 2-vCPU test VM, single-operation throughput held around 80,000 SET and GET per second at a p50 under 0.33 milliseconds; with pipelining enabled it reached 568,000 SET and 952,000 GET per second:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"920\" height=\"300\" src=\"https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/06\/wm-valkey-benchmark-throughput-ubuntu-2604.png\" alt=\"valkey-benchmark SET and GET requests per second on Ubuntu 26.04\" class=\"wp-image-168372\" title=\"\" srcset=\"https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/06\/wm-valkey-benchmark-throughput-ubuntu-2604.png 920w, https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/06\/wm-valkey-benchmark-throughput-ubuntu-2604-300x98.png 300w, https:\/\/computingforgeeks.com\/wp-content\/uploads\/2026\/06\/wm-valkey-benchmark-throughput-ubuntu-2604-768x250.png 768w\" sizes=\"auto, (max-width: 920px) 100vw, 920px\" \/><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">The marketing figure of 2.1 million requests per second is real but earned on much larger hardware: nine I\/O threads, small payloads, and many cores. The two levers that close most of the gap are visible above. Pipelining batches round trips, so if your workload can send commands in groups, it is the single biggest win. The second lever is I\/O threads, which default to 1. On a host with spare cores, raise it to match:<\/p>\n\n\n\n<pre class=\"wp-block-code code\"><code>valkey-cli CONFIG SET io-threads 4<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Make it permanent in <code>valkey.conf<\/code> and restart. The threading redesign in the 9.x line is where the per-instance throughput gains come from, but it only helps when there are cores to use, so do not set it above your core count on a small VM.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Three settings decide behaviour under real load. <code>maxmemory<\/code> caps memory; pair it with <code>maxmemory-policy allkeys-lru<\/code> for a pure cache so old keys are evicted instead of writes failing. <code>appendfsync everysec<\/code> is the durability sweet spot, flushing the append-only file once a second rather than on every write; switch to <code>always<\/code> only if losing one second of writes is unacceptable, and accept the throughput cost. For a cache where the data also lives in a database, you can turn persistence off entirely and let Valkey rebuild from the source of truth. The metrics worth watching are <code>used_memory<\/code> against your <code>maxmemory<\/code> ceiling, <code>evicted_keys<\/code> (steady growth means the cache is too small), and the keyspace hit ratio from <code>INFO stats<\/code>, which is the number that tells you whether the cache is actually earning its keep.<\/p>\n\n","protected":false},"excerpt":{"rendered":"<p>Valkey is a drop-in replacement for Redis, and on Ubuntu the install is a three-way choice with very different version outcomes. The Ubuntu archive gives you a Canonical-supported package that lags upstream, the official Docker image gives you the latest release in seconds, and a source build gives you that same latest release as a &#8230; <a title=\"Install Valkey on Ubuntu 26.04 \/ 24.04 (Redis Replacement)\" class=\"read-more\" href=\"https:\/\/computingforgeeks.com\/install-valkey-ubuntu\/\" aria-label=\"Read more about Install Valkey on Ubuntu 26.04 \/ 24.04 (Redis Replacement)\">Read more<\/a><\/p>\n","protected":false},"author":21,"featured_media":168375,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[461,299,50,81],"tags":[324,592,2254],"cfg_series":[39878],"class_list":["post-168374","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-databases","category-how-to","category-linux-tutorials","category-ubuntu","tag-databases","tag-redis","tag-ubuntu","cfg_series-valkey-on-linux"],"_links":{"self":[{"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/posts\/168374","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/users\/21"}],"replies":[{"embeddable":true,"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/comments?post=168374"}],"version-history":[{"count":6,"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/posts\/168374\/revisions"}],"predecessor-version":[{"id":168404,"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/posts\/168374\/revisions\/168404"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/media\/168375"}],"wp:attachment":[{"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/media?parent=168374"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/categories?post=168374"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/tags?post=168374"},{"taxonomy":"cfg_series","embeddable":true,"href":"https:\/\/computingforgeeks.com\/wp-json\/wp\/v2\/cfg_series?post=168374"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}