Who this is for: n8n developers who need to cut latency, reduce API costs, and keep workflows reliable at scale. We cover this in detail in the n8n Performance & Scaling Guide.
Quick Diagnosis
Problem: Repeated nodes (e.g., HTTP Request, Database Query) call the same external endpoint on every execution, inflating latency and API spend.
Featured‑snippet solution: Wrap the expensive call in a cache node that checks Redis (or an in‑memory Map) first; if the key exists, return the cached payload, otherwise fetch, store, and forward the result.
1. When to Use a Cache in n8n ?
If you encounter any database optimization resolve them before continuing with the setup.
| Situation | Recommended Cache | TTL (Time‑to‑Live) |
|---|---|---|
| High‑frequency API calls with rate limits | Redis (remote) | 30 s – 5 min |
| Short‑lived data in a single worker (e.g., cron‑triggered batch) | In‑memory Map (Node.js) |
10 s – 60 s |
| Large payloads (≥ 1 MB) that are reused | Redis (binary safe) | 5 min – 1 h |
| Data that must survive container restarts | Redis (persistent) | 1 h – 24 h |
EEFA note – Never set a TTL longer than the data’s business validity. Stale cache entries cause silent bugs in production workflows.
2. Setting Up Redis for n8n
2.1. Deploy Redis (Docker)
docker run -d \ --name n8n-redis \ -p 6379:6379 \ -e REDIS_PASSWORD=StrongP@ssw0rd \ redis:7-alpine \ redis-server --requirepass $REDIS_PASSWORD
EEFA: In production enable appendonly yes, bind to a private network, and never expose Redis without authentication.
2.2. Add the Redis client library to the n8n container
FROM n8nio/n8n:latest RUN npm install ioredis@5
Re‑build and redeploy the container so the workflow can require('ioredis').
2.3. Create a Cache Helper workflow
- Trigger – Cron (e.g., every minute) or Webhook (incoming request).
- Function – Initialise a Redis client (see snippet below).
- Function – Use subsequent nodes to encapsulate GET, SET, and INVALIDATE logic.
Initialise the Redis client
// Function node: import and configure ioredis
const Redis = require('ioredis');
const redis = new Redis({
host: 'n8n-redis',
port: 6379,
password: $json["REDIS_PASSWORD"], // from env var or secret
});
return [{ json: { redis } }];
The redis object is now available to downstream Function nodes via $json.redis. If you encounter any database migration strategies resolve them before continuing with the setup.
3. Caching an HTTP Request Node
3.1. Cache‑first Function (key generation & lookup)
// Input: {{ $json["url"] }} from previous node
const url = $json["url"];
const cacheKey = `http:${url}`;
const ttlSec = 120; // 2 minutes
// Try Redis first
const cached = await redis.get(cacheKey);
if (cached) {
return [{ json: JSON.parse(cached), cached: true }];
}
If the cache miss occurs, the function passes the key and TTL downstream.
3.2. HTTP Request node (runs only on miss)
Configure the node to Execute Once when cached === false. Use the url field supplied by the previous Function node.
3.3. Store‑after‑fetch Function (write to Redis)
if ($json["cached"]) return []; // skip on hit
const payload = $json["body"]; // HTTP response body
await redis.setex($json["cacheKey"], $json["ttlSec"], JSON.stringify(payload));
return [{ json: payload }];
Downstream nodes receive the same payload shape whether the data came from cache or the remote service. If you encounter any multi instance sync resolve them before continuing with the setup.
EEFA warning – For non‑JSON responses (e.g., binaries) store as a Buffer and decode appropriately.
4. In‑Memory Cache Alternative (No Redis)
When a Redis service isn’t available, an in‑process Map works for low‑traffic scenarios.
4.1. Cache‑lookup Function (using a global Map)
// Initialise the map once per container
if (!global.cacheMap) global.cacheMap = new Map();
const key = `http:${$json["url"]}`;
const ttl = 30 * 1000; // 30 seconds
if (global.cacheMap.has(key) && Date.now() - global.cacheMap.get(key).ts < ttl) {
return [{ json: global.cacheMap.get(key).data, cached: true }];
}
return [{ json: { key, ttl } }]; // miss – forward to HTTP node
4.2. Store‑after‑fetch Function (populate the map)
global.cacheMap.set($json["key"], {
ts: Date.now(),
data: $json["body"],
});
return [{ json: $json["body"] }];
EEFA note – In‑memory caches are cleared on every container restart; consider a warm‑up webhook if you need pre‑populated data.
5. Cache Invalidation Strategies
| Trigger | Implementation | Typical Use‑Case |
|---|---|---|
| Time‑based TTL | EXPIRE in Redis or timestamp check for Map | Predictable data refresh |
| Manual purge | redis.del(key) via a Webhook endpoint | Admin UI clears stale data after a content update |
| Event‑driven | Subscribe to a queue (e.g., RabbitMQ) and call redis.del(key) on change events | Keep cache in sync with source‑of‑truth |
| Cache‑busting query param | Append ?v=timestamp to URL and include timestamp in cache key |
Versioned API responses |
Example: Manual purge webhook
// Webhook node receives { "url": "https://api.example.com/data" }
const key = `http:${$json["url"]}`;
await redis.del(key);
return [{ json: { success: true, purgedKey: key } }];
6. Monitoring & Debugging Cache Performance
6.1. Redis Metrics (Prometheus)
yaml
scrape_configs:
- job_name: 'redis'
static_configs:
- targets: ['n8n-redis:9121']
Watch redis_keyspace_hits_total, redis_keyspace_misses_total, and redis_memory_used_bytes to gauge cache effectiveness.
6.2. n8n Workflow Logging
Add a *Set* node after each cache check:
| Field | Value |
|---|---|
| cacheStatus | {{ $json[“cached”] ? “HIT” : “MISS” }} |
| cacheKey | {{ $json[“cacheKey”] || $json[“key”] }} |
Enable Workflow Execution Log** → Settings → Execution → “Log execution data”. Filter by cacheStatus to spot abnormal miss rates.
EEFA – High miss rates often indicate TTLs that are too short or nondeterministic keys (e.g., missing query‑string normalisation).
7. Full‑Stack Example: Caching a PostgreSQL Query
- Function – Build cache key
const sql = $json["query"]; // e.g. SELECT * FROM users WHERE id = $1
const crypto = require('crypto');
const key = `pg:${crypto.createHash('sha1').update(sql).digest('hex')}`;
return [{ json: { sql, key } }];
2. Redis GET – Same pattern as the HTTP cache‑first function.
3. Postgres node – Executes only on a miss.
4. Redis SETEX – Store rows with ttlSec = 300 (5 min).
5. Downstream nodes – Receive rows regardless of source.
Why SHA‑1? – Guarantees a fixed‑length, deterministic key even for long queries, avoiding Redis key‑length limits.
8. Checklist – Deploying a Robust n8n Cache
- Redis instance: authentication, persistence, network isolation.
- n8n container:
ioredisinstalled, env vars for host/port/password. - Cache key design: deterministic, includes versioning when schema changes.
- TTL selection: matches data freshness requirements.
- Invalidation hooks: webhook or event listener for manual purge.
- Monitoring: Redis hit/miss metrics + n8n execution logs.
- Production testing: simulate cache miss/hit load with
abork6.
Conclusion
Caching in n8n—whether via Redis or an in‑memory Map—turns repetitive, latency‑bound calls into fast, cheap lookups. By designing deterministic keys, picking appropriate TTLs, and wiring explicit invalidation paths, you keep data fresh while protecting downstream services from rate‑limit exhaustion. Coupled with monitoring (Redis metrics + workflow logs), the pattern scales from development sandboxes to production clusters, delivering measurable latency reductions and cost savings without sacrificing reliability.



