---
title: "Shopware 6 & Redis: How Misconfigured Instances Hurt More Than They Help"
description: "Common Redis misconfigurations in Shopware 6 that kill performance instead of improving it – from persistence on cache instances to missing eviction policies – and how to fix them."
canonical_url: "https://kickbyte.de/en/blog/shopware-6-redis-configuration-mistakes"
last_updated: "2026-08-21T17:54:06.951Z"
---

# Shopware 6 & Redis: How Misconfigured Instances Hurt More Than They Help

Redis is the go-to tool in [Shopware 6](/en/services/ecommerce) for moving cache, sessions, and HTTP cache out of the database or file system,
drastically improving performance. In practice, however, we regularly see setups where Redis becomes the bottleneck —
not because Redis is slow, but because it's misconfigured.

This article covers the most common mistakes and how to avoid them.

<youtube-video className="not-prose" params="cc_lang_pref=en&cc_load_policy=1" title="Shopware 6 & Redis: How Misconfigured Instances Hurt More Than They Help" video-id="JThcPGsfQgo">



</youtube-video>

---

## Mistake 1: A Single Redis Instance for Everything

This is the classic. A single Redis instance on port 6379 handles cache, sessions, HTTP cache, cart persistence, number
ranges, locks, increment storage, and the message queue.

**Why this hurts:**

- Cache data competes with session data for available memory. When `maxmemory` kicks in and the eviction policy starts
removing keys, those keys could be sessions or shopping carts — customers get kicked out of checkout or lose their
cart.
- A careless `FLUSHALL` during debugging wipes everything at once — sessions, carts, number range states.
- Different data types require different persistence and eviction strategies. Cache data is volatile; carts and number
ranges are not.

**Solution: At least two separate Redis instances — ephemeral and persistent.**

Since Shopware 6.6.8.0, named Redis connections have been supported directly in `shopware.yaml`:

```yaml
# config/packages/shopware.yaml
shopware:
    redis:
        connections:
            ephemeral:
                dsn: '%env(REDIS_EPHEMERAL)%'
            persistent:
                dsn: '%env(REDIS_PERSISTENT)%'
```

```dotenv
REDIS_EPHEMERAL=redis://redis-cache:6379
REDIS_PERSISTENT=redis://redis-session:6380
```

These connection names can then be referenced across all subsystems — cache, sessions, cart, number ranges, locks,
increment storage.

<table>
<thead>
  <tr>
    <th>
      Instance
    </th>
    
    <th>
      Purpose
    </th>
    
    <th>
      Persistence
    </th>
    
    <th>
      Eviction Policy
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        ephemeral
      </code>
    </td>
    
    <td>
      App cache, HTTP cache, locks, increment
    </td>
    
    <td>
      <strong>
        Off
      </strong>
    </td>
    
    <td>
      <code>
        allkeys-lru
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        persistent
      </code>
    </td>
    
    <td>
      Sessions, carts, number ranges
    </td>
    
    <td>
      <strong>
        On
      </strong>
      
       (RDB + AOF)
    </td>
    
    <td>
      <code>
        volatile-lru
      </code>
    </td>
  </tr>
</tbody>
</table>

Docker Compose excerpt:

```yaml
services:
    redis-cache:
        image: redis:7-alpine
        command: >
            redis-server
              --maxmemory 2gb
              --maxmemory-policy allkeys-lru
              --save ""
              --appendonly no
        volumes: [] # No volume — cache is volatile

    redis-session:
        image: redis:7-alpine
        command: >
            redis-server
              --maxmemory 256mb
              --maxmemory-policy volatile-lru
              --appendonly yes
              --appendfsync everysec
              --save "3600 1" --save "300 100"
        volumes:
            - redis-session-data:/data
```

The Shopware docs recommend `volatile-lru` for the persistent instance — this only evicts keys that have a TTL set,
while keys without expiry (e.g. number ranges) are always preserved. This is the crucial difference from
`allkeys-lru`, which would also delete keys without a TTL.

---

## Mistake 2: Persistence Enabled on the Cache Instance

By default, Redis starts with RDB snapshots enabled (`save 3600 1 300 100 60 10000`). This means Redis periodically
writes the entire dataset to disk — even if it's just volatile cache data.

**Why this hurts:**

- The `BGSAVE` process forks the Redis process. With 2 GB of cache data, an additional 2 GB of RAM is briefly consumed
through copy-on-write. On a VM with 4 GB of RAM, this can trigger an OOM kill.
- Disk I/O from the dump puts unnecessary loads on the system, especially on shared hosting or small VPS instances with
slow disks.
- On restart, Redis loads the dump and restores stale cache entries. This sounds beneficial but is actually
counterproductive: after a deployment, the cache may contain outdated entries that Shopware serves instead of
regenerating.

**Solution:**

```conf
# redis-cache.conf (ephemeral)
save ""
appendonly no
```

Two lines. No RDB, no AOF. The cache instance starts empty — exactly as it should. Shopware rebuilds the cache on
demand.

For the **persistent instance** (sessions, carts, number ranges), persistence is **mandatory**:

