RE:NODE

Databases12 min read

Valkey getting started: valkey-cli, AUTH and first commands

Connect to a Valkey server with valkey-cli or redis-cli, authenticate safely, build a connection URL for your app and learn the commands you use daily.

0 readers

To use a Valkey server you need three things: a host, a port and a password. With those, valkey-cli -h <host> -p <port> --askpass opens an interactive session, PING answers PONG, and your application connects with a URL of the form redis://default:<password>@<host>:<port>/0. Because Valkey speaks the Redis protocol, redis-cli works exactly as well as valkey-cli, and every Redis client library connects unchanged. The rest of this post is the detail: getting a command-line client onto your machine, authenticating without leaking the password, building URLs that survive special characters, the twenty commands you will use every day, connecting from code, and what the common error messages mean.

What you need before you start#

Whoever runs the server gives you:

  • Host: a name or IP address, such as db.example.net.
  • Port: 6379 by convention, though a hosted server often listens on another number.
  • Password: Valkey's requirepass, or the password of an ACL user. Without a username, you are authenticating as the built-in user called default.
  • TLS or not: whether the server expects an encrypted connection. If it does, the URL scheme is rediss:// and the CLI needs --tls; if it does not, a TLS client fails at the handshake.

Write them into a password manager, not a text file on the desktop. Valkey has no login delay and no lockout by default; a password is the only thing between the port and your data, so it should be long and random. Valkey security and ACLs goes into exposure, ACL users and the dangerous commands.

Getting a command-line client

You do not need a server installed locally, only the client.

PlatformHow to get a client
Debian, Ubunturedis-tools package (redis-cli); newer releases also package Valkey's tools
Fedoravalkey package, which includes valkey-cli
macOSbrew install valkey
WindowsWSL with a Linux package, or Docker
Anywhere with Dockerdocker run --rm -it valkey/valkey valkey-cli -h <host> -p <port>

redis-cli from any Redis 6 or 7 release talks to Valkey without trouble. Graphical tools built for Redis - RedisInsight, Another Redis Desktop Manager and others - generally work with Valkey too, since they speak the same protocol; they are pleasant for browsing keys but no substitute for knowing the CLI.

Connecting and authenticating#

The obvious way is also the leaky one:

bash
$ valkey-cli -h db.example.net -p 6380 -a 'the-password'Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.

The warning is earned. A password on the command line is visible to anyone who can list processes on that machine, and it lands in your shell history. Better options, in order of preference:

bash
# Prompt for it$ valkey-cli -h db.example.net -p 6380 --askpass# Or pass it through the environment$ export VALKEYCLI_AUTH='the-password'      # redis-cli reads REDISCLI_AUTH$ valkey-cli -h db.example.net -p 6380# Or authenticate inside the session$ valkey-cli -h db.example.net -p 6380db.example.net:6380> AUTH the-passwordOK

AUTH password authenticates as the default user; AUTH username password authenticates as an ACL user. The CLI also accepts --user for the username. If the server uses TLS, add --tls and, for a private certificate authority, --cacert ca.pem.

A first session should be four commands long:

code
db.example.net:6380> PINGPONGdb.example.net:6380> INFO server# Serverredis_version:7.2.4server_name:valkeyvalkey_version:8.1.1...db.example.net:6380> DBSIZE(integer) 0db.example.net:6380> QUIT

INFO server tells you exactly what you are connected to. Valkey reports redis_version:7.2.4 for the benefit of old clients, and its real version in valkey_version. Note that version: commands added after the fork exist only in releases that include them.

Connection URLs#

Most client libraries and frameworks accept a single URL, which keeps configuration to one environment variable.

code
redis://[username]:[password]@[host]:[port]/[database]
URLMeaning
redis://:s3cret@db.example.net:6380/0Password only, default user, database 0
redis://default:s3cret@db.example.net:6380/0The same, with the user named
redis://app:s3cret@db.example.net:6380/2ACL user app, database 2
rediss://:s3cret@db.example.net:6380/0The same over TLS

The trap is special characters. A password containing @, :, /, #, ? or % breaks the URL parser, usually with a misleading "connection refused" or "invalid port" error. Percent-encode them - @ becomes %40, / becomes %2F, # becomes %23 - or generate passwords from letters and digits only, which at forty characters is as strong as anything you need.

bash
$ python3 -c 'import urllib.parse,sys; print(urllib.parse.quote(sys.argv[1], safe=""))' 'p@ss/w#rd'p%40ss%2Fw%23rd

Some Valkey-specific clients also accept valkey:// and valkeys://, but redis:// is understood by every client and framework, so it is the safer default. Keep the URL in an environment variable such as VALKEY_URL or REDIS_URL - environment variables and secrets covers why it does not belong in the repository.

