Redis Caching Self-Hosting

Self-Hosting Redis 7: When to Use It and How to Connect

By Witchly Team · · 8 min read

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.

PlanRAMBest for
Free Helper2 GBSide projects, dev/staging, small bot caches
The Helper ($2/mo Elite)1 GBTiny production bot, simple session store
The Daemon ($4/mo Elite)2 GBProduction app cache, rate limiter, real session store
The Orchestrator ($8/mo Elite)4 GBHeavy 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:

  1. Applications category → Redis 7
  2. Set the server name (e.g., myapp-cache)
  3. Pick your plan
  4. The Configure step exposes the password (Redis’s requirepass) — set it to something long and random
  5. 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 the default user.
  • 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

  • requirepass is set to a long random password.
  • maxmemory is set to ~70% of your plan’s RAM.
  • maxmemory-policy matches your workload (allkeys-lru for cache, volatile-lru for session store).
  • Your app handles “Redis is unreachable” gracefully — falling back to the source database, not crashing.
  • Connection pooling is configured (ioredis and 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.