--- title: "How to Configure Redis as a Cache on Ubuntu 24.04" description: "Install Redis on Ubuntu 24.04, turn it into a secure LRU cache with a memory limit and password, cache data from Python with TTLs and measure the hit rate." category: "Performance Optimization" language: en source: https://cubepath.com/docs/performance-optimization/caching-with-redis-configuration-and-usage updated: 2026-09-25 --- # How to Configure Redis as a Cache on Ubuntu 24.04 Redis is an in-memory key-value store that answers most requests in well under a millisecond, which makes it a common choice for caching database queries, API responses and rendered fragments. In this tutorial you will install Redis on Ubuntu 24.04, configure it as a pure cache with a memory limit and an eviction policy, protect it with a password, use it from a small Python application with the cache-aside pattern, and check how well the cache is working. ## Prerequisites - A server running Ubuntu 24.04 LTS, for example a CubePath VPS, with at least 1 GB of RAM. - A non-root user with `sudo` privileges. - Python 3 (installed by default on Ubuntu 24.04) for the example application in Step 5. ## Step 1 - Installing Redis Ubuntu 24.04 ships Redis 7.0 in its main repository, which is maintained with security updates for the life of the release. Install the server: ```bash sudo apt update sudo apt install redis-server ``` The package starts Redis as the `redis-server` systemd service and enables it at boot. Check that it is running: ```bash sudo systemctl status redis-server --no-pager ``` ```output ● redis-server.service - Advanced key-value store Loaded: loaded (/usr/lib/systemd/system/redis-server.service; enabled; preset: enabled) Active: active (running) since Thu 2026-09-25 10:12:03 UTC; 8s ago ... ``` Send a `PING` with the command-line client: ```bash redis-cli ping ``` ```output PONG ``` Confirm that Redis only listens on the loopback interface. This is the package default and it is what you want unless another server needs to reach it: ```bash sudo ss -ltnp | grep 6379 ``` ```output LISTEN 0 511 127.0.0.1:6379 0.0.0.0:* users:(("redis-server",pid=2841,fd=6)) LISTEN 0 511 [::1]:6379 [::]:* users:(("redis-server",pid=2841,fd=7)) ``` ## Step 2 - Configuring Redis as a cache By default Redis behaves like a database: it has no memory limit and it periodically saves its dataset to disk. For a cache you want the opposite: a fixed memory budget, automatic eviction of old keys when that budget is full, and no disk persistence, because cached data can always be regenerated from the source. Open the main configuration file: ```bash sudo nano /etc/redis/redis.conf ``` Find each of the following directives (use `Ctrl+W` to search) and set them to these values. If a directive is commented out with `#`, remove the `#`: ```ini # Memory budget for cached data maxmemory 256mb # When the budget is full, evict the least recently used keys maxmemory-policy allkeys-lru # Pure cache: no RDB snapshots and no append-only file save "" appendonly no ``` Choose `maxmemory` according to your server. A reasonable starting point is 25 to 50 percent of the RAM that is not used by your application and database. Redis needs some memory on top of `maxmemory` for its own overhead and client buffers, so never set it to the full RAM of the server. The eviction policy decides which keys are removed when the limit is reached: | Policy | Evicts | When to use it | |---|---|---| | `noeviction` | Nothing, writes return an error | Redis used as a database or queue (the default) | | `allkeys-lru` | Least recently used keys | General-purpose cache (recommended) | | `allkeys-lfu` | Least frequently used keys | Caches with a small set of very popular keys | | `volatile-lru` | Least recently used keys that have a TTL | Mixed use: cache keys with TTL next to permanent keys | | `volatile-ttl` | Keys closest to expiring | When TTLs reflect how valuable each key is | If the same Redis instance also stores data you must not lose (for example job queues), do not use an `allkeys-*` policy. Run a separate Redis instance for caching instead. ## Step 3 - Requiring a password Even when Redis only listens on localhost, any local process or compromised web application can talk to it. Set a password with the `requirepass` directive. Generate a long random value first: ```bash openssl rand -base64 32 ``` ```output Xq3m9Wc4oF2m8JdN1hTq0bR7pLkVs6Ey5uZaCgHiYw0= ``` Open the configuration file again: ```bash sudo nano /etc/redis/redis.conf ``` Find the commented `# requirepass foobared` line and replace it with your own value: ```ini requirepass your_redis_password ``` Replace `your_redis_password` with the string you generated. The file is readable only by the `redis` user and root, so the password is not exposed to other users. Restart Redis to apply all the changes from Steps 2 and 3: ```bash sudo systemctl restart redis-server ``` An unauthenticated command is now rejected: ```bash redis-cli ping ``` ```output (error) NOAUTH Authentication required. ``` Use `--askpass` so the password is not stored in your shell history, then confirm the cache settings: ```bash redis-cli --askpass CONFIG GET 'maxmemory*' ``` ```output Please input password: ******** 1) "maxmemory" 2) "268435456" 3) "maxmemory-policy" 4) "allkeys-lru" 5) "maxmemory-samples" 6) "5" 7) "maxmemory-eviction-tenacity" 8) "10" ``` `268435456` bytes is 256 MB, so the limit and the policy are active. ## Step 4 - Working with keys and expiration times Open an interactive session and authenticate: ```bash redis-cli --askpass ``` Store a value with a time to live (TTL) of 300 seconds using the `EX` option, read it back and check how long it has left: ```text 127.0.0.1:6379> SET cache:product:42 '{"id":42,"name":"Widget","price":19.9}' EX 300 OK 127.0.0.1:6379> GET cache:product:42 "{\"id\":42,\"name\":\"Widget\",\"price\":19.9}" 127.0.0.1:6379> TTL cache:product:42 (integer) 295 ``` When the TTL reaches zero Redis deletes the key automatically. Delete a key explicitly when the underlying data changes: ```text 127.0.0.1:6379> DEL cache:product:42 (integer) 1 127.0.0.1:6379> GET cache:product:42 (nil) 127.0.0.1:6379> exit ``` Two habits keep a cache manageable: - **Always set a TTL** on cached keys. Even with an eviction policy, TTLs keep stale data from living forever. - **Use structured key names** such as `cache:product:42` or `cache:api:users:page:1`. A consistent prefix makes it easy to find related keys and to invalidate them. ## Step 5 - Caching database queries from Python The most common way to use Redis as a cache is the **cache-aside** pattern: the application first looks for the value in Redis; on a miss it loads the value from the database, stores it in Redis with a TTL, and returns it. The next request for the same key is served from memory. Install the Python client from the Ubuntu repository: ```bash sudo apt install python3-redis ``` Create a small example script: ```bash nano ~/cache_demo.py ``` Add the following code. The `slow_database_query` function stands in for a real database call that takes half a second: ```python import json import os import time import redis r = redis.Redis( host="127.0.0.1", port=6379, password=os.environ["REDIS_PASSWORD"], decode_responses=True, ) def slow_database_query(product_id): time.sleep(0.5) # simulate a slow SQL query return {"id": product_id, "name": f"Product {product_id}", "price": 19.9} def get_product(product_id, ttl=300): key = f"cache:product:{product_id}" cached = r.get(key) if cached is not None: return json.loads(cached), "hit" product = slow_database_query(product_id) r.set(key, json.dumps(product), ex=ttl) return product, "miss" def update_product(product_id, data): # Write to the database first, then invalidate the cached copy r.delete(f"cache:product:{product_id}") if __name__ == "__main__": for attempt in range(3): start = time.perf_counter() product, status = get_product(42) elapsed_ms = (time.perf_counter() - start) * 1000 print(f"{status:4} {elapsed_ms:7.1f} ms {product}") ``` Pass the password through an environment variable instead of writing it into the code. `read -s` prompts for it without echoing it to the screen: ```bash read -s -p "Redis password: " REDIS_PASSWORD && export REDIS_PASSWORD python3 ~/cache_demo.py ``` ```output miss 503.8 ms {'id': 42, 'name': 'Product 42', 'price': 19.9} hit 0.4 ms {'id': 42, 'name': 'Product 42', 'price': 19.9} hit 0.3 ms {'id': 42, 'name': 'Product 42', 'price': 19.9} ``` The first call pays the full cost of the query; the following calls are answered from Redis in a fraction of a millisecond until the TTL expires or `update_product` deletes the key. In a real application, load the password from your usual secrets mechanism (for example an environment file readable only by the service user) and create the `redis.Redis` client once at startup, because it keeps a connection pool that is reused across requests. Most frameworks have Redis cache backends built in, such as Django's `django.core.cache.backends.redis.RedisCache`, Laravel's `redis` cache driver or Rails' `:redis_cache_store`. They implement the same pattern for you; you only need to point them at `127.0.0.1:6379` with the password. ## Step 6 - Monitoring the cache A cache is only useful if most lookups are hits. Redis counts hits and misses in the `stats` section of `INFO`: ```bash redis-cli --askpass INFO stats | grep -E "keyspace_(hits|misses)|evicted_keys|expired_keys" ``` ```output Please input password: ******** expired_keys:118 evicted_keys:0 keyspace_hits:15732 keyspace_misses:1204 ``` The hit rate is `keyspace_hits / (keyspace_hits + keyspace_misses)`, here about 93 percent. A low hit rate usually means TTLs are too short or keys are not reused. A steadily growing `evicted_keys` means the cache is full and Redis is removing keys before they expire; if that hurts the hit rate, increase `maxmemory`. Check memory usage against the limit: ```bash redis-cli --askpass INFO memory | grep -E "used_memory_human|maxmemory_human|mem_fragmentation_ratio" ``` ```output Please input password: ******** used_memory_human:41.87M maxmemory_human:256.00M mem_fragmentation_ratio:1.12 ``` Other useful checks: - `redis-cli --askpass --stat` prints a live, one-line-per-second summary of keys, memory, clients and requests. - `redis-cli --askpass --bigkeys` scans the keyspace (without blocking Redis) and reports the largest keys of each type. - `SLOWLOG GET 10` inside `redis-cli` lists the ten most recent slow commands. Avoid the `KEYS` command on a production instance: it scans every key in one blocking operation. Use `SCAN` with a pattern, for example `SCAN 0 MATCH cache:product:* COUNT 100`, when you need to find keys. ## Step 7 - Allowing access from another server (optional) If your application runs on a different server, bind Redis to a **private** network address rather than the public one. Open the configuration file: ```bash sudo nano /etc/redis/redis.conf ``` Change the `bind` line to include the private IP of the Redis server, replacing `your_private_ip`: ```ini bind 127.0.0.1 -::1 your_private_ip ``` Restart Redis and allow only the application server through UFW, replacing `app_server_ip` with its private address: ```bash sudo systemctl restart redis-server sudo ufw allow from app_server_ip to any port 6379 proto tcp ``` From the application server, test the connection (install `redis-tools` there to get `redis-cli`): ```bash redis-cli -h your_private_ip --askpass ping ``` ```output Please input password: ******** PONG ``` > Warning: Never expose port 6379 to the internet. Redis traffic is not encrypted by default, and exposed instances are scanned and attacked constantly. For traffic between data centers, use a private network or a VPN such as WireGuard. ## Troubleshooting **`OOM command not allowed when used memory > 'maxmemory'`.** The eviction policy is still `noeviction`, or you use a `volatile-*` policy and there are no keys with a TTL to evict. Set `maxmemory-policy allkeys-lru` and restart Redis. **`WARNING Memory overcommit must be enabled!` in the log.** Redis logs this at startup. It matters mostly when persistence is enabled, because saving forks the process. To follow the recommendation, create `/etc/sysctl.d/99-redis.conf` with the line `vm.overcommit_memory = 1` and apply it with `sudo sysctl --system`. **The service does not start after editing the configuration.** A typo in `redis.conf` stops Redis at startup. Read the exact error with `sudo journalctl -u redis-server -n 30` and fix the line it points to. **Clients get `Connection refused` from another server.** Check that `bind` includes the private IP, that `ss -ltnp | grep 6379` shows Redis listening on it, and that the UFW rule allows the client's address. ## Conclusion You installed Redis on Ubuntu 24.04, configured it as a memory-bounded LRU cache without persistence, protected it with a password, cached a slow query from Python with the cache-aside pattern and learned how to read the hit rate and eviction counters. From here you can plug Redis into your framework's cache backend, store user sessions in it so they are shared across several application servers, or add Redis metrics such as hit rate, memory and evictions to your monitoring system.