How to set up Redis as object cache on a Linux VPS
Technical guide to install Redis on an Ubuntu VPS, configure authentication, persistence, and integrate it with PHP or Node.js applications as an object cache.
In-memory object caching is one of the best cost-benefit optimizations for any web application that runs repeated queries, renders expensive fragments, or serializes sessions. Redis has become the de facto standard — single-threaded, sub-millisecond latency, native support in almost every PHP, Node.js, Python, or Ruby framework.
This tutorial shows how to install Redis 7.x on an Ubuntu 24.04 LTS VPS, apply basic hardening (authentication, local bind, memory limits), and integrate it with PHP and Node.js applications. The focus is using it as an object cache — not as a persistent database or a queue broker, although the base configuration works as a starting point for those too.
Estimated execution time: 20 to 30 minutes, including tests. You finish with Redis running, authenticated, with a discard policy configured and a working integration example.
Prerequisites
A VPS with Ubuntu 24.04 LTS, SSH access as a user with sudo, ~512 MB of free RAM (Redis itself consumes little; the limit will depend on cache volume). A PHP 8.x or Node.js 20.x application running on the same server. An active UFW firewall is recommended.
7.x (default apt on Ubuntu 24.04) 6379 (loopback only) 127.0.0.1 ::1 /etc/redis/redis.conf If you’ve never worked with Redis before, it’s worth knowing the primary types: strings, hashes, lists, sets, and sorted sets. For object caching, 90% of usage is strings (a value serialized as JSON or in the client’s native format). The rest shows up in specific cases like leaderboards, lightweight queues, and counters.
Installing Redis
This section covers installation via the official Ubuntu repository. The packaged version (7.x) is good enough for object caching without reservations — you only need the upstream repository if you want features from very recent versions like Redis Functions or specific modules.
Update the package index and install the server:
sudo apt update
sudo apt install -y redis-serverThe redis-server package installs the binary, the config file at /etc/redis/redis.conf, and the systemd service. The service starts automatically after installation.
Verify that the service is running:
sudo systemctl status redis-server
redis-cli pingThe ping command should return PONG. If it returns a connection error, check the logs with sudo journalctl -u redis-server -n 50 — it’s usually a file permission issue or port conflict.
Configure Redis to start with the system:
sudo systemctl enable redis-serverWithout this, after a VPS reboot Redis stays stopped and your application breaks silently — don’t forget this step in environments that reboot due to kernel updates.
Security and limit configuration
Redis installed straight from apt comes with reasonable defaults for development but insecure for production: no password, persistence enabled, no memory limit. Let’s adjust that.
Edit the configuration file:
sudo nano /etc/redis/redis.confFind and adjust these directives (use Ctrl+W in nano to search):
bind 127.0.0.1 ::1
protected-mode yes
port 6379
requirepass YourStrongRandomPassword32Chars
maxmemory 256mb
maxmemory-policy allkeys-lruThe bind directive ensures Redis only listens on the loopback interface — nobody on the internet reaches port 6379. protected-mode yes is a second layer that refuses external connections even if the bind is relaxed by mistake. requirepass requires authentication on every connection.
Never use a short password or a dictionary word in requirepass. Redis processes over 80,000 attempts per second, and an attacker with local access (through a compromised neighboring container or a vulnerable app) cracks a weak password in seconds. Generate one with openssl rand -base64 32.
Disable persistence if you will only use it as a cache:
Look for the SNAPSHOTTING section in redis.conf and comment out the three save lines:
# save 3600 1 300 100 60 10000
save ""This eliminates the periodic I/O of RDB snapshots, reducing latency and disk wear. If the application rebuilds the cache from the database in a few seconds, persistence is overhead without benefit.
Restart to apply the structural changes:
sudo systemctl restart redis-server
redis-cli -a YourStrongRandomPassword32Chars pingThe PONG response confirms that authentication is working. If you see NOAUTH Authentication required, the password is being read from the config. If you see WRONGPASS, you typed it wrong.
Using -a PASSWORD on the redis-cli command line writes the password into the shell history and into ps. In scripts or recurring use, prefer interactive redis-cli and run AUTH password inside, or export it in an environment variable protected with 600 permissions.
Integration with PHP
PHP uses either the phpredis extension (native C, more performant) or the predis/predis library via Composer (pure PHP, more portable). For object caching in production, phpredis wins by a significant margin.
Install the phpredis extension:
sudo apt install -y php-redis
sudo systemctl restart php8.3-fpmAdjust the PHP-FPM version to match your installation. After restarting, confirm with php -m | grep redis — it should list redis.
Create a test script at /tmp/redis-test.php:
<?php
$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$redis->auth('YourStrongRandomPassword32Chars');
$key = 'user:42:profile';
$cached = $redis->get($key);
if ($cached === false) {
$data = ['id' => 42, 'name' => 'John', 'plan' => 'pro'];
$redis->setex($key, 3600, json_encode($data));
echo "Cache MISS — populated\n";
} else {
echo "Cache HIT: " . $cached . "\n";
}Run it twice: the first call shows MISS, the second HIT. The setex method sets the key with a TTL of 3600 seconds — after that time, the key expires automatically.
Integration with Node.js
In Node.js the recommended client is the official redis (v4+) — asynchronous, maintained by Redis Inc., TypeScript-compatible. Older applications using ioredis remain supported, but for new projects use the official one.
Install the client in your Node project:
npm install redisCreate a cache.js file with the base setup:
import { createClient } from 'redis';
const client = createClient({
url: 'redis://:[email protected]:6379'
});
client.on('error', (err) => console.error('Redis error:', err));
await client.connect();
const key = 'session:abc123';
const cached = await client.get(key);
if (!cached) {
await client.setEx(key, 1800, JSON.stringify({ userId: 42 }));
console.log('MISS — populated');
} else {
console.log('HIT:', cached);
}The URL embeds an empty username and the password — the standard format. In production, read it from an environment variable; never hardcode it in code that goes to the repository.
Verification and monitoring
After configuring and integrating, it’s worth confirming that the cache is actually being used — Redis internal metrics help detect low hit ratios or memory pressure.
Connect to Redis and inspect statistics:
redis-cli -a YourStrongRandomPassword32Chars
> INFO stats
> INFO memory
> DBSIZEKey metrics:
keyspace_hitsvskeyspace_misses: an ideal hit ratio is above 80% for efficient object cachingused_memory_human: memory in use, should stay below the configuredmaxmemoryevicted_keys: if it grows fast, increasemaxmemoryor review the TTLs
If the hit ratio stays below 50% consistently, either the keys are not being reused (TTLs too short) or the application is caching data that is too unique (keys with highly variable parameters).
redis-cli -a PASSWORD --stat shows throughput in real time (ops/s, memory, connected clients) — useful to observe behavior under load without installing anything extra. For persistent dashboards, integrate with your usual metrics system.
Troubleshooting
Error “MISCONF Redis is configured to save RDB snapshots”
This appears when RDB persistence is enabled but the disk is full or has no write permission. If you disabled persistence in Step 05, this should not happen — confirm that save "" was saved and the service was restarted. In an emergency, CONFIG SET stop-writes-on-bgsave-error no allows writes to keep going while you fix the disk.
Connection refused from an application in another Docker container
By default we set bind 127.0.0.1 — this blocks connections coming from other containers. Solutions: use host network in the application container, run Redis also in a container sharing the network, or expand the bind to include the bridge interface (bind 127.0.0.1 172.17.0.1) with a firewall closing the port on the external interface.
Intermittent high latency
It’s usually I/O from the RDB snapshot during bgsave. Check with INFO persistence whether rdb_last_bgsave_status is OK and how long it took. If persistence is required but snapshots cause spikes, consider AOF with appendfsync everysec, which has a more consistent I/O profile.
Next steps
With Redis running as an object cache, it’s worth exploring:
- Layered cache strategies (local memory + Redis + database) with libraries like
symfony/cachein PHP orcache-managerin Node - Redis pub/sub for cache invalidation across multiple application servers
- Master-replica replication for scalable reads when a single instance is no longer enough
- Rate limiting with
INCR+ TTL — it replaces homemade implementations with superior performance
If you’re running this in production and need a dedicated instance with automatic VPS snapshots, consistent latency, and DDoS protection at the edge, Hostini VPS deliver that by default — NVMe storage, network with packet filtering, and daily backups at no extra cost.
Frequently asked questions
What is the difference between using Redis as a cache and as a database?
Redis can serve both roles. As a cache, you set maxmemory + maxmemory-policy (allkeys-lru) so it automatically discards old keys. As a database, you enable persistence (RDB+AOF) and disable eviction. Mixing both in the same instance produces unpredictable behavior — separate them by port or container.
Do I need to configure persistence if I will only use it as a cache?
Not necessarily. A pure cache can run with persistence turned off (save "" in redis.conf) — if it restarts, the cache is rebuilt from the database. Persistence only makes sense when the rebuild is expensive (heavy reports, aggregations). RDB snapshots have periodic I/O cost that can affect latency on a small VPS.
How much throughput can a single Redis process handle?
A single-threaded Redis instance on a modern VPS handles 80-150k ops/s for simple operations (GET/SET) with sub-millisecond latency. The bottleneck is usually the network or the application's serialization, not Redis. To go beyond that, use Redis Cluster or read-only replicas.
How do I prevent Redis from being exposed to the internet by mistake?
Keep bind 127.0.0.1 ::1 in redis.conf, keep protected-mode yes, and never open port 6379 on the firewall. Applications on the same host connect through loopback. If you need remote access, use an SSH tunnel or stunnel — never expose 6379 directly with just a password.
Which maxmemory-policy should I choose for object caching?
For a pure cache, allkeys-lru is the sensible default — it discards the least recently accessed keys when it reaches the limit. allkeys-lfu (Least Frequently Used) works better when the access pattern is stable. Avoid noeviction on a cache: once it fills up, writes start failing and the application breaks.
Do I need to restart Redis after changing redis.conf?
For simple changes (maxmemory, timeout, requirepass), use CONFIG SET via redis-cli — it applies without restarting. CONFIG REWRITE persists it to the file. Structural changes (bind, port, daemonize, persistence) require systemctl restart redis-server.