Self-Hosting Redis 7: When to Use It and How to Connect
Table of Contents
Redis is the database you reach for when latency matters more than durability — caching, session storage, rate-limit counters, leaderboards, and the dozen other “I just need to read or write a tiny value really fast” use cases that pop up in every real application.
This post covers what Redis is best at, when not to use it, and how to deploy a production-ready Redis 7 instance on Witchly’s Applications tier.
What Redis actually is
Redis is an in-memory key-value store. Your data lives in RAM (with optional persistence to disk), every operation is sub-millisecond, and the data model is intentionally minimal — strings, lists, sets, sorted sets, hashes, and a few specialty types like streams and HyperLogLogs.
Because everything is in memory and the protocol is dead simple, Redis can handle 100,000+ operations per second on modest hardware. That’s two orders of magnitude faster than typical PostgreSQL/MongoDB lookup workloads.
When Redis is the right tool
Caching
Your app does a slow database query. Cache the result in Redis with a 60-second TTL:
GET user:123:profile → cached → 200 µs response
(every 60s) → DB query → 50 ms response, stored back in Redis
This is by far the most common Redis use case. Five lines of code can drop a page’s load time from 200ms to 5ms.
Session storage
When a user logs in, you generate a session ID and store the session data in Redis:
SET session:abc123 '{"userId": 42, "role": "admin"}' EX 3600
Every request reads the session in <1ms. Way better than reading from your main database on every request.
Rate limiting
Want to allow 100 requests per user per minute? Redis with INCR and EXPIRE:
INCR rate:user:42
EXPIRE rate:user:42 60
GET rate:user:42 → if > 100, reject
Atomic, fast, no concurrency issues.
Leaderboards
Sorted sets are perfect for “top 10 scores”:
ZADD leaderboard 9876 player_alice
ZADD leaderboard 8542 player_bob
ZREVRANGE leaderboard 0 9 WITHSCORES → top 10
Updates and queries are O(log N) — fast even with millions of entries.
Pub/sub messaging
Lightweight real-time messaging between processes (Discord bot shards, web app workers, etc.) without standing up a full message broker:
SUBSCRIBE notifications:guild:123
PUBLISH notifications:guild:123 "user joined"
Not a replacement for Kafka/RabbitMQ for serious workloads, but for “tell my web app that a Discord event happened” it’s perfect.
Distributed locks
When two processes might try to do the same thing at the same time:
SET lock:resource:42 owner_id NX EX 30 # set if not exists, expire 30s
Only one process gets the lock. Standard pattern for cron-job deduplication, quota enforcement, etc.
When Redis is the wrong tool
Primary database
Don’t store your “real” data in Redis. RAM is volatile, persistence is configurable but not transactional, and you’ll regret making Redis the source of truth for anything that needs to be correct.
Use PostgreSQL or MongoDB for primary data; use Redis as a fast cache in front of them.
Anything that needs ACID transactions
Redis has transactions (MULTI/EXEC) but they’re not the same as Postgres transactions. No rollback on failure, no isolation levels. If you need “either all of these things succeed or none do,” use a real DB.
Anything bigger than your RAM
Redis keeps everything in memory by default. If your dataset doesn’t fit in RAM, performance falls off a cliff (or Redis OOMs and dies). Plan for ~70% of your plan’s RAM as usable Redis space; the rest goes to OS, the Redis process itself, and headroom.
Plan recommendations
Redis 7 is available on both Free and Elite Application tiers — it’s lightweight enough that the free tier can host meaningful Redis workloads.
| Plan | RAM | Best for |
|---|---|---|
| Free Helper | 2 GB | Side projects, dev/staging, small bot caches |
| The Helper ($2/mo Elite) | 1 GB | Tiny production bot, simple session store |
| The Daemon ($4/mo Elite) | 2 GB | Production app cache, rate limiter, real session store |
| The Orchestrator ($8/mo Elite) | 4 GB | Heavy caching workloads, leaderboards with millions of entries |
Most projects don’t need much Redis RAM — caches and sessions are small. If you find yourself needing more than 4 GB, you’re probably using Redis in a way that should be a real DB.
Deploying
In the dashboard’s deploy wizard:
- Applications category → Redis 7
- Set the server name (e.g.,
myapp-cache) - Pick your plan
- The Configure step exposes the password (Redis’s
requirepass) — set it to something long and random - Deploy
After provisioning, the Console tab streams Redis’s startup. When you see:
The server is now ready to accept connections on port 6379
(or whichever port the Applications port range gives you), it’s live.
The connection string
Standard Redis URI:
redis://default:[email protected]:6379/0
- default — Redis 6+ uses ACL-based users; the built-in user is named
default. The egg ships with a single password applied to thedefaultuser. - password — what you set in the Configure step
- host / port — from the dashboard Network tab
- 0 — the database number (Redis has 16 databases by default, numbered 0-15; most apps use 0)
Drop this URI into your app’s environment variable.
Configuration knobs worth knowing
The egg ships a sane redis.conf. The key sections:
Authentication
requirepass your-password
Set via the Configure step. Never run public Redis without a password — internet-facing, password-less Redis is one of the most-exploited misconfigurations in the world.
Persistence
Redis offers two persistence modes:
- RDB (snapshots): Periodic point-in-time saves to disk. Default. Recovers fast, may lose seconds of data on crash.
- AOF (append-only file): Every write is logged. Slower, but you lose at most a second of data.
Default config uses RDB with reasonable snapshot intervals:
save 3600 1 # save after 1 change in 3600s (idle backup)
save 300 100 # save after 100 changes in 300s (active workload)
save 60 10000 # save after 10,000 changes in 60s (heavy workload)
For most workloads, RDB is fine. Switch to AOF only if you literally cannot afford to lose any data.
Max memory + eviction policy
maxmemory 1gb
maxmemory-policy allkeys-lru
When Redis hits the memory cap, it starts evicting. allkeys-lru evicts least-recently-used keys regardless of TTL — sensible default for caching workloads. Other useful policies:
volatile-lru— only evicts keys with TTLs set (don’t touch keys without TTLs)allkeys-lfu— least-frequently-used (better than LRU when access patterns are skewed)noeviction— refuse writes when full (only use if Redis is your primary store and data must not be evicted)
For a cache, allkeys-lru is correct. For a session store, volatile-lru (so non-session data isn’t accidentally evicted).
After editing redis.conf via the File Manager, restart the server in the Console tab.
Connecting from your stack
Node.js (ioredis)
import Redis from 'ioredis'
const redis = new Redis(process.env.REDIS_URL!)
await redis.set('user:42:profile', JSON.stringify({ name: 'Alice' }), 'EX', 3600)
const cached = await redis.get('user:42:profile')
ioredis handles connection pooling, reconnects, pipelining — it’s the right choice for any Node app.
Python (redis-py)
import redis
r = redis.from_url(os.environ["REDIS_URL"])
r.set("user:42:profile", json.dumps({"name": "Alice"}), ex=3600)
cached = r.get("user:42:profile")
Go (go-redis)
opt, _ := redis.ParseURL(os.Getenv("REDIS_URL"))
rdb := redis.NewClient(opt)
rdb.Set(ctx, "user:42:profile", `{"name":"Alice"}`, time.Hour)
Discord bot example (caching guild settings)
async function getGuildConfig(guildId: string) {
const cached = await redis.get(`guild:${guildId}:config`)
if (cached) return JSON.parse(cached)
const fromDB = await db.guild_configs.findOne({ guildId })
await redis.set(`guild:${guildId}:config`, JSON.stringify(fromDB), 'EX', 600)
return fromDB
}
DB hit on cache miss, cache hit on every subsequent request for 10 minutes. Five lines of code, dramatic latency drop on hot paths.
Backups
Unlike Postgres or MongoDB, Redis is often used in ways where backups don’t matter — caches are inherently disposable. If your Redis is just a cache, you don’t need to back it up. A cold Redis post-restart simply rebuilds from your primary database the next time the app reads each key.
If you’re using Redis for things that do matter (session store, leaderboard, rate-limit counters that affect billing), then back up the RDB snapshot file:
cp /home/container/dump.rdb /home/container/backups/dump-$(date +%Y%m%d).rdb
Schedule that daily via the Schedules tab. Combine with the dashboard’s auto-backup for the whole-disk fallback.
For most projects: skip backups, treat Redis as ephemeral, design the app so cache misses are correct (just slower). This is far simpler than maintaining backup hygiene for two databases.
Production checklist
-
requirepassis set to a long random password. -
maxmemoryis set to ~70% of your plan’s RAM. -
maxmemory-policymatches your workload (allkeys-lrufor cache,volatile-lrufor session store). - Your app handles “Redis is unreachable” gracefully — falling back to the source database, not crashing.
- Connection pooling is configured (
ioredisand most clients pool by default — verify yours does). - If you care about persistence, RDB or AOF is enabled.
- If you don’t care about persistence, you’ve explicitly designed the app to survive cache loss.
Common pitfalls
”Redis got OOMKilled”
You wrote past maxmemory and the OS killed the process before Redis could evict. Always set maxmemory lower than your plan’s RAM (70% is the rule of thumb) so Redis has time to evict before the OS panics.
”Cache stampede”
When a popular key expires, every concurrent request misses the cache simultaneously and stampedes the source DB. Solutions:
- Stale-while-revalidate: serve the expired value while one process refreshes it
- Locking: the first miss acquires a lock; everyone else waits for the refresh
- Probabilistic early expiration: randomise expiration so all your caches don’t expire at exactly the same moment
For most small apps, you don’t need to worry about this — but if you ever see DB load spikes correlated with cache expiry, you’ve found your culprit.
”Connection refused” after deploying
The Configure-step password didn’t get applied, or the connection URL is using the wrong password. Re-check both.
Memory leak via untracked keys
Set TTLs on cache keys (SET key value EX 600). Keys without TTLs accumulate forever. The default eviction policy can clean them up, but designing them to expire is cleaner.
Wrapping up
Redis 7 on Witchly is a small, fast, dollars-a-month addition to almost any project. Pair it with Postgres or MongoDB and you’ve got the canonical “real DB + cache layer” stack that powers most modern web apps.
Deploy from the Applications pricing page, or follow the Redis setup doc for the dashboard walkthrough.