Through TenantsDB, each tenant gets isolated key-value storage. On L1 shared instances the proxy prefixes every key with the tenant's namespace so tenants never see each other's data. On L2 dedicated instances each tenant has its own Redis process. Your app connects with any standard Redis client. No SDK needed.
Redis works differently from the SQL and MongoDB databases in TenantsDB. There is no schema, no tables, no blueprints to deploy, and no DDL. You create a Redis blueprint to define settings (default TTL, max keys, and per-pattern TTL rules), then create tenants. Each tenant connects and starts using Redis commands immediately.
Redis also uses a different connection-string layout: the proxy reads the username field to decide what to route to. The blueprint name reaches the workspace; a tenant ID reaches that tenant. The password is always your proxy_password. TLS is enabled via the rediss:// scheme (double-s) for all clients.
Every Redis connection uses a routing identifier as the username (either a blueprint name for workspace mode, or a tenant ID for tenant mode) and a proxy_password. Pick the scope by matching the reach you need.
The proxy_password printed when you created the project (or any sk_ key with scope_type=project). Reaches workspaces and the control plane API. Cannot reach individual tenants through the wire proxy. Use for admin tooling, the dashboard, and CLI calls.
sk_ value as the Redis password. Use the project proxy_password instead. sk_ keys are for HTTP API calls only; the wire proxy rejects them on tenant routing as of the latest release.
Generated by tdb apikeys create --scope-type workspace --scope-values myapp. Reaches the named workspace AND every tenant under that blueprint. Use for backend jobs that touch all customers of a blueprint, such as cache warming, key-space sweeps, and analytics ingest.
Auto-generated on every tdb tenants create and returned in the response connection_string. Reaches one tenant only. Use this for the per-customer Redis connection in your application.
Need a read-only or write-only key for the same tenant? Create one with tdb apikeys create --role read --scope-type tenant --scope-values wayne.
admin, write, or read). The proxy logs in to the backend as a native Redis ACL user that enforces permissions at the Redis layer. A read key cannot execute SET even if scope allows the connection. Scope and role stack: defense in depth.redis-cli --tls -u "rediss://myapp:tdb_d2bf66ed7898c448@redis.tenantsdb.com:6379/0"
The username field carries the blueprint name (myapp in the example above). The password is your project proxy_password or a workspace-scoped proxy_password for that blueprint.
Workspace connections are useful for testing settings, inspecting workspace-level keys, and running maintenance scripts that don't belong to any single tenant. Workspace keys are separate from tenant keys.
proxy_password.
Each tenant has its own proxy_password. The value is returned in the connection_string field of the tdb tenants create response. Save it alongside the tenant record in your application, keyed by tenant_id.
redis-cli --tls -u "rediss://wayne:tdb_4f2c9d1ab7e8350c@redis.tenantsdb.com:6379/0"
redis-cli --tls -u "rediss://globex:tdb_e7b1f5c821a04d68@redis.tenantsdb.com:6379/0"
proxy_password so credentials never travel across customers. Tenant wayne and tenant globex can both have a key called user:1 with different values, and a leaked wayne password cannot read globex.proxy_password or an sk_ key as a tenant password, authentication fails with: AUTH failed: ERR access denied. The wire proxy underlying reason is credential is project-scoped; direct-tenant connections require a tenant-scoped or workspace-scoped key. This is intentional. The project credential is for workspace and control plane access only.# Strings SET user:1 "Alice" SET user:2 "Bob" # Hashes (structured data) HSET account:1 name "Alice" balance 1000 HSET account:2 name "Bob" balance 2000 # With expiration (seconds) SET session:abc "session_data" EX 3600 # Lists (queues) LPUSH queue:jobs "job1" "job2" "job3"
All standard Redis commands work through the proxy. The proxy transparently prefixes keys on L1 shared instances for tenant isolation. Your application never needs to handle prefixing.
wayne using its per-tenant proxy_password. For workspace connections, replace the username with the blueprint name and the password with your project or workspace-scoped proxy_password.const Redis = require('ioredis'); const redis = new Redis(process.env.REDIS_URL); // REDIS_URL=rediss://wayne:tdb_4f2c9d1ab7e8350c@redis.tenantsdb.com:6379/0 // String await redis.set('user:1', 'Alice'); const name = await redis.get('user:1'); // Hash await redis.hset('account:1', 'name', 'Alice', 'balance', '1000'); const balance = await redis.hget('account:1', 'balance'); // With expiration await redis.set('session:abc', 'data', 'EX', 3600);
npm install ioredis
rediss:// scheme natively for TLS connections. Look up the per-tenant proxy_password by tenant ID in your application's secret store.import redis import os r = redis.from_url(os.environ['REDIS_URL']) # REDIS_URL=rediss://wayne:tdb_4f2c9d1ab7e8350c@redis.tenantsdb.com:6379/0 # String r.set('user:1', 'Alice') name = r.get('user:1') # b"Alice" # Hash r.hset('account:1', mapping={'name': 'Alice', 'balance': '1000'}) balance = r.hget('account:1', 'balance') # b"1000" # With expiration r.set('session:abc', 'data', ex=3600)
pip install redis
decode_responses=True in the connection to get strings instead.package main import ( "context" "fmt" "os" "time" "github.com/redis/go-redis/v9" ) func main() { ctx := context.Background() opt, _ := redis.ParseURL(os.Getenv("REDIS_URL")) // REDIS_URL=rediss://wayne:tdb_4f2c9d1ab7e8350c@redis.tenantsdb.com:6379/0 opt.Protocol = 2 // pin RESP2; some go-redis versions request RESP3 upgrades rdb := redis.NewClient(opt) // String rdb.Set(ctx, "user:1", "Alice", 0) name, _ := rdb.Get(ctx, "user:1").Result() fmt.Println(name) // Hash rdb.HSet(ctx, "account:1", "name", "Alice", "balance", "1000") balance, _ := rdb.HGet(ctx, "account:1", "balance").Result() fmt.Println(balance) // With expiration rdb.Set(ctx, "session:abc", "data", 60*time.Second) }
go get github.com/redis/go-redis/v9
rediss:// scheme and enables TLS automatically. Setting opt.Protocol = 2 avoids RESP3 negotiation, which not all proxy paths support yet.
On L1 shared instances, all tenants share the same Redis server. The proxy isolates tenants by automatically prefixing every key with the tenant's namespace. Your application sends SET user:1 "Alice" and the proxy stores it as tdb_xxx_tenant_wayne:user:1. On read, the prefix is stripped. Your application never sees the prefix.
On L2 dedicated instances each tenant has its own Redis process, so no prefixing is applied. The connection string stays the same in both modes; tenants can be moved between L1 and L2 without code changes.
The proxy uses standard Redis AUTH with username and password (Redis 6+ ACL auth). Pass the routing identifier as the username and your proxy_password as the password.
| Connecting to | Username | Password |
|---|---|---|
| A workspace | Blueprint name (e.g., myapp) |
Project or workspace-scoped proxy_password |
| A tenant | Tenant ID (e.g., wayne) |
Tenant-scoped or workspace-scoped proxy_password |
proxy_password from tdb tenants create or tdb apikeys create. It is not the sk_ value (which is for HTTP API calls only). Older docs that suggested using sk_ as the Redis password are obsolete and the wire proxy now rejects that pattern.
The proxy enforces credential scope at the AUTH step. Project credentials can connect to workspaces. Workspace-scoped credentials can connect to their workspace AND every tenant of that blueprint. Tenant-scoped credentials can only connect to their specific tenant.
Using a project credential against a tenant fails with:
AUTH failed: ERR access denied
The proxy log records the underlying reason: credential is project-scoped; direct-tenant connections require a tenant-scoped or workspace-scoped key. Using a workspace-scoped credential against a different blueprint's tenant produces the same generic AUTH failed response with a scope-mismatch reason in the log.
Each API key has a role (admin, write, or read) which the proxy maps to a native Redis ACL user on the backend connection. A read-role key attempting a SET fails from Redis itself, not just the proxy.
Through the wire proxy the failure looks like:
(error) NOPERM this user has no permissions to run the 'set' command
Through the control plane POST /tenants/{id}/query endpoint the same operation returns HTTP 403 with code: permission_denied and the Redis NOPERM message in the error body. Same underlying Redis ACL user, two different transports.
The role users also restrict the key space they can touch. On L1 the ACL pattern is scoped to ~tdb_xxx_tenant_wayne:*, blocking cross-tenant KEYS or SCAN attempts at the Redis layer even if the proxy were bypassed.
If you connect to a workspace or tenant whose blueprint targets a different database type (PostgreSQL, MySQL, MongoDB), the proxy rejects the connection with a clear pointer to the right proxy:
blueprint "pg_test" is PostgreSQL, not Redis. Connect via the postgresql proxy instead.
The proxy enforces per-IP connection limits and a wire-level auth-ban tracker. These apply to Redis the same way they apply to PostgreSQL, MySQL, and MongoDB. Full values and rejection message formats live in Connection Limits & Rejection Behavior.
For Redis specifically, IP-level rejections arrive as a standard -ERR reply with the connection rejected: prefix. Standard clients (ioredis, redis-py, go-redis, jedis) surface this as a normal connection error. Example:
(error) ERR connection rejected: your IP is temporarily rate-limited after repeated failed auth attempts, retry in 47s
The TTL portion (retry in 47s) decrements on each retry while the ban is active, so client retry logic can parse it for accurate backoff. The auth-ban tracker counts failed AUTH commands, so wrong-password retry storms from broken application config will trigger it. Distinct from scope rejections (which surface as AUTH failed: ERR access denied), IP-level rejections use the connection rejected: prefix so client-side error handling can branch on them cleanly.
Redis settings are configured per workspace and differ from the SQL and MongoDB settings.
| Setting | Description |
|---|---|
| default_ttl | Default expiration (seconds) applied to keys that do not have an explicit TTL set. |
| max_keys | Maximum number of keys allowed per tenant. |
| patterns | Pattern-specific TTL rules. Each pattern (e.g., session:*) can have its own TTL and an enforced flag that overrides user-set TTLs. |
enforced pattern wins over everything and replaces an explicit EX or PX on the write. A non-enforced pattern or default_ttl applies only when the write sets no expiry of its own. Patterns are always checked before default_ttl.
All connections require TLS. Use the rediss:// scheme (double-s) in your connection URL. TLS is terminated at the edge by HAProxy. Every Redis client library supports rediss:// natively.
There are no proxy-level size restrictions. The Redis native limit of 512MB per value applies. Keep in mind that large values use their full size in memory on the Redis server. Connection strings stay the same when tenants migrate between L1 and L2.