The commands you will use every day#

Valkey is a key-value store where each value has a type, and commands are specific to the type. A session that touches the main ones:

code
> SET greeting "hello" EX 60OK> GET greeting"hello"> TTL greeting(integer) 57> INCR page:views(integer) 1> HSET user:42 name "Anna" plan "pro"(integer) 2> HGETALL user:421) "name"2) "Anna"3) "plan"4) "pro"> LPUSH jobs "send-email:913"(integer) 1> RPOP jobs"send-email:913"> SADD online:users 42 57(integer) 2> ZADD leaderboard 3100 anna 2800 ben(integer) 2> ZREVRANGE leaderboard 0 9 WITHSCORES1) "anna"2) "3100"3) "ben"4) "2800"> TYPE user:42hash> EXPIRE user:42 3600(integer) 1> UNLINK greeting(integer) 1
CommandWhat it does
SET key value EX 60Store a string with a 60-second expiry
SET key value NXStore only if the key does not exist (locks, deduplication)
GET, MGETRead one or many strings in one round trip
INCR, INCRBYAtomic counters
EXPIRE, TTL, PERSISTSet, read and remove expiry
HSET, HGET, HGETALLField-value records
LPUSH, RPOP, BRPOPLists as queues, blocking pop for workers
SADD, SISMEMBERSets of unique members
ZADD, ZRANGESorted sets: leaderboards, time-ordered indexes
DEL, UNLINKDelete; UNLINK frees memory in the background
SCANIterate keys safely, a page at a time

Valkey data types explained covers when to use each type in more depth.

Connecting from code#

Create one client when the application starts and reuse it. Opening a connection per request wastes a round trip on the handshake and AUTH, and leaks connections when something throws.

Node.js with `ioredis`:

javascript
import Redis from "ioredis";const valkey = new Redis(process.env.VALKEY_URL, {  maxRetriesPerRequest: 3,});await valkey.set("greeting", "hello", "EX", 60);console.log(await valkey.get("greeting"));

Python with `redis-py`:

python
import osimport redisvalkey = redis.Redis.from_url(os.environ["VALKEY_URL"], decode_responses=True)valkey.set("greeting", "hello", ex=60)print(valkey.get("greeting"))

decode_responses=True returns str instead of bytes, which is what you want unless you store binary values.

.NET with StackExchange.Redis, which uses its own connection-string format rather than a URL:

code
db.example.net:6380,password=s3cret,abortConnect=false

Pass that to ConnectionMultiplexer.Connect once, and share the multiplexer across the whole application; it is designed to be a singleton. abortConnect=false lets the application start and keep retrying if the server is briefly unreachable, rather than failing at boot.

Connection counts matter more than people expect: every process, worker and cron job holds its own connections. Connection pools and limits covers sizing them.

Naming keys and letting them expire#

Key names and numbered databases

Valkey has no tables, so structure lives in key names. The convention is colon-separated segments from general to specific: user:42, session:9f2c..., cache:v3:report:team:7:2026-10-08. A prefix per application (shop:, api:) keeps two applications sharing one server from colliding, and a version segment in cache keys means changing the shape of a value is a prefix change rather than a flush.

A standalone server also has numbered databases, 0 to 15 by default, selected with SELECT 2 or the /2 at the end of a URL. They share memory, persistence and the password, and FLUSHALL empties all of them, so they are weaker isolation than they look. A prefix is usually clearer; a separate server is real isolation.

How expiry works

Any key can carry a time to live. SET key value EX 60 sets it at write time; EXPIRE key 60 adds it to an existing key; TTL key reads what is left in seconds, PTTL in milliseconds. Two return values from TTL confuse people: -1 means the key exists with no expiry, and -2 means the key does not exist at all.

Expired keys are removed two ways. When a client touches a key whose time has passed, it is deleted on the spot and the client sees nothing. Separately, the server samples keys with expiries in the background and deletes the expired ones it finds. Between the two, memory from expired keys is reclaimed steadily rather than instantly, which is why DBSIZE can briefly count keys that no client can read.

The rule that causes most surprises: a plain SET on an existing key removes its expiry. If code refreshes a cached value with SET key newvalue and no EX, the key now lives forever. Either pass the expiry on every write, or use SET key newvalue KEEPTTL to keep the one it had. EXPIRE also accepts NX, XX, GT and LT to set an expiry only when none exists, only when one does, or only when the new one is later or earlier - handy for sliding session timeouts. PERSIST key removes an expiry deliberately.

Give everything that is a cache or a session an expiry. A key without one is a promise that somebody will delete it, and nobody does.

