Connect to Redis
How to connect to Redis in Python, JavaScript, and Java — with AUTH, TLS, pooling, and the core data types you'll actually use.
Overview
Redis is an in-memory data store — reads and writes land in microseconds because nothing touches disk on the hot path. Connecting is easy; connecting right is where people get burned: no AUTH on an exposed port, no TLS over a public network, no pool, no maxmemory policy. Here’s the working setup in Python, JavaScript, and Java, plus the connection details most tutorials skip.
When to Use
Use this resource when:
- Adding a cache layer in front of a database to cut latency and load — see cache-aside for the most common pattern
- Storing sessions shared across app instances (stateless services can’t keep them in-process)
- Building rate limiters, leaderboards, feature flags, or real-time counters
- Passing lightweight messages between services with pub/sub or Streams
Redis is the wrong default when data must survive a restart by itself, when the dataset can’t fit in RAM, or when you need ad-hoc queries over relations — use PostgreSQL or MySQL for that, and keep Redis next to it, not instead of it.
Solution
Python
import os
import redis
# redis://default:password@host:6379/0 — use rediss:// for TLS
r = redis.Redis.from_url(
os.getenv("REDIS_URL", "redis://localhost:6379/0"),
decode_responses=True,
socket_timeout=5,
socket_connect_timeout=5,
max_connections=20
)
# Strings
r.set('user:1:name', 'Alice', ex=3600)
name = r.get('user:1:name')
# Lists
r.lpush('queue:tasks', 'task_1')
task = r.brpop('queue:tasks', timeout=5)
# Hashes
r.hset('user:1', mapping={'email': 'alice@example.com', 'role': 'admin'})
user = r.hgetall('user:1')
JavaScript
const Redis = require('ioredis');
const redis = new Redis(process.env.REDIS_URL || 'redis://localhost:6379', {
// rediss:// URLs enable TLS automatically; add tls: {} to override certs
maxRetriesPerRequest: 3,
retryStrategy: (times) => Math.min(times * 200, 2000), // backoff, ms
enableReadyCheck: true,
lazyConnect: false
});
// Strings
await redis.set('user:1:name', 'Alice', 'EX', 3600);
const name = await redis.get('user:1:name');
// Lists
await redis.lpush('queue:tasks', 'task_1');
const task = await redis.brpop('queue:tasks', 5);
// Hashes
await redis.hset('user:1', 'email', 'alice@example.com', 'role', 'admin');
const user = await redis.hgetall('user:1');
Java
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import java.util.Map;
public class RedisExample {
// For TLS use a URI: new JedisPool("rediss://default:pass@host:6379")
private final JedisPool pool = new JedisPool("localhost", 6379);
public void stringOps() {
try (Jedis jedis = pool.getResource()) {
jedis.setex("user:1:name", 3600, "Alice");
String name = jedis.get("user:1:name");
}
}
public void listOps() {
try (Jedis jedis = pool.getResource()) {
jedis.lpush("queue:tasks", "task_1");
var task = jedis.brpop(5, "queue:tasks");
}
}
public void hashOps() {
try (Jedis jedis = pool.getResource()) {
jedis.hset("user:1", "email", "alice@example.com");
jedis.hset("user:1", "role", "admin");
Map<String, String> user = jedis.hgetAll("user:1");
}
}
}
Explanation
All three clients speak RESP, Redis’s wire protocol, and each keeps connections alive differently: redis.Redis manages an internal pool (max_connections), ioredis pipelines commands over a single socket with built-in reconnect logic, and JedisPool hands out one blocking connection per getResource() — always inside try-with-resources or it leaks.
Two things the happy-path examples hide. First, production instances need AUTH: since Redis 6, ACLs give you named users (ACL SETUSER app on >secret ~app:* +@all), and the password rides in the URL — redis://default:secret@host:6379. Second, plain redis:// is unencrypted — over anything but loopback you want rediss:// (TLS). Managed services like ElastiCache and Redis Cloud hand you a rediss:// endpoint for exactly this reason.
Once you’re connected and caching, the next step is Redis cache patterns — key naming, stampede protection, and invalidation strategies.
What Works
- Put the connection string in an env var (
REDIS_URL) — credentials don’t belong in source, and the URL format works across all three drivers - Set explicit expiration (
EX,PX,EXAT, orKEEPTTLon rewrites) on cache keys to prevent unbounded memory growth - Use
decode_responses=Truein Python — the default returnsbytes, which breaks string comparisons silently - Prefer
BRPOP/BLPOPfor queue consumption — blocking reads beat polling loops - Batch related commands in a pipeline (
.pipeline()in redis-py and Jedis,.pipeline()/.multi()in ioredis) to cut round-trips
Common Mistakes
- Exposing Redis on a public interface without AUTH or TLS — it’s the most-scanned default port on the internet
- Storing large objects (>1 MB) in single keys, causing latency spikes on every read
- Not handling connection failures, causing cascading app errors when Redis is down — degrade to the database, don’t crash
- Forgetting TTLs, leading to
OOM command not allowedoncemaxmemoryfills up - Using
KEYSin production — it scans every key on the single thread and blocks the instance; useSCANinstead
Advanced Examples
Python with pipelines and transactions
import redis
r = redis.Redis(host='localhost', port=6379, decode_responses=True)
# Pipeline: batch multiple commands to reduce RTT
pipe = r.pipeline()
pipe.set('user:1:name', 'Alice', ex=3600)
pipe.set('user:1:email', 'alice@example.com', ex=3600)
pipe.hset('user:1', mapping={'name': 'Alice', 'email': 'alice@example.com'})
results = pipe.execute()
print(results) # [True, True, 1]
# Transaction with MULTI/EXEC — atomic, nothing interleaves
pipe = r.pipeline(transaction=True)
pipe.set('counter', 0)
pipe.incr('counter')
pipe.incr('counter')
results = pipe.execute()
print(results) # [True, 1, 2]
JavaScript with sorted sets and pub/sub
const Redis = require('ioredis');
const redis = new Redis({ host: 'localhost', port: 6379 });
// Sorted sets: leaderboards
await redis.zadd('leaderboard', 100, 'alice', 85, 'bob', 120, 'charlie');
const topPlayers = await redis.zrevrange('leaderboard', 0, 9, 'WITHSCORES');
// ['charlie', '120', 'alice', '100', 'bob', '85']
// Pub/Sub — subscriber needs its own connection
const subscriber = new Redis({ host: 'localhost', port: 6379 });
const publisher = new Redis({ host: 'localhost', port: 6379 });
await subscriber.subscribe('notifications');
subscriber.on('message', (channel, message) => {
console.log(`Received on ${channel}: ${message}`);
});
await publisher.publish('notifications', JSON.stringify({ event: 'user_joined', userId: 42 }));
Java with JedisPool and Lua scripting
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import java.util.List;
public class RedisAdvanced {
private final JedisPool pool;
public RedisAdvanced(String host, int port) {
this.pool = new JedisPool(host, port);
}
public void rateLimit(String userId, int maxRequests, int windowSeconds) {
try (Jedis jedis = pool.getResource()) {
String key = "rate:" + userId;
String luaScript =
"local current = redis.call('INCR', KEYS[1]) " +
"if current == 1 then " +
" redis.call('EXPIRE', KEYS[1], ARGV[2]) " +
"end " +
"return tonumber(current) <= tonumber(ARGV[1])";
Object allowed = jedis.eval(
luaScript, 1, key,
String.valueOf(maxRequests), String.valueOf(windowSeconds)
);
System.out.println("Request allowed: " + allowed);
}
}
public void pipelineExample() {
try (Jedis jedis = pool.getResource()) {
var pipe = jedis.pipelined();
pipe.set("key1", "value1");
pipe.set("key2", "value2");
pipe.sync();
}
}
}
Python async with redis-py
import asyncio
import redis.asyncio as aioredis
async def main():
r = aioredis.Redis(host='localhost', port=6379, decode_responses=True)
# Async string operations
await r.set('async:key', 'value', ex=60)
value = await r.get('async:key')
# Async pipeline
pipe = r.pipeline()
pipe.set('a', 1)
pipe.set('b', 2)
pipe.get('a')
results = await pipe.execute()
print(results) # [True, True, '1']
await r.aclose()
asyncio.run(main())
Variants
| Technology | Driver | Async | Cluster | Notes |
|---|---|---|---|---|
| Python | redis-py | Yes (redis.asyncio) | redis.cluster.RedisCluster | Official client — cluster support is built in since 4.x |
| Python | valkey-py | Yes | Yes | For Valkey, the Linux Foundation fork of Redis 7.2 |
| JavaScript | ioredis | Yes (Promise) | new Redis.Cluster() | Recommended for Node.js |
| JavaScript | node-redis (redis package) | Yes (Promise) | createCluster() | Official Redis client |
| Java | Jedis | No | JedisCluster | Simple blocking API |
| Java | Lettuce | Yes (reactive) | RedisClusterClient | The Spring Data Redis default |
| Go | go-redis | Yes | ClusterClient | Standard Go client |
Production Tips
- Set
maxmemoryand an eviction policy. Without it, Redis grows until the OOM killer picks it.allkeys-lruis the right default for caches;noevictionfor anything durable:
# redis.conf
maxmemory 2gb
maxmemory-policy allkeys-lru
- Monitor with
INFOandSLOWLOG.INFOshows memory, clients, and hit rates — checkkeyspace_hitsvskeyspace_misses;SLOWLOG GETlists commands that exceededslowlog-log-slower-thanmicroseconds:
info = r.info()
print(f"Used memory: {info['used_memory_human']}")
print(f"Hit rate: {info['keyspace_hits'] / (info['keyspace_hits'] + info['keyspace_misses']):.1%}")
-
Use Sentinel for high availability. Sentinel watches the master and promotes a replica on failure; all three clients have Sentinel-aware modes (
redis.sentinel.Sentinel,ioredissentinelsoption,JedisSentinelPool). -
Batch with
PIPELINE,MGET, andMSET. Anything more than a few commands in a row should go in one round-trip:
values = r.mget('key1', 'key2', 'key3')
r.mset({'key1': 'val1', 'key2': 'val2'})
- Use
HSCANinstead ofHGETALLon big hashes.HGETALLcopies the whole hash into memory at once;HSCANiterates in batches:
for field, value in r.hscan_iter('large_hash', count=100):
print(field, value)
- Enable
lazyfreeflags (Redis 4.0+). Async deletion prevents latency spikes when big keys expire or get evicted:
# redis.conf
lazyfree-lazy-eviction yes
lazyfree-lazy-expire yes
lazyfree-lazy-server-del yes
- Inspect storage with
OBJECT ENCODING. Redis switches between compact and full encodings per key — checking them tells you if a data type is bigger than it should be:
encoding = r.object('encoding', 'mykey')
print(f"Encoding: {encoding}") # e.g., listpack, hashtable, int
Troubleshooting
| Error | What it means | How to fix |
|---|---|---|
ECONNREFUSED | Nothing listening on 6379, or firewall/SG blocks it | Check redis-server is up, the port is open, and the client host is allowed |
NOAUTH Authentication required | The instance has a password and you didn’t send one | Pass it in the URL: redis://:password@host:6379 |
WRONGPASS invalid username-password pair | Bad ACL credentials (Redis 6+) | Check the user (default unless customized) and password |
READONLY You can't write against a read only replica | You’re writing to a replica | Point writes at the master, or check whether Sentinel just failed over |
OOM command not allowed | maxmemory reached and policy is noeviction | Raise maxmemory or switch to an eviction policy like allkeys-lru |
MISCONF Redis is configured to save RDB snapshots | Persistence to disk is failing (usually disk full or permissions) | Free disk space or fix dir permissions — writes stay blocked until it’s fixed |
WRONGTYPE Operation against a key holding the wrong kind of value | The key exists with a different type | Check TYPE key — you can’t GET a hash or HGETALL a string |
Connection timed out | Network drop or the server is busy on a slow command | Look at SLOWLOG, raise socket_timeout, and keep commands O(log n) |
See Also
- Redis documentation — commands, data types, ACLs, and client-side caching
- ioredis — cluster, Sentinel, TLS, and retry options for Node.js
- redis-py — connection pools,
redis.asyncio, andredis.cluster - Jedis —
JedisPooled, Sentinel, and cluster modes for Java
Frequently Asked Questions
Should I use Redis as my primary database?
Not by itself. Redis lives in memory — even with RDB snapshots or AOF persistence enabled, it's designed as a fast layer next to a real database, not the system of record. Pair it with PostgreSQL or MySQL for durable data.
How do I handle Redis cluster mode?
Use cluster-aware clients: redis.cluster.RedisCluster in redis-py 4.x (the old redis-py-cluster package is deprecated and merged upstream), new Redis.Cluster() in ioredis, and JedisCluster or Lettuce's RedisClusterClient in Java. Cluster shards data across nodes, so multi-key commands only work inside the same hash tag ({user:1}:name).
What is the difference between EX and PX in Redis?
EX sets expiration in seconds, PX in milliseconds — SET k v EX 60 equals SET k v PX 60000. EXAT/PXAT take absolute timestamps instead of relative ones, and KEEPTTL preserves the existing TTL when you overwrite a key.
How do I implement a rate limiter with Redis?
Use INCR with a window EXPIRE — or the Lua script in Advanced Examples if you need it atomic across drivers:
def rate_limit(r, user_id, max_requests=100, window=60):
key = f"rate:{user_id}"
current = r.incr(key)
if current == 1:
r.expire(key, window)
return current <= max_requests
What is the difference between MULTI/EXEC and PIPELINE?
MULTI/EXEC is a transaction — commands run atomically and nothing interleaves. PIPELINE only batches commands to save round-trips; other clients can interleave between them. Use MULTI when correctness matters (counters, transfers), PIPELINE when you're just loading data fast.
How do I use Redis Streams for message queuing?
XADD appends entries, XREAD/XREADGROUP read them back — Streams give you Kafka-lite semantics with consumer groups, pending entries, and replay:
# Producer
r.xadd('events', {'type': 'order_created', 'order_id': 123})
# Consumer
response = r.xread({'events': '0'}, block=5000, count=10)
for stream, messages in response:
for msg_id, fields in messages:
print(f"Event {msg_id}: {fields}")
How do I connect with AUTH and TLS?
Put both in the URL — rediss://user:password@host:6379 gives you TLS and AUTH in one string. redis-py reads it via Redis.from_url(), ioredis parses it in the constructor (add tls: {} for custom CA certs), and Jedis accepts the rediss:// URI in JedisPool/JedisPooled.
Related Resources
Connect to MySQL
How to connect to MySQL in Python, JavaScript, and Java — with connection pooling, SSL, and prepared statements that hold up in production.
RecipeConnect to PostgreSQL
How to connect to PostgreSQL databases in Python, JavaScript, and Java.
PatternCache-Aside Pattern
Load data into the cache on demand from the backing store. A caching pattern that gives the application full control over what and when to cache.
RecipeRedis Cache Patterns for High-Performance Applications
How to implement cache-aside, write-through, and write-behind patterns with Redis to reduce database load and improve response times
RecipeCaching Strategies: Browser, CDN, Redis & Database
Implement effective caching at every layer of the stack: browser cache, CDN edge, Redis application cache, and database query cache — with invalidation strategies that keep data fresh.
GuideConnection Pooling: Optimize Database Connections for Scale
A practical guide to database connection pooling: sizing pools, handling idle timeouts, detecting leaks, and configuring HikariCP, PgBouncer, and cloud-native pools for maximum throughput.