```conf
# redis-session.conf (persistent)
appendonly yes
appendfsync everysec
save 3600 1
save 300 100
```

---

## Mistake 3: No maxmemory Set

Without a `maxmemory` limit, Redis grows until the host's memory is exhausted. The Linux OOM killer then terminates the
Redis process — or worse: the MySQL process, because it consumes more RAM.

**Why this hurts:**

- Uncontrolled memory consumption leads to system instability.
- Swap usage makes Redis orders of magnitude slower. Redis and swap don't mix — a Redis instance that swaps is slower
than a cache hitting MariaDB directly.

**Solution:**

Rules of thumb for a typical Shopware shop:

- **Cache instance (ephemeral):** 1–2 GB (depending on product count and category depth)
- **Persistent instance:** 128–256 MB (depending on concurrent users and carts)

Always set it explicitly:

```conf
maxmemory 2gb
maxmemory-policy allkeys-lru  # for ephemeral
```

And monitor it. An `INFO memory` check belongs in your monitoring:

```bash
redis-cli -p 6379 INFO memory | grep used_memory_human
redis-cli -p 6379 INFO memory | grep maxmemory_human
```

---

## Mistake 4: Wrong Eviction Policy

Redis offers several eviction policies. The choice directly affects shop behavior.

**Common misconfigurations:**

- `noeviction` on the cache instance → Redis rejects write operations once `maxmemory` is reached. Shopware throws
errors, the shop goes down.
- `allkeys-random` instead of `allkeys-lru` → Redis deletes random keys instead of the least recently used ones.
Frequently accessed cache entries get deleted while rarely used ones survive.
- `allkeys-lru` on the persistent instance → Redis can delete number range keys that have no TTL. This leads to
duplicate order numbers.

**The correct mapping (per Shopware docs):**

<table>
<thead>
  <tr>
    <th>
      Instance
    </th>
    
    <th>
      Policy
    </th>
    
    <th>
      Why
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      Ephemeral (cache)
    </td>
    
    <td>
      <code>
        allkeys-lru
      </code>
    </td>
    
    <td>
      Least recently used entries are evicted first, new writes always succeed
    </td>
  </tr>
  
  <tr>
    <td>
      Persistent (sessions, carts, number ranges)
    </td>
    
    <td>
      <code>
        volatile-lru
      </code>
    </td>
    
    <td>
      Only keys with a TTL are evicted; keys without TTL (number ranges) are preserved
    </td>
  </tr>
</tbody>
</table>

The difference is subtle but critical: `volatile-lru` protects all keys without an explicit TTL from deletion. Sessions
and carts have TTLs and can be evicted under memory pressure. Number ranges have no TTL and remain safe.

---

## Mistake 5: Incomplete Shopware-Side Configuration

Redis is running, the connection is established — but Shopware doesn't use it for all subsystems because the
configuration was only partially done.

**Typical mistakes:**

- Only the app cache is on Redis, but session handler, cart storage, locks, and increment are forgotten.
- Lock DSN still points to `flock` instead of Redis → breaks in horizontal scaling setups.
- Increment storage stays on MySQL → unnecessary DB load from locking queries.

Complete configuration with named connections (≥ 6.6.8.0):

```yaml
# config/packages/shopware.yaml
shopware:
    redis:
        connections:
            ephemeral:
                dsn: '%env(REDIS_EPHEMERAL)%'
            persistent:
                dsn: '%env(REDIS_PERSISTENT)%'

    cache:
        invalidation:
            delay: 0
            count: 150
            delay_options:
                storage: redis
                connection: 'ephemeral'

    cart:
        redis_url: '%env(REDIS_PERSISTENT)%'

    number_range:
        redis_url: '%env(REDIS_PERSISTENT)%'

    increment:
        message_queue:
            type: redis
            config:
                url: '%env(REDIS_EPHEMERAL)%'
        user_activity:
            type: redis
            config:
                url: '%env(REDIS_EPHEMERAL)%'
```

```yaml
# config/packages/prod/framework.yaml
framework:
    cache:
        default_redis_provider: '%env(REDIS_EPHEMERAL)%'
        pools:
            cache.object:
                adapter: cache.adapter.redis
                tags: true
            cache.http:
                adapter: cache.adapter.redis
                tags: true

    session:
        handler_id: '%env(REDIS_PERSISTENT)%'

    lock:
        main: '%env(REDIS_EPHEMERAL)%'
```

If you don't need the increment storage (no use of admin live statistics), you can set it to `array` and eliminate the
load entirely:

```yaml
shopware:
    increment:
        message_queue:
            type: array
        user_activity:
            type: array
```

---

## Mistake 6: Misunderstanding ?persistent=1

A classic seen in many tutorials:

```yaml
shopware:
    cart:
        redis_url: 'redis://redis:6380?persistent=1'
```

The `?persistent=1` parameter has **nothing to do with data persistence**. It enables **persistent connections** —
connection pooling on the PHP side. This keeps the TCP connection to Redis open across multiple requests instead of
rebuilding it on every request.

**Why the confusion hurts:**