Pipelines and transactions#

Every command costs a network round trip, and from an application server to a database that is typically a fraction of a millisecond to a few milliseconds. A loop that sends a thousand GET commands one at a time spends almost all its time waiting. Two tools fix that.

Multi-key commands do several operations in one round trip: MGET and MSET for strings, HMGET for several hash fields, and variadic forms of SADD, LPUSH and DEL.

Pipelining sends many commands without waiting for each reply, then reads all the replies at once. Every client library supports it - pipeline() in ioredis and redis-py, batches in StackExchange.Redis - and it routinely makes bulk work ten times faster or more. A pipeline is not atomic; other clients' commands can run between yours.

When you need atomicity, MULTI queues commands and EXEC runs them together, with nothing from other clients in between. There is no rollback: if one command in the block fails, the others still run. WATCH key before MULTI makes the transaction abort if the watched key changed in the meantime, which gives you optimistic locking. For logic that must read and then decide, a Lua script with EVAL runs on the server as a single atomic step, and is often simpler than WATCH - Valkey rate limiting has a worked example.

Valkey on RE:NODE#

On the database line, each Valkey server comes with a generated password and is reached on the host and port shown in the panel for that plan, with no proxy slot in between - your application connects to that address directly, with redis://default:<password>@<host>:<port>/0 or the equivalent in your client. Data is saved to disk with AOF and snapshots, so a restart does not empty it. Plans start at 256 MB of memory, of which the plan lists roughly 200 MB as usable for data. If the application runs on an app plan, put the URL in an environment variable on its Startup tab.

When the connection fails#

`NOAUTH Authentication required.` You connected but did not authenticate. Add the password to the URL or the CLI.

`WRONGPASS invalid username-password pair or user is disabled.` The password is wrong, or it was mangled by URL parsing. Test the same password with --askpass in the CLI; if that works, the problem is encoding in the URL.

`Connection refused` or a timeout. Wrong host or port, a firewall between you and the server, or a TLS mismatch. valkey-cli -h <host> -p <port> PING from the same machine as the application separates network problems from application ones.

`OOM command not allowed when used memory > 'maxmemory'.` The server is full and its eviction policy does not allow evicting anything. Valkey memory and eviction covers the fix.

`ERR max number of clients reached`. Something is leaking connections, usually a client created per request. CLIENT LIST shows who is connected and from where.

`LOADING` ... loading the dataset in memory. The server has just started and is reading its snapshot or replaying its append-only file. Commands are refused until it finishes, which takes seconds for a small dataset and longer for a large one. Good clients retry this error on their own; if yours does not, add a short retry with backoff around start-up.

`READONLY You can't write against a read only replica.` You are connected to a replica rather than the primary. Check the host and port - it usually means configuration copied from another environment.

Connections drop after a few minutes idle. Something between the client and the server - a firewall, a NAT gateway, a load balancer - is forgetting idle TCP connections. The server side has timeout (close idle clients after N seconds, 0 by default, meaning never) and tcp-keepalive (send keepalive probes, 300 seconds by default). On the client side, enable keepalive in the library's options and make sure it reconnects automatically, which ioredis, redis-py and StackExchange.Redis all do when configured to.

`MISCONF` ... unable to persist to disk. The last background save failed, and the server stops accepting writes so you notice. Usually the disk is full or the fork for the save ran out of memory.

FAQ#

Can I use redis-cli with Valkey?

Yes. redis-cli from Redis 6 or 7 speaks the same protocol and works for every command Valkey shares with Redis. The only difference is cosmetic: valkey-cli reads its password from VALKEYCLI_AUTH, while redis-cli reads REDISCLI_AUTH.

What username do I use if I was only given a password?

default. Valkey's built-in user is called default, and AUTH password with no username authenticates as it. In a URL, either leave the username empty (redis://:password@host) or write default explicitly.

Is it safe to run MONITOR to see what my app is doing?

For a few seconds while debugging, yes. MONITOR streams every command the server receives, which costs noticeable throughput on a busy server and prints every value, including session data and anything sensitive. Never leave it running, and never paste its output anywhere public.

How do I see how much memory my data uses?

INFO memory shows used_memory_human for the whole server, and MEMORY USAGE keyname for a single key. valkey-cli --bigkeys and --memkeys sample the keyspace to find the largest keys without blocking the server.

Why does my password work in the CLI but not in my app?

Almost always URL encoding. A character such as @, / or # in the password is read as part of the URL's structure. Percent-encode it, or regenerate the password with letters and digits only.


Comments

Completely anonymous: no account, no email, no cookie. We store the name you type, the text and the time - nothing else. Links are limited and markup is not rendered.

0/2000