- Developers set `?persistent=1` and assume data persistence is taken care of. The actual Redis persistence (RDB/AOF) is
never configured. After a Redis restart, all carts are gone.
- Conversely, `?persistent=1` is omitted because persistence isn't wanted (cache instance). But connection pooling would
have the most impact there, since cache requests are the most frequent.

**Solution:**

- **Data persistence** is configured on the Redis server side (`save`, `appendonly`)
- **Connection pooling** is configured in the DSN (`?persistent=1`) — beneficial for both instances

---

## Mistake 7: Forgetting Cart Migration

Shopware stores carts in MySQL by default. Switching to Redis requires an explicit migration, otherwise existing carts
are gone after the config change.

```bash
# Migrate carts from MySQL to Redis
bin/console cart:migrate
```

The command reads carts from the database and writes them to the configured Redis instance. It automatically uses the
`redis_url` from `shopware.yaml`. The command is idempotent and can be included in your deployment script.

Without this step, all customers with active carts lose their items after the switch.

---

## Mistake 8: No Monitoring

Redis hums along quietly. Nobody notices the hit rate is at 12%, memory is 98% full, or latency is at 50ms instead of
0.2ms.

**Minimum monitoring:**

```bash
# Check hit rate
redis-cli -p 6379 INFO stats | grep -E "keyspace_hits|keyspace_misses"

# Memory usage
redis-cli -p 6379 INFO memory | grep used_memory_peak_human

# Connected clients
redis-cli -p 6379 INFO clients | grep connected_clients

# Slow log
redis-cli -p 6379 SLOWLOG GET 10
```

The hit rate for the cache instance should be above 80%. If it's lower, either the TTL is wrong, the eviction policy is
misconfigured, or `maxmemory` is too low and Redis is constantly cleaning up.

For a proper setup: Prometheus with `redis_exporter` and a Grafana dashboard. The 30-minute setup investment saves hours
of debugging.

---

## Mistake 9: TCP Configuration Ignored

Redis and Shopware run on the same host but communicate over TCP instead of Unix sockets. Or Redis runs on a separate
host, but TCP keepalive and timeout are left by default.

**When Redis runs on the same host:**

Unix sockets instead of TCP eliminate the entire TCP stack overhead. At high throughput, these are measurable
microseconds per request:

```conf
# redis-cache.conf
unixsocket /var/run/redis/redis-cache.sock
unixsocketperm 770
port 0
```

```dotenv
REDIS_EPHEMERAL=redis:///var/run/redis/redis-cache.sock
```

**When Redis runs remotely:**

```conf
tcp-keepalive 60
timeout 300
tcp-backlog 511
```

Without `tcp-keepalive`, dead connections can remain open for minutes and block the connection pool.

When using a Redis cluster, additionally set in `php.ini`:

```ini
redis.clusters.cache_slots=1
```

This skips the cluster node lookup on every connection.

---

## Summary

<table>
<thead>
  <tr>
    <th>
      Mistake
    </th>
    
    <th>
      Impact
    </th>
    
    <th>
      Fix
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      Single instance for everything
    </td>
    
    <td>
      Eviction deletes sessions/carts
    </td>
    
    <td>
      Separate ephemeral/persistent instances
    </td>
  </tr>
  
  <tr>
    <td>
      Persistence on cache
    </td>
    
    <td>
      OOM kill, stale cache after restart
    </td>
    
    <td>
      <code>
        save ""
      </code>
      
      , <code>
        appendonly no
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      No <code>
        maxmemory
      </code>
    </td>
    
    <td>
      OOM kill, swap usage
    </td>
    
    <td>
      Set explicit limit
    </td>
  </tr>
  
  <tr>
    <td>
      Wrong eviction policy
    </td>
    
    <td>
      Shop errors or data loss
    </td>
    
    <td>
      <code>
        allkeys-lru
      </code>
      
       (cache), <code>
        volatile-lru
      </code>
      
       (persistent)
    </td>
  </tr>
  
  <tr>
    <td>
      Incomplete Shopware config
    </td>
    
    <td>
      Redis running but not fully used
    </td>
    
    <td>
      Configure all subsystems
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        ?persistent=1
      </code>
      
       confused
    </td>
    
    <td>
      No data persistence despite assumption
    </td>
    
    <td>
      Configure server-side persistence
    </td>
  </tr>
  
  <tr>
    <td>
      Cart migration forgotten
    </td>
    
    <td>
      Carts lost after Redis switch
    </td>
    
    <td>
      <code>
        bin/console cart:migrate
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      No monitoring
    </td>
    
    <td>
      Problems go undetected
    </td>
    
    <td>
      Monitor hit rate, memory, slow log
    </td>
  </tr>
  
  <tr>
    <td>
      TCP instead of socket
    </td>
    
    <td>
      Unnecessary overhead
    </td>
    
    <td>
      Unix socket for co-located setups
    </td>
  </tr>
</tbody>
</table>

Redis is a powerful tool — but only when the configuration matches the use case. A misconfigured Redis is worse than no
Redis at all because it introduces an additional point of failure while giving the impression that the performance work
is already done.
