...

How to Install Redis on a VPS: Security, Memory Limits, and Data Persistence

Martin Klein

Reading time 1 minute

Below is a concise procedure for configuring Redis on a VPS, covering installation, secure access, memory limits, eviction policy selection, and verification of data persistence after a restart.

  1. Install Redis on Ubuntu and make sure the service is running:
 sudo apt update
sudo apt install -y redis-server
sudo systemctl enable --now redis-server

Verify:

 redis-cli ping

Expected response:

 PONG
  1. Do not expose Redis to the entire internet. If the application runs on the same VPS, keep Redis bound to localhost:
 bind 127.0.0.1
protected-mode yes

If the application is hosted on another server, use a private network if possible and also restrict access to port 6379 through the firewall.

  1. Configure a dedicated ACL user for the application instead of using the unrestricted default user. Grant the user access only to the commands and key patterns that the application actually requires.
  2. Set a memory limit using maxmemory, leaving headroom for the operating system and persistence. For example, on a VPS with 4 GB of RAM, approximately 2 GB for Redis may be a reasonable starting point:
 maxmemory 2gb

However, the exact value depends on the workload, persistence configuration, and other processes running on the server.

  1. Choose a maxmemory-policy that matches the role of Redis. For a standard cache, suitable eviction policies often include:
 allkeys-lru

or:

 allkeys-lfu

If Redis is used as a data store and keys must not be removed automatically, a safer option to consider is:

 noeviction
  1. Choose a persistence strategy. Use RDB for periodic snapshots or AOF to persist operations more frequently. If Redis is used only as a recoverable cache, persistence can sometimes be disabled entirely.
  2. Create a test key:
 redis-cli SET persistence:test "redis-survived"

After configuring RDB or AOF, restart Redis:

 sudo systemctl restart redis-server

Then check:

 redis-cli GET persistence:test

If the key is returned, persistence is working as expected for the selected setup.

  1. To troubleshoot memory issues, check:
 redis-cli INFO memory
redis-cli INFO stats
redis-cli CONFIG GET maxmemory
redis-cli CONFIG GET maxmemory-policy

Pay particular attention to used_memory, the current maxmemory value, and the evicted_keys counter.

If Redis starts automatically, accepts only permitted connections, the application uses a dedicated user, maxmemory and the eviction policy match the intended use case, and data is restored as expected after a restart, the basic configuration can be considered ready for continued use.

What We’ll Cover

Redis is often first used for a simple task: deploy the service, connect an application, and store cached data in it. But more serious questions soon arise: how much memory should be allocated to Redis, what happens when RAM fills up, how can you avoid exposing Redis to the entire internet, and will the data survive a routine restart?

In this article, we’ll walk through this entire process on a single VPS. We’ll install Redis, configure secure access and ACLs, set maxmemory, compare eviction policies, and examine when Redis functions as a standard cache and when it becomes part of a persistent data store.

We’ll then move on to persistence: compare RDB and AOF, create test keys, restart Redis, and verify that the data has been preserved. Finally, we’ll look at how to diagnose memory shortages and which settings to double-check before deploying to production.

How the Redis Server Will Be Set Up

Now that we have a high-level plan, let’s examine the specific setup we will build and the parameters that matter most.

In the basic setup, Redis will run as a separate service on a single Ubuntu VPS. The application will connect to it over TCP, send commands, and retrieve data from RAM. Redis behavior will then depend on its configuration: it can be used as a temporary cache that automatically evicts older keys, or persistence can be enabled so that it can be used as a database, in which case the data must be handled much more carefully.

This means that simply starting redis-server is not enough. Four things must be determined in advance: who can connect, how much memory Redis is allowed to use, which keys can be evicted when RAM is low, and whether the data must survive a restart.

What We Will Install and Check

We will need two main components on the server:

  • redis-server—the Redis service itself;
  • redis-cli—the command-line client used to test the connection and execute commands.

After installation, we will check not only for the response:

 PONG

but also more practical aspects.

We will check:

  • Whether Redis starts via systemd;
  • Whether it listens only on the required interface;
  • Whether authentication works;
  • Whether ACLs are applied;
  • What maxmemory value is configured;
  • Which maxmemory-policy is selected;
  • Whether RDB or AOF is enabled;
  • Whether data persists after a restart;
  • What happens as Redis approaches the RAM limit.

In other words, a standard PING will be only the first check, not the final one.

How the Application Connects to Redis

Redis typically accepts connections on port:

 6379

If the application runs on the same VPS, the simplest configuration is:

In this case, Redis does not need to be exposed externally.

The connection string might look like this:

 redis://app_user:[email protected]:6379

If the application and Redis are hosted on different VPS instances, it is better to use a private network:

In this setup, Redis listens on the private interface, and the firewall allows connections only from the application VPS.

However, the network itself is only one layer of protection. Later, we will also configure ACLs so that the application connects using a dedicated user account and cannot execute unnecessary commands.

How Redis as a Cache Differs from Redis as a Data Store

This is one of the key points.

When Redis is used as a cache, the data is considered reproducible. For example, the application can retrieve it again from the primary database or an API.

In this case, losing some keys is not inherently catastrophic.

In this setup, using TTL and eviction is standard practice:

If Redis reaches its memory limit, old or infrequently used keys can be evicted, and the application can recreate them as needed.

However, Redis can also store data that cannot be lost without consequences.

For example:

  • Queues;
  • Counters;
  • Session state;
  • Temporary workflow state;
  • Certain rapidly changing data structures that the application considers important.

This requires a different approach.

Automatically evicting arbitrary keys becomes risky, while persistence plays a much more important role.

In simplified terms:

This distinction will then determine both the maxmemory-policy and the choice between RDB and AOF.

Key configuration settings

Rather than reviewing the entire redis.conf file, we will focus on several groups of parameters.

Network settings determine where Redis accepts connections:

 bind
protected-mode
port

Security and user settings control authentication and permitted commands:

 user
ACL

Memory:

 maxmemory
maxmemory-policy

Persistence:

 save
appendonly
appendfsync

The rest of the configuration will focus on these parameters:

ParameterPurposeWhat we will configure
bind Interfaces on which Redis accepts connectionslocalhost or a private IP address
protected-mode Additional protection against insecure network configurationsKeep enabled
ACLUsers and permitted commandsCreate a dedicated application user
maxmemory Maximum amount of RAM available to RedisSet with sufficient headroom for the OS
maxmemory-policy What to do when memory is fullChoose based on whether Redis is used as a cache or a data store
save RDB snapshot rulesConfigure and verify
appendonly Enabling AOFCompare with RDB
appendfsync AOF synchronization frequencyReview always, everysec, and no 

The architecture is now clear: Redis will run as a separate service, the application will have restricted access, memory usage will be controlled through maxmemory, and data durability will be managed through the selected persistence mechanism.

Choosing a Redis Version and VPS Configuration

Next, you need to determine how much computing capacity Redis actually requires. With Redis, the primary consideration is usually memory rather than CPU: data is stored in RAM, so the amount of available memory directly determines how many keys the server can hold at once.

We will use Redis 8.x on Ubuntu 24.04 LTS and a small VPS with sufficient resources for hands-on testing, persistence, and controlled workloads.

Resource Requirements for a Small Redis Instance

A small Redis instance does not require a powerful server.

If it is used as a cache, session store, rate limiter, or store for a small volume of operational data, a suitable starting configuration is:

  • 1–2 vCPUs
  • 2–4 GB RAM
  • 20–40 GB SSD

In most small-scale use cases, CPU is not the first bottleneck. Redis processes commands very quickly, and the main constraints are typically:

  • Data volume;
  • Number of requests;
  • Value size;
  • Number of connections;
  • Persistence;
  • Network load.

Redis can run with 1 GB of RAM in a test environment, but the available headroom will quickly run out, especially if AOF or RDB persistence is enabled and other processes are running on the server.

For this hands-on article, we will therefore use a slightly more generous configuration so that we can comfortably experiment with memory and persistence.

Why RAM Is Especially Important for Redis

Redis stores its working data in RAM.

This is what makes Redis so fast, but it also makes RAM the server’s primary resource.

In simplified terms:

However, memory usage is not simply the combined size of all values.

Redis also uses RAM for:

  • Keys;
  • Metadata;
  • Internal structures;
  • Allocator overhead;
  • Client buffers;
  • Replication buffers, if used;
  • Temporary memory required for certain operations;
  • Copy-on-write during a fork for an RDB snapshot or AOF rewrite.

Therefore, if a dataset occupies 2 GB, for example, you cannot automatically assume that a VPS with 2 GB of RAM will be sufficient.

Additional memory headroom is required.

This is particularly important when persistence is enabled: while creating a snapshot or rewriting the AOF, Redis may temporarily use more memory than it does during normal operation.

For this reason, when we configure maxmemory later, we will not set it to the full amount of available RAM. Instead, we will reserve some memory for the operating system and Redis background operations.

How to Estimate Memory Requirements

For a rough estimate, start with three figures:

For example, suppose the application is expected to store approximately 1.2 GB of data.

Allow an additional ~0.5–0.8 GB for Redis data structures and growth.

You should also reserve memory for the operating system and persistence.

In this case, a VPS with 2 GB of RAM would be too constrained, while 4 GB would provide significantly more headroom.

It is also important to remember that Redis rarely stays at its initial size. The cache grows, new key patterns emerge, and the number of sessions or counters increases.

Therefore, when choosing a VPS, consider not only the current data volume but also the expected growth.

In simplified terms:

We will calculate the exact maxmemory limit separately later.

Configuration We Will Use

For our test environment, we will use:

  • Ubuntu 24.04 LTS;
  • Redis 8.x;
  • 2 vCPU;
  • 4 GB RAM;
  • 40 GB SSD;
  • one standalone Redis instance;
  • systemd for service management.

This is sufficient to:

  • Test ACLs;
  • Test maxmemory;
  • Trigger eviction;
  • Enable RDB;
  • Enable AOF;
  • Perform a restart and a reboot;
  • Verify that data is preserved;
  • Observe behavior under low-memory conditions.

We will deliberately avoid allocating all 4 GB to Redis.

Later, we will set maxmemory well below the total amount of RAM, leaving some memory available for the system.

For reference:

ResourceMinimum Test VPSRecommended Small ServerConfiguration Used in This Article
CPU1 vCPU2+ vCPU2 vCPU
RAM1–2 GB4+ GB4 GB
Disk10–20 GB20–40+ GB SSD40 GB SSD
SwapAs a safeguard onlySmall reserveTo be tested separately
PurposeLab / testingSmall cache or servicePractical test environment

The minimum configuration is suitable for testing, but a production Redis deployment should have additional RAM capacity. This provides headroom for data growth, background operations, and persistence.

VPS Check Commands

Before installation, let’s inspect the server we’ve been provisioned.

Ubuntu version:

 lsb_release -a

or:

 cat /etc/os-release

CPU count:

 nproc

For more detailed information:

 lscpu

Next, RAM:

 free -h

Pay particular attention to:

 total
available

Swap:

 swapon --show

Disks:

 lsblk

Free disk space:

 df -h

It is also useful to check the current system load beforehand:

 uptime

And the main processes:

 ps aux --sort=-%mem | head

This will help determine how much RAM the system is already using before Redis is installed.

The main commands can be kept in a quick reference table:

What to checkPurposeCommand
UbuntuCheck the OS versionlsb_release -a 
CPUCheck the available CPUsnproc 
RAMCheck total and available memoryfree -h 
SwapCheck virtual memoryswapon --show 
DiskCheck storage deviceslsblk 
Free disk spaceAssess available space for RDB/AOFdf -h 
Current loadAssess the VPS status before installing Redisuptime 

We now understand what resources are available to Redis and how much capacity will remain for the operating system. The next step is to install Redis on Ubuntu, start the service, and verify the first PONG response.

Installing Redis on Ubuntu

Choosing an Installation Method

For Ubuntu, the most convenient option is to install Redis via apt.

This installs Redis as a standard system service, provides a standard configuration file, and automatically integrates it with systemd. For a typical VPS, this approach is simpler and more reliable than building Redis manually from source.

The basic installation process is as follows:

 sudo apt update
sudo apt install -y redis-server

Redis can then be started as a service.

Which Components Are Installed

Main package:

 redis-server

It contains the Redis server itself and the service files required to run it.

For command-line access, we will also need:

 redis-cli

We will use it to:

  • Send PING commands;
  • Read and write keys;
  • Test authentication;
  • View INFO output;
  • View CONFIG settings;
  • Test persistence and eviction.

You can check the versions as follows:

 redis-server --version
redis-cli --version

This immediately confirms that both the server and the client are installed.

How the Redis Service Works

After installation, Redis is managed by systemd.

Check the service status:

 sudo systemctl status redis-server

Quick check:

 sudo systemctl is-active redis-server

Expected output:

 active

To have Redis start automatically after a reboot:

 sudo systemctl enable redis-server

Basic service management commands:

 sudo systemctl start redis-server
sudo systemctl stop redis-server
sudo systemctl restart redis-server

If the service fails to start after a configuration change, check the systemd logs first:

 sudo journalctl -u redis-server -n 100 --no-pager

This command will be particularly useful later after making changes to redis.conf.

Where Configuration, Data, and Logs Are Stored

The main Redis configuration file is usually located at:

 /etc/redis/redis.conf

We will use this file to configure the following:

  • bind;
  • protected-mode;
  • ACL;
  • maxmemory;
  • maxmemory-policy;
  • RDB;
  • AOF.

The working directory containing persistence files is usually:

 /var/lib/redis

RDB snapshots and AOF files may be stored here.

In a package-based installation, logs may be sent to the systemd journal. If file-based logging is used, the path is specified by the logfile parameter in redis.conf.

You can check the setting with:

 grep "^logfile" /etc/redis/redis.conf

For diagnostics with systemd:

 sudo journalctl -u redis-server

The main locations to check are:

 /etc/redis/redis.conf
/var/lib/redis

systemd journal

Next, we move on to our favorite part: hands-on practice.

Hands-on: Installing and Starting Redis

First, update the package index:

 sudo apt update

Install Redis:

 sudo apt install -y redis-server

Check the version:

 redis-server --version

Check the client version as well:

 redis-cli --version

Now start Redis:

 sudo systemctl start redis-server

Enable it to start automatically at boot:

 sudo systemctl enable redis-server

Check the service status:

 sudo systemctl status redis-server

For a quick check:

 sudo systemctl is-active redis-server

If everything is working correctly, proceed to Redis itself.

Run:

 redis-cli ping

Expected response:

 PONG

This means that redis-cli successfully connected to the local Redis server and the server processed the command correctly.

You can also open the interactive Redis CLI:

 redis-cli

Then run:

 PING

The response should again be:

 PONG

To exit, run:

 QUIT

Key commands at this stage:

TaskCommand
Install Redissudo apt install -y redis-server 
Start the servicesudo systemctl start redis-server 
Enable automatic startupsudo systemctl enable redis-server 
Check the service statussudo systemctl status redis-server 
Test Redisredis-cli ping 
View logssudo journalctl -u redis-server -n 100 --no-pager 
Open the configuration filesudo nano /etc/redis/redis.conf 

The basic installation is now complete: Redis is running, systemd is managing it, and redis-cli can connect to it locally.

Understanding Basic Redis Security Settings

Redis is already installed and responding to PING, but it should not be left in a “set it and forget it” state. The next step is to restrict network access and determine which interfaces the service should listen on.

Redis has a very simple network interface, so a bind or firewall misconfiguration can quickly expose a local service to external access. It is best to configure the network from the outset so that Redis is accessible only to the hosts that actually need it.

Why Redis Should Not Be Exposed to the Internet Indiscriminately

Redis is not designed to simply listen on port 6379 on all network interfaces and accept connections from any client.

If the port is accessible from the internet, several risks arise:

  • Credential brute-force attempts;
  • Scanning and automated attacks;
  • Unnecessary load;
  • The risk of an ACL error being exploited;
  • The risk of accidentally granting overly broad permissions.

Even if authentication is already configured, there is still no reason to make Redis publicly accessible.

A better approach is to implement access in multiple layers:

Each layer serves a specific purpose, and together they provide a more predictable access model.

How bind Works

The bind parameter specifies which local interfaces Redis listens on for incoming connections.

For example:

 bind 127.0.0.1

means that Redis is accessible only from the VPS itself.

To also listen on the server’s private interface:

 bind 127.0.0.1 10.0.0.10

Redis now accepts connections:

  • Via localhost;
  • Via the private IP address 10.0.0.10.

It is important not to confuse this with client filtering.

bind does not define a list of allowed source IP addresses. It only specifies the server’s own addresses on which Redis accepts connections.

Access for specific clients is restricted using a firewall.

Why protected-mode Is Needed

The protected-mode setting provides an additional safeguard against overly permissive configurations.

In a basic configuration:

 protected-mode yes

it is advisable to leave it enabled.

It helps prevent an obviously insecure scenario in which Redis is accessible through an external interface without adequate protection, allowing passwordless access only from local interfaces.

However, protected-mode should not be considered a complete replacement for ACLs or a firewall.

It is better viewed as another layer of protection: protected-mode ≠ ACL ≠ firewall

All three mechanisms serve different purposes.

How to Restrict Network Access

If the application is running on another server, the bind directive alone is not sufficient.

Assume the following:

Redis VPS:       10.0.0.10

Application VPS: 10.0.0.20

In redis.conf, you can keep the following settings:

 bind 127.0.0.1 10.0.0.10
protected-mode yes
port 6379

Redis is now listening on the private network interface.

However, you should also restrict access to the port using UFW:

 sudo ufw allow from 10.0.0.20 to any port 6379 proto tcp

Check the configuration:

 sudo ufw status numbered

Unless necessary, do not add the following rule:

 sudo ufw allow 6379/tcp

This rule would open the port to all source addresses.

If your cloud provider offers a separate network firewall or security group, it is best to apply the same restriction there as well.

The result is:

Let’s move on.

When localhost Is Sufficient

If the application and Redis are running on the same VPS, the simplest option is usually the best:

In this case, you can keep the following settings in redis.conf:

 bind 127.0.0.1
protected-mode yes

No externally accessible port is needed at all.

This means fewer:

  • Network rules;
  • Routes;
  • Points of failure;
  • Opportunities for misconfiguration.

Unless there is a specific reason to connect to Redis remotely, localhost is sufficient.

Hands-on: keeping Redis accessible only where needed

First, open the configuration file:

 sudo nano /etc/redis/redis.conf

For a local setup, keep the following settings:

 bind 127.0.0.1
protected-mode yes
port 6379

Save the file and restart Redis:

 sudo systemctl restart redis-server

Verify that the service is running:

 sudo systemctl is-active redis-server

Now check which addresses it is listening on:

 sudo ss -lntp | grep 6379

For localhost, the expected address is:

 127.0.0.1:6379

If access over a private network is required, the configuration may look like this:

 bind 127.0.0.1 10.0.0.10
protected-mode yes
port 6379

After restarting Redis, check again:

 sudo ss -lntp | grep 6379

You should now see both localhost and the private IP address.

From the application VPS, you can test the TCP port itself:

 nc -vz 10.0.0.10 6379

If the connection succeeds, the next level of verification is authentication and ACLs.

Here is a brief comparison of the options:

Scenariobind FirewallWhen to use
Localhost127.0.0.1 Port 6379 blocked externallyThe application and Redis are on the same VPS
Private network127.0.0.1 10.0.0.10 Only the application’s IP address is allowedThe best option for separate VPS instances on the same private network
Public networkLocalhost + the required public interfaceOnly the trusted public IP addressOnly if a private network is not architecturally available

The network perimeter is now configured: Redis listens only on the required interfaces, while the firewall further restricts the source of incoming connections.

The next step is to configure authentication and ACLs so that even a client that can reach port 6379 cannot gain access without the correct account and permission to use the allowed commands.

Configuring Redis Authentication

Why a Firewall Alone Is Not Enough

A firewall handles network-level access: it determines who can reach port 6379.

But once the connection is established, Redis needs to know exactly what this client is allowed to do.

Without authentication and ACLs (not to be confused with cloud ACLs; here, ACL refers specifically to Redis’s internal mechanism), any permitted network client may have overly broad privileges.

For example, an application could accidentally execute an administrative command or access keys it does not need.

A proper setup therefore looks like this:

The firewall restricts connection sources, while ACLs restrict what clients can do within Redis.

How ACLs Work

Redis ACLs are built around users.

For each user, you can specify:

  • Whether the user is enabled;
  • Which password is used;
  • Which keys the user can access;
  • Which commands are allowed;
  • Which commands are prohibited.

For example, you can create an application user that has access only to keys with the following prefix:

 app:*

and only to standard read and write operations.

Conceptually:

The user does not need access to administrative commands.

This is more convenient than granting the application full access to the entire Redis instance.

Why an Application Should Not Use the Default User Without Restrictions

Redis has a default user.

If it is left enabled without appropriate restrictions, the application may gain excessively broad privileges. This is especially problematic when using a simple setup with a single shared password and no user separation.

It is much better to create a dedicated application user and explicitly grant it only the permissions it needs.

For example:

 app_user

will be used exclusively by the application.

The default user can either be disabled or reserved for a limited administrative use case if it is genuinely needed.

The resulting setup is as follows:

Next, we will move on to the commands.

Commands the Application Needs

The exact set depends on how Redis is used.

For a typical cache, the application often needs:

  • GET;
  • SET;
  • DEL;
  • EXPIRE;
  • TTL;
  • MGET;
  • MSET;
  • EXISTS.

If counters are used:

  • INCR;
  • DECR.

If lists, sets, or hashes are used, the command set expands.

For example:

 HGET
HSET
LPUSH
RPOP
SADD
SMEMBERS

However, there is no reason to allow all Redis commands in advance “just in case.”

The fewer permissions granted, the easier it is to control the consequences of an application error.

Hands-on: Creating a Dedicated User

Open the Redis configuration file:

 sudo nano /etc/redis/redis.conf

For this example, create a user:

 app_user

with a separate password, access limited to keys matching:

 app:*

and a limited set of commands.

The ACL configuration line might look like this:

 user app_user on >StrongPassword123 ~app:* +get +set +del +expire +ttl +exists +mget +mset

Here is what each part means:

  • app_user — the username;
  • on — enables the user;
  • >StrongPassword123 — the password;
  • ~app:* — grants access only to keys with the app: prefix;
  • +get, +set, and the other entries — the permitted commands.

In production, the password should, of course, be random and significantly stronger than the one used in this example.

After updating the configuration, restart Redis:

 sudo systemctl restart redis-server

Check the service status:

 sudo systemctl is-active redis-server

Now try running a command without authentication:

 redis-cli GET app:test

If authentication is required, Redis should deny access.

Now connect as app_user:

 redis-cli \
  --user app_user \
  --pass StrongPassword123

After connecting, create a key:

 SET app:test "hello"

Expected response:

 OK

Test read access:

 GET app:test

Expected result:

 "hello"

Now test the key pattern restriction.

Try creating a key without the app: prefix:

 SET other:test "blocked"

Redis should reject the operation because this key does not match:

 ~app:*

You can also test a prohibited administrative command:

 CONFIG GET maxmemory

If this command was not permitted, Redis should return an ACL error.

This confirms two things:

  • The user authenticates successfully;
  • The user‘s permissions are properly restricted.

Authentication and ACLs are now configured: the application connects as a dedicated user, can access only the required keys, and has no administrative privileges.

The next step is to verify the connection string, local and remote connections, and Redis behavior when an incorrect password is entered or a prohibited ACL command is used.

Checking the Connection to Redis

It is helpful to distinguish three layers here: network → authentication → permissions. If a TCP connection cannot be established, the password has not yet come into play. If Redis returns an authentication error, the network is working, but the credentials are incorrect. If login succeeds but a specific command is denied, the issue is with the ACL.

What a Connection String Looks Like

For Redis, you can use a URI in the following format:

 redis://username:password@host:6379

For a local connection from our application, the connection string will look something like this:

 redis://app_user:[email protected]:6379

If you use a separate Redis logical database, you can append its number:

 redis://app_user:[email protected]:6379/0

Here:

  • app_user is the ACL user;
  • password is the user‘s password;
  • 127.0.0.1 is the Redis address;
  • 6379 is the port;
  • /0 is the logical database number.

In a real-world application, it is better to store the password in environment variables or secret storage rather than hard-code it in the application code or shell commands.

Now let’s test the simplest scenario: a local connection.

How to Test the Local Connection

On the Redis VPS itself, connect as follows:

 redis-cli \
  --user app_user \
  --pass StrongPassword123

If the credentials are correct, Redis CLI will open without errors.

Test the connection:

 PING

Expected response:

 PONG

Now test access to the permitted key pattern:

 SET app:connection-test "ok"

Response:

 OK

Now read the value:

 GET app:connection-test

Expected response:

 "ok"

The local test confirms several things at once: redis-server is running, authentication succeeds, the ACL user is active, and the permitted commands execute successfully.

The next step is to run the same test from another server.

How to Test a Remote Connection

If Redis and the application are hosted on different VPS instances, first check the TCP port.

From the application VPS:

 nc -vz 10.0.0.10 6379

If the connection succeeds, proceed to the Redis CLI:

 redis-cli \
  -h 10.0.0.10 \
  -p 6379 \
  --user app_user \
  --pass StrongPassword123

Test the connection:

 PING

Expected response:

 PONG

Then read the permitted key again:

 GET app:connection-test

If Redis works locally but nc cannot connect from another VPS, the issue occurs before authentication. In that case, check the bind setting, UFW, the cloud firewall, or the private network.

If nc connects successfully but Redis returns a login error, the network layer can be considered operational.

Next, we will test this specific scenario.

What Happens When the Password Is Incorrect

Let’s try connecting with the correct username but an incorrect password:

 redis-cli \
  --user app_user \
  --pass WrongPassword

Redis should reject the authentication attempt.

The error will indicate that the username/password pair failed authentication.

This is useful because it immediately distinguishes a credentials issue from a network error.

In simplified terms:

If the password appears to be correct, check the username as well. In Redis ACLs, the username matters: one user’s password will not grant access as another user.

After testing the incorrect password, restore the correct credentials and proceed to the next level: permissions.

What Happens When an ACL-Denied Command Is Run

Reconnect as app_user:

 redis-cli \
  --user app_user \
  --pass StrongPassword123

A standard operation should work:

 GET app:connection-test

Now try an administrative command that the user has not been granted permission to run:

 CONFIG GET maxmemory

Redis should return a NOPERM ACL error.

The same thing will happen if you try to access a key outside the permitted pattern:

 SET other:test "blocked"

Because the user is restricted to:

 ~app:*

this key should be denied.

This is not an authentication error. The user has logged in successfully, but Redis is blocking a specific action.

In practice, this distinction is very useful:

  • Login fails → check the credentials;
  • Login succeeds, but the command is denied → check the ACL;
  • No connection at all → check the network.

The main scenarios can be summarized in a short table:

ScenarioCommandExpected Result
Local connectionredis-cli --user app_user --pass ... Successful login
Remote connectionredis-cli -h 10.0.0.10 --user app_user --pass ... Successful login
Connection checkPING PONG 
Incorrect passwordLogin with an incorrect passwordAuthentication error
Denied commandCONFIG GET maxmemory NOPERM 
Denied keySET other:test ... NOPERM 
Unreachable portnc -vz 10.0.0.10 6379 Connection error or timeout

The connection has now been tested at all the main levels: network, authentication, and ACL checks operate independently and produce clear errors.

Understanding How Redis Uses Memory

We can now turn to memory, which is critical to Redis in almost any use case.

Redis stores its working dataset in RAM, so the server’s behavior as the dataset grows depends directly on how much memory is available and whether maxmemory is configured. If this is not configured in advance, Redis may start competing with the operating system for RAM, use swap space, or ultimately run into the system OOM killer.

Why Redis Primarily Stores Data in RAM

Redis’s main advantage is extremely fast data access. To achieve this, keys and values are stored in RAM rather than read from disk for every request.

Even when RDB or AOF is enabled, persistence does not turn Redis into a conventional disk-based database. These mechanisms are primarily used to recover data after a restart or failure, while normal operations still use the dataset that Redis keeps in memory.

This leads to an important limitation: data capacity cannot be estimated based solely on SSD size. A server may have hundreds of gigabytes of free disk space, but if Redis needs to hold a 6 GB dataset with only 4 GB of RAM, a large disk alone will not solve the problem.

Therefore, the following sections will focus primarily on RAM, while disk capacity will be considered separately as storage for RDB, AOF, and system files.

What Memory Usage Includes

The amount of memory Redis actually uses is almost always greater than the size of the values alone.

The following reside in RAM:

  • Keys;
  • Values;
  • Redis data structures;
  • Metadata;
  • Allocator overhead;
  • Client buffers;
  • Internal structures;
  • Memory used for replication, if enabled;
  • Temporary overhead during certain background operations.

For example, a thousand small string values occupy more than the combined size of their payload data. Each key and object requires additional structures, and the memory allocator also introduces overhead of its own.

It is therefore useful to distinguish between the two: payload size ≠ actual Redis memory usage

In addition, Redis may create a child process using fork() during an RDB snapshot or AOF rewrite. Copy-on-write is used at this point: memory pages can be shared as long as they remain unchanged, but active write operations during the background process can significantly increase actual RAM usage.

This is why, when calculating maxmemory later, we will leave some headroom rather than setting the limit close to the VPS’s total RAM.

How to Check Current Redis Memory Usage

The primary diagnostic command is:

 redis-cli INFO memory

If ACLs are enabled:

 redis-cli \
  --user app_user \
  --pass StrongPassword123 \
  INFO memory

However, for technical monitoring, it is better to use a user that is authorized to run the INFO command.

Several metrics in the output are particularly relevant.

For example:

 used_memory:1048576
used_memory_human:1.00M
used_memory_rss:7340032
used_memory_peak_human:1.20M
maxmemory:2147483648
maxmemory_human:2.00G
mem_fragmentation_ratio:1.15

used_memory shows the amount of memory allocated by Redis through the allocator.

A more human-readable version is:

 used_memory_human

used_memory_rss shows how much physical memory the Redis process occupies from the operating system’s perspective.

These values may differ. RSS reflects allocator behavior, fragmentation, and the memory pages actually mapped, so it does not necessarily match used_memory.

Peak memory usage can be checked using:

 used_memory_peak

or:

 used_memory_peak_human

This is useful if Redis is currently using little RAM but previously experienced a significantly higher load.

Once the current memory usage is clear, you can move on to the main limit: maxmemory.

What Is maxmemory?

maxmemory sets the memory threshold at which Redis begins applying the configured maxmemory-policy.

For example:

 maxmemory 2gb

This can be configured in:

 /etc/redis/redis.conf

You can view the current setting as follows:

 redis-cli CONFIG GET maxmemory

The output will look similar to this:

 1) "maxmemory"
2) "2147483648"

Redis reports the value in bytes.

What happens after this limit is reached depends on the following setting:

 maxmemory-policy

For example, Redis can:

  • Evict old keys;
  • Evict rarely used keys;
  • Select keys with a TTL for eviction;
  • Stop accepting write operations altogether.

This mechanism lets you define in advance how the server should behave when its memory is full, rather than waiting for the entire VPS to run out of RAM.

This is why maxmemory and maxmemory-policy should almost always be considered together.

What happens if no limit is set

If maxmemory is not configured, Redis has no hard memory limit of its own and can continue growing along with the dataset.

In that case, the VPS’s physical RAM becomes the limit.

For example:

This behavior makes particularly little sense for a cache: instead of evicting a few old keys, Redis may begin putting pressure on the entire system.

If Redis is used as a critical data store and automatic data deletion is unacceptable, a limit is still useful—you can simply pair it with the noeviction policy. Redis will not silently delete keys; instead, new write operations will fail when there is insufficient memory.

In other words, maxmemory is not only for caches. It defines the threshold at which Redis begins following a predefined policy.

The key INFO memory metrics can be summarized in a short table:

MetricWhat it showsWhat to look for
used_memory Memory allocated by RedisCurrent usage
used_memory_human The same value in a human-readable formatQuick manual check
used_memory_rss Actual process RSSCompare with used_memory 
used_memory_peak Maximum usage since startupIdentify previous peaks
maxmemory Configured Redis memory limitShould not consume all of the VPS’s RAM
mem_fragmentation_ratio Ratio of RSS to memory in useHelps identify fragmentation

It is now clear why simply checking the VPS’s total RAM is not enough for Redis. You must separately determine how much memory Redis is allowed to use and how much headroom to reserve for the system and persistence operations.

Our next step is to calculate maxmemory using a specific example with 4 GB of RAM and set the limit so that Redis does not operate too close to the server’s physical memory capacity.

Calculating the maxmemory Limit

Why You Should Not Allocate All VPS RAM to Redis

Suppose a VPS has:

4 GB of RAM

If you configure:

 maxmemory 4gb

Redis will be allowed to use nearly all of the server’s memory for its data.

However, memory is also required for:

  • The operating system;
  • Page cache;
  • System processes;
  • Client buffers;
  • Allocator overhead;
  • Memory fragmentation;
  • Forking during RDB snapshots or AOF rewrites;
  • Other services running on the VPS.

This configuration therefore quickly leaves the system with no memory headroom.

This can result in an undesirable chain of events:

Therefore, maxmemory is not all the RAM on the Redis server, but only the portion of memory that we explicitly allow Redis to use for its working dataset.

How Much Memory to Reserve for the Operating System

There is no universal percentage.

On a small VPS, it is wise to leave a significant amount of headroom, especially if Redis is not the only service running. For a server with 4 GB of RAM, a reasonable guideline is:

1 GB

for Ubuntu and system processes.

However, this is not a hard-and-fast rule.

If the same server also runs:

  • Nginx;
  • an application;
  • a monitoring agent;
  • Docker;
  • other databases;

you will need to increase the reserve.

It is therefore useful to check the system’s actual memory usage first:

 free -h

and:

 ps aux --sort=-%mem | head

If a clean system is already using, for example, 700–900 MB of RAM, leaving it only an additional 300 MB would be too aggressive.

After accounting for the system reserve, you need to consider another important factor: persistence.

Accounting for persistence and fork

Redis uses fork() for RDB snapshots and certain AOF operations.

The main process continues running, while the child process creates the snapshot or performs the rewrite.

Immediately after fork(), memory usage does not fully double because of copy-on-write. However, if the application is actively modifying data at that time, the affected memory pages are copied.

As a result, RAM usage can increase significantly during the background operation.

As a rule of thumb:

The larger the dataset and the more intensive the write workload, the more important this headroom becomes.

Therefore, for Redis with persistence enabled, you cannot simply use the following calculation:

VPS RAM – OS memory = maxmemory

Additional memory must be reserved specifically for background operations.

This is especially important for a small VPS, where even a few hundred megabytes of additional memory usage can have a noticeable impact on the system.

Example Calculation for a VPS with 4 GB of RAM

Consider the following configuration:

VPS: 4 GB of RAM

First, reserve approximately:

1 GB

for the operating system and system processes.

This leaves:

3 GB

Now reserve approximately another:

1 GB

for persistence, fork operations, fragmentation, and unexpected growth.

This gives us:

A practical starting point is therefore:

 maxmemory 2gb

This is a fairly conservative configuration, but it provides a good safety margin for a small VPS.

It is important to understand that this is not a universal formula.

If Redis is used solely as a cache without persistence and the server runs almost nothing else, the limit can be set higher.

Conversely, if the dataset is large, write activity is high, RDB and AOF are enabled, and an application is running on the same server, more memory will need to be reserved.

Therefore, the actual configuration process is as follows:

Next, we move on to the practical steps.

Hands-on: Setting maxmemory

Open the configuration file:

 sudo nano /etc/redis/redis.conf

Find or add the following:

 maxmemory 2gb

You can also specify the current eviction policy alongside it, for example:

 maxmemory-policy noeviction

For now, we will use noeviction so that Redis does not automatically delete keys before we examine the different policies separately.

Configuration snippet:

 maxmemory 2gb
maxmemory-policy noeviction

Save the file and restart Redis:

 sudo systemctl restart redis-server

Check the service:

 sudo systemctl is-active redis-server

Now check whether the limit has been applied:

 redis-cli CONFIG GET maxmemory

Expect a value around:

 2147483648

This is 2 GB in bytes.

Check the policy:

 redis-cli CONFIG GET maxmemory-policy

Expected result:

 noeviction

Now check the memory information:

 redis-cli INFO memory

The following fields are of particular interest:

 used_memory
used_memory_human
used_memory_rss
maxmemory
maxmemory_human

For a more concise check, use:

 redis-cli INFO memory | grep -E "used_memory_human|maxmemory_human"

Example output:

 used_memory_human:1.10M
maxmemory_human:2.00G

Redis is currently using very little RAM, but it already knows the threshold beyond which it will act according to the selected policy.

Redis now has a clearly defined memory limit. It can no longer grow unchecked until it consumes all the RAM available on the VPS.

Selecting a Key Eviction Policy

Next, Redis needs to know what to do when memory usage approaches this limit.

This is controlled by:

 maxmemory-policy

This setting determines how Redis behaves when it runs out of memory: whether to reject new writes or free up space by evicting existing keys.

What Happens When maxmemory Is Reached

When Redis reaches the configured limit, it checks the maxmemory-policy setting.

Two fundamentally different scenarios are then possible.

The first option is:

 noeviction

Redis does not automatically delete anything and begins rejecting operations that require additional memory.

The second option is eviction. In this case, Redis selects eligible keys and deletes them to free up space for new data.

So:

The policy should therefore always be chosen based on Redis’s role. Automatic eviction is often useful for a cache, but it can be dangerous for critical data.

How noeviction Differs from Eviction

noeviction is the most conservative option for preserving existing data.

With this policy, Redis does not automatically delete keys.

If no memory is available, a write command may fail with an error such as:

 OOM command not allowed when used memory > 'maxmemory'

Existing data can still be read.

This policy is well suited to situations where automatic key deletion is unacceptable.

Eviction policies work differently: Redis frees memory by deleting some existing data.

This is useful for caching because an old key can be deleted and, if necessary, retrieved again from the primary data source.

In other words:

Next, it is important to understand how keys are selected for eviction.

How LRU, LFU, and TTL Policies Work

Redis supports several strategies.

LRU — Least Recently Used.

The idea is simple: keys that have not been accessed for the longest time are evicted first.

For example:

  • key A → accessed just now
  • key B → 10 minutes ago
  • key C → an hour ago

When memory is running low, Redis is more likely to select key C.

There are two main LRU policies:

 allkeys-lru
volatile-lru

allkeys-lru considers all keys.

volatile-lru considers only keys with a TTL.

LFU — Least Frequently Used.

Here, Redis uses access frequency rather than the time since the last access.

For example:

  • key A → read constantly
  • key B → read occasionally
  • key C → almost never read

When eviction is required, key C becomes the first candidate.

The corresponding policies are:

 allkeys-lfu
volatile-lfu

For caching, LFU can sometimes be a better fit than LRU when there is a small set of consistently “hot” data that is important to keep in memory.

There is also a TTL-based option:

 volatile-ttl

It selects from keys with an expiration time and prioritizes those whose TTL is closest to expiring.

Random eviction policies are also available:

 allkeys-random
volatile-random

They evict keys at random and are generally used less often because LRU and LFU provide more meaningful behavior.

How to choose a cache policy

If Redis is used as a standard cache, automatic eviction is a normal part of its operation.

The most common options to consider are:

 allkeys-lru

or:

 allkeys-lfu

allkeys-lru is well suited to scenarios where recently used data is generally more important than data that has not been used for a long time.

allkeys-lfu is a good choice when frequently requested keys should remain in the cache longer.

If only some of the keys in the cache are temporary and have a TTL, you can use volatile-*.

However, there is an important caveat: if memory is filled with keys that do not have a TTL, volatile-* will not be able to evict them.

For this reason, allkeys-* is often the simpler and more straightforward choice for a conventional cache.

How to Choose a Policy for a Data Store

If Redis stores data that must not be lost automatically, eviction becomes risky.

In this case, it is usually more appropriate to use:

 maxmemory-policy noeviction

Redis will then leave existing keys intact.

When memory is full, new writes will fail, but this is preferable to data being deleted without warning.

This mode makes the problem immediately apparent:

 OOM

and allows the administrator to:

  • Increase RAM;
  • Increase maxmemory;
  • Delete unnecessary data manually;
  • Correct the TTL settings;
  • Redesign the storage model.

This is generally safer for critical state.

If Redis is used in a mixed mode, with some keys being critical and others temporary, it is best not to rely on a single global eviction policy. A more reliable approach is to separate the data types in advance or, at minimum, design the TTLs and keyspace to ensure predictable behavior.

Hands-on: Configuring maxmemory-policy

Open the configuration file:

 sudo nano /etc/redis/redis.conf

For a standard cache, you can use:

 maxmemory 2gb
maxmemory-policy allkeys-lru

Alternatively:

 maxmemory 2gb
maxmemory-policy allkeys-lfu

If Redis is used as a data store:

 maxmemory 2gb
maxmemory-policy noeviction

Save the file and restart Redis:

 sudo systemctl restart redis-server

Check the service:

 sudo systemctl is-active redis-server

Now check the current policy:

 redis-cli CONFIG GET maxmemory-policy

For example:

 1) "maxmemory-policy"
2) "allkeys-lru"

Check the limit again:

 redis-cli CONFIG GET maxmemory

This confirms that Redis recognizes both the memory limit and the policy to apply once that limit is reached.

The following table provides an overview:

PolicyWhat is evictedUse casePrimary risk
noeviction NothingCritical data and storage-like use casesNew writes begin to fail
allkeys-lru Least recently used keysStandard cacheAny key may be evicted
allkeys-lfu Least frequently used keysCache with distinct hot keysAny key may be evicted
volatile-lru Least recently used keys with a TTLCache where only temporary keys may be evictedKeys without a TTL are not eligible for eviction
volatile-lfu Least frequently used keys with a TTLSimilar use cases, but with access frequency taken into accountThe same dependency on TTL
volatile-ttl Keys closest to expirationTemporary data with appropriate TTL settingsDoes not affect keys without a TTL
allkeys-random Random keysUncommon specialized use casesUnpredictable eviction
volatile-random Random keys with a TTLUncommon specialized use casesDepends on whether TTLs are set

If Redis is used as a cache, allkeys-lru or allkeys-lfu is usually a reasonable starting point. If data must not be evicted automatically, start with noeviction.

Testing Eviction in Practice

We have already selected an eviction policy, but now we need to see it in action, not just in the configuration. We will create a controlled test by temporarily reducing maxmemory, populating Redis with test keys, and observing what happens as memory usage approaches the limit.

For this experiment, there is no need to fill all 2 GB of RAM. We will temporarily set a low limit, such as 32 MB, and restore the operating value after the test.

Populating Redis with Test Keys

First, check the current settings:

 redis-cli CONFIG GET maxmemory
redis-cli CONFIG GET maxmemory-policy

For testing, temporarily set the following:

 redis-cli CONFIG SET maxmemory 32mb
redis-cli CONFIG SET maxmemory-policy allkeys-lru

Verify the settings:

 redis-cli CONFIG GET maxmemory
redis-cli CONFIG GET maxmemory-policy

Now, create test keys with values large enough to fill the available memory noticeably faster.

One simple approach is to use a loop:

 payload=$(head -c 2048 /dev/zero | tr '\0' 'A')
for i in $(seq 1 50000); do
  redis-cli SET "eviction:test:$i" "$payload" > /dev/null
done

Each value contains approximately 2 KB of data.

To check how many keys were actually retained, run:

 redis-cli DBSIZE

However, DBSIZE alone does not indicate whether eviction has started. To determine this, monitor memory usage and statistics at the same time.

How to Tell When Redis Is Approaching maxmemory

While populating Redis, you can periodically run:

 redis-cli INFO memory

For more concise output:

 redis-cli INFO memory | grep -E "used_memory_human|maxmemory_human"

For example, you might initially see:

 used_memory_human:8.42M
maxmemory_human:32.00M

Later:

 used_memory_human:24.73M
maxmemory_human:32.00M

The value will then approach the configured limit.

You can check the current maxmemory setting separately:

 redis-cli CONFIG GET maxmemory

Keep in mind that used_memory does not have to stop at exactly 32.00M. Redis accounts for overhead and internal data structures, and from the operating system’s perspective, the process may consume more memory.

Our goal here is different: to identify the point at which Redis can no longer continue accepting new data indefinitely and begins applying the maxmemory-policy.

What Happens After the Limit Is Reached

With:

 maxmemory-policy allkeys-lru

Redis begins freeing memory by evicting existing keys.

New SET operations continue to succeed, but some older entries gradually disappear.

You can check one of the earlier keys:

 redis-cli GET eviction:test:1

If it has already been evicted, the result will be:

 (nil)

Meanwhile, a more recent key may still exist:

 redis-cli GET eviction:test:50000

However, LRU should not be treated as a strict queue in which the first key added is always the first to be removed. Redis estimates how recently keys were used and selects eviction candidates, so the actual eviction order depends on key access patterns.

For comparison, you can temporarily enable:

 redis-cli CONFIG SET maxmemory-policy noeviction

and continue writing data.

When there is no longer enough memory for a new operation, Redis will return an OOM error instead of automatically evicting existing keys.

This makes the difference directly observable in practice:

 allkeys-lru

→ older keys are evicted

→ new writes continue

 noeviction

→ existing keys are retained

→ new writes begin to fail

After the comparison, switch back to allkeys-lru for the current eviction test:

 redis-cli CONFIG SET maxmemory-policy allkeys-lru

The only remaining step is to check how many keys Redis actually evicted.

How to Check the Number of Evicted Keys

Redis statistics can be viewed with:

 redis-cli INFO stats

The metric we need is:

 evicted_keys

You can display only this metric:

 redis-cli INFO stats | grep evicted_keys

Before the test, the value may be:

 evicted_keys:0

After the memory fills up:

 evicted_keys:8421

The exact number depends on the size of the values, the current overhead, and the number of entries.

It is useful to monitor two groups of metrics together:

 redis-cli INFO memory | grep -E "used_memory_human|maxmemory_human"
redis-cli INFO stats | grep evicted_keys

The first shows memory usage, while the second shows the actual number of evictions.

You can also check the number of existing keys:

 redis-cli DBSIZE

If we have executed tens of thousands of SET commands, but DBSIZE has stopped growing in proportion to the number of entries while evicted_keys continues to increase, the eviction policy is indeed working.

After the test, it is important to restore the normal operating settings:

 redis-cli CONFIG SET maxmemory 2gb

Then verify them:

 redis-cli CONFIG GET maxmemory
redis-cli CONFIG GET maxmemory-policy

If the production policy should also be allkeys-lru, leave it unchanged. If Redis will subsequently be used as a data store, restore noeviction before deploying to production.

Also remember that CONFIG SET changes the running configuration, but the persistent settings must still be defined in /etc/redis/redis.conf. This file must contain the operating maxmemory and maxmemory-policy values so that Redis starts with the required settings after the next restart.

We have now observed how Redis actually behaves when memory fills up. The next question is different: what happens not to individual keys during eviction, but to the entire dataset after a restart? To answer it, we will examine persistence and compare RDB with AOF.

Understanding Redis Data Persistence

Why Keeping Data in RAM Does Not Mean There Is No Persistence

During normal operation, Redis reads and modifies data in RAM. This is what makes it so fast.

However, it can also persist its state to disk.

In simplified terms:

After a restart, Redis can read the persistence files and restore the keys to memory.

It is important to distinguish between two things:

  • Active data → RAM
  • Recovery after a restart → RDB / AOF

In other words, persistence does not change Redis’s fundamental operating model. The disk is not used for every routine read; it is used to restore state after a restart or failure.

Next, we will examine the two main persistence mechanisms: RDB and AOF.

How RDB Works

RDB creates a snapshot of the Redis state at a specific point in time.

The result is a file such as:

 dump.rdb

Redis stores the data that existed at the time the snapshot was created in this file.

The process works as follows:

RDB snapshots can be created:

  • Automatically according to configured rules;
  • Manually;
  • Before certain operations.

The main advantage of RDB is that it produces a compact file and makes recovery relatively straightforward.

However, snapshots are created periodically rather than after every write. Therefore, an unexpected failure may result in the loss of changes made after the last successful snapshot.

For example:

  • 10:00 → RDB snapshot
  • 10:01 → SET key1
  • 10:02 → SET key2
  • 10:03 → unexpected shutdown

If no new snapshot was created, the latest changes may not be included in dump.rdb.

RDB is therefore well suited to use cases where some loss of the latest writes is acceptable or periodic state backups are required.

How AOF Works

AOF works differently.

Instead of taking periodic snapshots, Redis records operations that modify data.

Conceptually:

 SET app:a 1
INCR app:counter
DEL app:old

are written to an append-only log.

After a restart, Redis can replay this log to restore its state.

Diagram:

The following setting controls how often data is actually synchronized to disk:

 appendfsync

Later, we will examine each of these options separately:

 always
everysec
no

This setting determines the balance between durability and performance.

AOF generally reduces the potential loss of recent changes compared with infrequent RDB snapshots, but it generates more disk activity and requires periodic rewrites to prevent the log from growing indefinitely.

Can RDB and AOF Be Used Together?

Yes.

Redis can simultaneously:

  • create RDB snapshots;
  • maintain an AOF log.

Both mechanisms then operate in parallel.

This provides a more flexible setup:

  • RDB → a compact snapshot
  • AOF → a more detailed change log

However, this comes at the cost of additional disk load and greater operational complexity.

If both mechanisms are enabled, Redis uses the AOF at startup because it provides a more complete record of the latest changes.

The RDB+AOF combination is therefore suitable when both a snapshot and an operation log are needed.

This is often excessive for a small cache, but may be entirely justified for more important data.

When Persistence Is Not Needed at All

If Redis is used exclusively as a cache and the data can always be restored from the primary source, persistence can be disabled.

For example:

If Redis restarts with an empty dataset, the application will simply begin repopulating the cache.

In this scenario, persistence only:

  • Causes additional disk writes;
  • Increases the load;
  • Makes the configuration more complex.

However, if Redis stores:

  • Important counters;
  • State;
  • Queues;
  • Data that cannot simply be recreated;

disabling persistence becomes risky.

The main options can be compared as follows:

ModeWhat is persistedPotential loss of recent dataUse cases
No persistenceNothingAll data is lost after a restartCache only
RDBPeriodic snapshotsChanges made after the most recent snapshot may be lostRecoverable caches, backups, and moderately important data
AOFLog of write operationsDepends on appendfsync More important, frequently changing data
RDB + AOFSnapshot + logMinimal, within the limits of the selected appendfsync setting When both recovery and an additional safeguard are important

Now that the persistence mechanism is clear, let’s start with the simpler option: RDB.

Configuring RDB

RDB is a good choice as a first persistence mechanism: it is relatively simple, creates a compact snapshot, and lets you quickly verify whether data survives a restart.

First, we’ll examine when this mode is useful, then enable it and see where dump.rdb is created.

When RDB Is the Best Choice

RDB is particularly well suited to three scenarios.

First, Redis is used primarily as a cache, but rebuilding the entire cache from scratch after every restart is undesirable.

Second, periodic snapshots are needed as additional recovery points.

Third, losing some of the most recent changes between snapshots is acceptable.

In other words, RDB is a good fit when simplicity and compactness are important and persisting every individual operation is not required.

However, if losing the last few seconds or minutes of writes is unacceptable, AOF is usually a better choice.

How snapshot rules work

Automatic snapshots are configured using save rules.

These rules are based on two parameters: elapsed time and the number of changes.

For example:

 save 3600 1

means: create a snapshot if at least one key has changed within 3600 seconds.

Another example:

 save 300 100

creates a snapshot if at least 100 changes occur within 300 seconds.

You can configure multiple rules at the same time.

For example:

 save 3600 1
save 300 100
save 60 10000

Redis evaluates each rule independently. If at least one condition is met, Redis creates an RDB snapshot.

This allows snapshots to be created less frequently during periods of low activity and more frequently during periods of intensive write activity.

Where the Dump Is Stored

The RDB file location is determined by two parameters:

 dir
dbfilename

You can check them as follows:

 redis-cli CONFIG GET dir
redis-cli CONFIG GET dbfilename

The output will typically look something like this:

 dir
/var/lib/redis

and:

 dbfilename
dump.rdb

The full path is therefore:

 /var/lib/redis/dump.rdb

You can check the file with:

 sudo ls -lh /var/lib/redis/dump.rdb

If a snapshot has never been created, the file may not exist yet.

Next, we will configure RDB persistence and create a snapshot manually.

Hands-on: Enabling and Verifying RDB

Open the configuration file:

 sudo nano /etc/redis/redis.conf

We’ll keep a few straightforward rules:

 save 3600 1
save 300 100
save 60 10000

Make sure the RDB file is named:

 dbfilename dump.rdb

and the working directory is:

 dir /var/lib/redis

Save the configuration and restart Redis:

 sudo systemctl restart redis-server

Check the service:

 sudo systemctl is-active redis-server

Now create a test key:

 redis-cli SET app:rdb-test "saved-by-rdb"

If you are using an ACL user:

 redis-cli \
  --user app_user \
  --pass StrongPassword123 \
  SET app:rdb-test "saved-by-rdb"

Verify it:

 redis-cli GET app:rdb-test

Now trigger a snapshot manually:

 redis-cli BGSAVE

The expected response is:

 Background saving started

You can check the persistence status as follows:

 redis-cli INFO persistence

For example, the following fields are relevant:

 rdb_bgsave_in_progress
rdb_last_save_time
rdb_last_bgsave_status

After the operation completes:

 rdb_bgsave_in_progress:0
rdb_last_bgsave_status:ok

Now check the file:

 sudo ls -lh /var/lib/redis/dump.rdb

You can also check its modification time:

 sudo stat /var/lib/redis/dump.rdb

If dump.rdb exists and rdb_last_bgsave_status reports ok, the snapshot was created successfully.

RDB is now operational: Redis can periodically save its state to dump.rdb and use this file to restore the data the next time it starts.

The next step is to configure AOF and compare it with RDB in practice: how the log is maintained, how always, everysec, and no differ, and which option is better suited to more important data.

Configuring AOF

When to Use AOF

AOF makes sense when minimizing the potential loss of recent changes is more important.

For example, if Redis stores:

  • Counters;
  • Queues;
  • Session state;
  • Temporary workflow state;
  • Data that you do not want to lose between two RDB snapshots.

In this setup, AOF generally provides more predictable recovery after a failure.

However, this comes at the cost of additional disk writes and some overhead.

In simplified terms:

and

Now let’s examine exactly how it works.

How appendonly works

AOF is enabled using the following setting:

 appendonly yes

Once enabled, Redis begins logging operations that modify data.

For example:

 SET app:user:1 "Alice"
INCR app:counter
DEL app:old

These operations are written to the AOF.

When Redis restarts, it reads the log and replays the commands to restore its state.

In simplified terms:

However, the AOF must not grow indefinitely.

Redis periodically performs an AOF rewrite, creating a more compact representation of the current state instead of retaining the entire history of changes.

For example, if the value of a key has changed hundreds of times, the new AOF does not need to retain every intermediate SET command. It only needs to store the final state.

How appendfsync Policies Differ

The appendonly yes setting alone does not determine how often data is physically synchronized to disk.

This is controlled by:

 appendfsync

There are three main modes.

First:

 appendfsync always

Redis requests synchronization after every write operation.

This is the safest option in terms of minimizing the loss of recent data, but it also places the heaviest load on the disk.

Second:

 appendfsync everysec

Synchronization is typically performed about once per second.

This provides a good balance between reliability and performance, making everysec the most balanced option for many use cases.

Third:

 appendfsync no

Redis does not request fsync after every write or once per second, relying instead on the operating system.

Performance may be higher, but the timing of the actual write to disk becomes less predictable.

Choosing Between always, everysec, and no

For a typical VPS, a reasonable starting point is:

 appendfsync everysec

In this mode, any loss of recent changes in the event of a failure is typically limited to a short interval, and fsync is not called after every individual command.

always is suitable when losing even the most recent operations is highly undesirable and the storage can handle the additional load.

no is appropriate when performance is more important than ensuring that data is flushed to disk promptly.

In summary:

appendfsync ReliabilityPerformanceWhen to Use
always Highest among AOF modesLowerCritical writes
everysec HighGood balanceMost production scenarios
no LowerHigherWhen a greater loss of recent changes is acceptable

Now let’s enable AOF on our server.

Hands-on: Enabling AOF

Open:

 sudo nano /etc/redis/redis.conf

Find or add:

 appendonly yes
appendfsync everysec

Also check the AOF filename:

 appendfilename "appendonly.aof"

In newer versions of Redis, AOF data may be stored as a set of files in a separate directory rather than as a single file, including base and incremental AOF files. Therefore, it is best to check the actual location using the current configuration and the contents of the working directory.

Save the file and restart Redis:

 sudo systemctl restart redis-server

Check:

 sudo systemctl is-active redis-server

Now check whether the settings have been applied:

 redis-cli CONFIG GET appendonly

Expected output:

 appendonly
yes

Also:

 redis-cli CONFIG GET appendfsync

Expected output:

 appendfsync
everysec

Persistence status:

 redis-cli INFO persistence

The AOF parameters of interest include:

 aof_enabled
aof_rewrite_in_progress
aof_last_rewrite_time_sec
aof_last_bgrewrite_status

With AOF enabled, expect:

 aof_enabled:1

Persistence now works through both RDB snapshots and the append-only mechanism.

The next step is to test the most important scenario: whether the created keys actually survive a Redis restart and a full VPS reboot.

Verifying Data Persistence After a Restart

Creating Test Keys

Let’s create several different values:

 redis-cli SET app:persistence:string "survives-restart"
redis-cli SET app:persistence:counter 42
redis-cli HSET app:persistence:user name "Alice" role "admin"

If you are using app_user, run the commands as that user.

Let’s verify the values:

 redis-cli GET app:persistence:string

Expected output:

 "survives-restart"

Counter:

 redis-cli GET app:persistence:counter

Output:

 "42"

Hash:

 redis-cli HGETALL app:persistence:user

We now have several easily identifiable test values.

Before restarting, it is useful to verify once more that persistence is enabled:

 redis-cli INFO persistence

Then check the RDB/AOF status.

Restarting Redis

Now perform a standard restart:

 sudo systemctl restart redis-server

Then check the service status:

 sudo systemctl is-active redis-server

Expected output:

 active

A restart clears the process’s RAM, so if persistence is not working correctly, the keys may be lost at this point.

Now let’s check the data.

Verifying recovery

Read the first key:

 redis-cli GET app:persistence:string

Expected output:

 "survives-restart"

Counter:

 redis-cli GET app:persistence:counter

Expected output:

 "42"

Hash:

 redis-cli HGETALL app:persistence:user

If all the values are present, Redis has successfully restored its state after the restart.

The complete sequence looks like this:

However, restarting the service is not the same as fully rebooting the VPS.

How restarting Redis differs from a full VPS reboot

When you run:

 sudo systemctl restart redis-server

only Redis is restarted.

The operating system continues to run, temporary files and caches remain in place, the disk stays mounted, and other services are unaffected.

When you run:

 sudo reboot

the entire VPS is rebooted.

This allows you to check several things at once:

  • Whether Redis starts automatically;
  • Whether the configuration is preserved;
  • Whether the persistence files are available after boot;
  • Whether the data is restored;
  • Whether the ACL and network settings are preserved.

Therefore, it is best to test both scenarios as part of the final check.

Verifying Data After a Reboot

Reboot the VPS:

 sudo reboot

After reconnecting via SSH, first check the service:

 sudo systemctl status redis-server

Or, more concisely:

 sudo systemctl is-active redis-server

If the service starts automatically, the output will be:

 active

Now retrieve the test key again:

 redis-cli GET app:persistence:string

Expected output:

 "survives-restart"

Check the counter:

 redis-cli GET app:persistence:counter

And the hash:

 redis-cli HGETALL app:persistence:user

If all three values are returned, the data has persisted through not only a process restart but also a full VPS reboot.

You can also run:

 redis-cli INFO persistence

and verify that the AOF and RDB data were loaded again without errors.

Redis as a Cache and Redis as a Data Store

We have now configured both memory limits and persistence. The next step is to align these mechanisms with the application’s actual requirements, because the same Redis configuration cannot accommodate every use case.

The key question here is simple: can the data be restored from another source if Redis loses it? If so, it is primarily a cache. If not, Redis becomes part of the storage system, and the requirements for eviction, TTL, and persistence change significantly.

When Redis Is Used Only as a Cache

A classic Redis use case is to store a copy of data that already exists elsewhere.

For example:

The application checks Redis first. If the required key is found, the data is returned immediately. Otherwise, the application reads it from the primary database and stores it in Redis again.

In this case, deleting a key does not result in the loss of the original data. At most, the next request will be slightly slower because the cache must be repopulated.

The following policies are well suited to this use case:

 maxmemory-policy allkeys-lru

or:

 maxmemory-policy allkeys-lfu

When memory fills up, Redis can automatically free up space for new data.

Persistence is not always necessary in this scenario. If the cache can be safely warmed up again after a restart, RDB and AOF only add disk I/O and complicate the configuration. However, if repopulating a large cache takes a long time and puts a load on the primary database, RDB can help bring the server back into operation more quickly.

Caching is relatively straightforward. Things become more complicated when Redis stores state that does not exist anywhere else.

When Redis Becomes Part of Persistent Storage

Redis is no longer just a cache when losing a key means losing real application state.

For example, it may store:

  • Counters;
  • Queues;
  • Task state;
  • Rate-limit state that is important to preserve;
  • Session data, if there is no other source;
  • Intermediate workflow state;
  • Other data that cannot be recreated automatically.

In this case, it is no longer safe to treat every key as disposable.

If you configure:

 allkeys-lru

When memory runs low, Redis may delete a key that the application still considers important.

Therefore, for a storage-like use case, it is safer to use:

 maxmemory-policy noeviction

When memory fills up, the problem will become explicit: new operations requiring additional RAM will begin to fail, but Redis will not free up space by deleting existing data.

Persistence also becomes more important. If the state matters, Redis must be able to restore it after a restart or failure.

Therefore, the role of Redis directly determines two settings at once:

We can now examine the relationship between these mechanisms more closely.

How Eviction and Persistence Change

Eviction and persistence serve entirely different purposes.

maxmemory-policy determines what Redis should do when it reaches its configured memory limit.

Persistence determines where data is restored from after a restart.

Therefore, enabling AOF does not protect a key from eviction.

If the following setting is used:

 maxmemory-policy allkeys-lru

Redis may remove a key from the working set, after which persistence records the new state without that key.

The reverse is also true: noeviction does not protect against data loss after an unexpected restart if persistence is completely disabled.

Therefore, these settings must be selected together.

For a cache, the following combination may be appropriate:

 maxmemory-policy allkeys-lru

persistence disabled

For more critical state:

 maxmemory-policy noeviction
appendonly yes
appendfsync everysec

These represent fundamentally different Redis operating models.

There are intermediate options as well. For example, temporary data may be valuable while the application is running but expire automatically after several minutes or hours. In this case, TTL becomes the primary consideration.

Managing TTL

TTL determines how long a key should exist.

For example:

 SET app:cache:user:42 "..." EX 300

creates a key that expires after five minutes.

You can check the remaining time with the following command:

 TTL app:cache:user:42

TTL is particularly useful for:

  • Caches;
  • Sessions;
  • Temporary tokens;
  • Rate limiting;
  • Intermediate results;
  • Data with a natural lifespan.

For cached data, the rule is usually simple: if the data becomes outdated over time, it is best to set an expiration explicitly.

Otherwise, Redis may accumulate a huge number of keys that the application will never read again.

However, TTL should not be added indiscriminately to all data. If a key stores state that must persist until it is explicitly changed or deleted, an unintended expiration creates a risk of data loss.

You should therefore define the lifecycle of each key type in advance:

This makes Redis behavior much more predictable.

Which Settings to Choose for Each Use Case

In practice, it is useful to distinguish between three use cases rather than two: standard caching, temporary application data, and more persistent state.

Use CaseEvictionPersistenceTTLWhat to Consider
Cacheallkeys-lru / allkeys-lfu Can be disabledUsually requiredThe data can be retrieved again
Temporary DataDepends on whether data loss is acceptableRDB or AOF as neededUsually requiredThe data lifetime and the consequences of deletion must be understood
Persistent Statenoeviction Usually AOF or RDB+AOFOnly if expiration is part of the application logicKeys must not be lost silently

This table is a starting point, not a universal template. For example, a session store may tolerate the automatic deletion of old sessions in one project while being considered a critical component in another.

The key is to answer three questions in advance:

  1. Can the key be restored from another source?
  2. Can Redis be allowed to delete it when memory is full?
  3. Should the key be available again after a restart?

The answers almost directly determine the TTL, eviction policy, and persistence settings.

Now that the role of Redis has been established, we can consider what happens when memory estimates prove too optimistic or the dataset grows unexpectedly. We will examine how a RAM shortage manifests itself and where to look for the cause.

Diagnosing Memory Shortages

A Redis memory issue does not always result in a service outage. Sometimes redis-server continues to respond normally to GET requests, but new writes suddenly start failing. In other cases, the application runs without errors while Redis continuously evicts thousands of keys from the cache.

Therefore, diagnosis requires checking several metrics rather than just one: used_memory, maxmemory, the configured policy, the number of evictions, and the memory status of the VPS itself.

What “OOM command not allowed” looks like

The most obvious scenario occurs with:

 maxmemory-policy noeviction

When Redis reaches maxmemory and the next command requires additional memory, the write fails with an OOM error.

For example:

 SET app:test "value"

may return a message such as:

 OOM command not allowed when used memory > 'maxmemory'.

This does not necessarily mean that Redis itself is malfunctioning.

It continues to run, and operations that do not require additional memory allocation can still be performed. For example, existing keys can still be read.

Therefore, in this case, OOM does not mean that Redis has crashed. It means that Redis has reached its configured limit and the policy does not allow it to free memory automatically.

The first thing to check after this error is the actual memory usage.

How to Check used_memory

Main command:

 redis-cli INFO memory

For a quick check:

 redis-cli INFO memory | grep -E "used_memory_human|used_memory_peak_human|maxmemory_human"

For example:

 used_memory_human:1.98G
used_memory_peak_human:2.01G
maxmemory_human:2.00G

If used_memory has nearly reached maxmemory, the cause of the OOM error is clear.

It is also useful to check:

 used_memory_peak

This shows how high memory usage has risen since Redis was started.

Current memory usage may have already decreased, but a high peak indicates that the server previously came close to the limit.

Next, check the limit itself—it may not be appropriate for the current dataset size.

How to Check maxmemory

Current value:

 redis-cli CONFIG GET maxmemory

And the policy:

 redis-cli CONFIG GET maxmemory-policy

For example:

 maxmemory
2147483648

and:

 maxmemory-policy
noeviction

This gives us a clear picture:

used_memory ≈ 2 GB

maxmemory = 2 GB

policy = noeviction

In this state, new writes can be expected to fail with OOM errors.

However, if an eviction policy is enabled, the application may not see any errors at all. Redis will start freeing memory automatically.

In that case, check the eviction statistics.

How to Detect Evictions

You can check the number of keys evicted due to insufficient memory with:

 redis-cli INFO stats | grep evicted_keys

For example:

 evicted_keys:18432

If the value keeps increasing, Redis is regularly reaching maxmemory and freeing up space.

Eviction itself is not an error for a cache. That is precisely why we enable allkeys-lru or allkeys-lfu.

What matters is the scale.

If the application is constantly losing cached data, the hit rate is falling, and evicted_keys is increasing rapidly, there may not be enough memory for the normal working set.

In other words:

  • evicted_keys increases slowly → normal behavior for a size-limited cache
  • evicted_keys increases continuously and rapidly → the cache is likely short on memory

At this point, it makes sense to check the dataset size, TTLs, and the actual usefulness of the stored keys.

Sometimes, however, a different anomaly occurs: Redis reports one memory usage figure, while Linux shows that the process is significantly larger.

Why a Process May Use More Memory Than the Size of Its Data

For example, Redis reports:

 used_memory_human:1.30G

while top or ps shows that the process is using significantly more memory.

This does not necessarily indicate a memory leak.

The difference may be caused by:

  • allocator overhead;
  • memory fragmentation;
  • client buffers;
  • copy-on-write;
  • background RDB/AOF operations;
  • internal data structures;
  • the way Linux accounts for RSS.

You can compare these metrics using:

 redis-cli INFO memory

The following metrics are of particular interest:

 used_memory
used_memory_rss
mem_fragmentation_ratio

At the OS level, use:

 ps -C redis-server -o pid,%mem,rss,vsz,cmd

and:

 free -h

In other words, looking only at the size of the stored values is not enough. Redis is a running process with its own buffers, data structures, and allocator.

However, if RSS grows unexpectedly while system RAM is running low, check persistence and background processes.

What to Do When RAM Is Running Low

After diagnosing the issue, do not increase maxmemory immediately.

First, determine why memory is running low.

If Redis is used as a cache, check the following:

  • Are the TTLs configured correctly?
  • Are unnecessary keys being left without an expiration?
  • Is the current eviction policy appropriate?
  • Can the size of the values be reduced?
  • Is the cache too large for the VPS?

To view the number of keys, run:

 redis-cli DBSIZE

To analyze the keyspace, run:

 redis-cli INFO keyspace

If the issue is not stale data and the working set has genuinely grown, the next step is to increase the VPS RAM and then recalculate maxmemory.

The constraints are even stricter when Redis is used as storage with noeviction. If data must not be deleted, you cannot simply enable allkeys-lru to eliminate OOM errors. You must either free memory in a controlled manner, scale up the server, or change the storage architecture.

Also check the system RAM:

 free -h

and swap:

 swapon --show

If Redis is being heavily swapped out, latency may increase significantly. Swap should therefore be reserved for emergencies rather than used to accommodate a dataset that cannot physically fit in RAM.

The main symptoms can be summarized as follows:

SymptomPossible causeWhat to checkWhat to do
OOM command not allowed Redis has reached maxmemory with noeviction enabled INFO memory, CONFIG GET maxmemory-policy Free memory, increase both RAM and the limit, or reduce the dataset
evicted_keys is increasing rapidlyThe cache is repeatedly reaching its limitINFO stats Check the TTLs, eviction policy, and VPS size
used_memory is nearly equal to maxmemory The working set has reached the limitINFO memory Assess the dataset and the required headroom
RSS is significantly higher than used_memory Fragmentation, buffers, or background operationsINFO memory, ps, free -h Check fragmentation, clients, and persistence
The VPS is using swap heavilyRedis and the other processes do not fit in RAMfree -h, swapon --show Reduce memory usage or increase RAM
The Redis process was terminated by the systemThe system ran out of memoryjournalctl, system logsIncrease RAM headroom and reduce maxmemory 

Troubleshooting should therefore proceed from the general to the specific: first confirm that the VPS is not experiencing system-wide memory pressure, then compare used_memory with maxmemory, check the policy, and only then examine evictions and the dataset structure.

Checking Redis After a Restart

What Should Start Automatically

After installation, we enabled Redis to start automatically via systemd:

 sudo systemctl enable redis-server

Therefore, the following service should start automatically after a reboot:

 redis-server.service

When the service starts, it should apply the settings from:

 /etc/redis/redis.conf

In other words, after a reboot, we expect the following to be preserved:

  • Bind;
  • Protected-mode;
  • ACL;
  • Maxmemory;
  • Maxmemory-policy;
  • RDB/AOF;
  • Test data, provided persistence is configured correctly.

Now let’s verify this step by step.

Check the service

Reboot the VPS:

 sudo reboot

After reconnecting via SSH, check the Redis status:

 sudo systemctl status redis-server

Run a quick check:

 sudo systemctl is-active redis-server

Expected output:

 active

Additionally, check that the service is enabled at startup:

 sudo systemctl is-enabled redis-server

Expected output:

 enabled

If the service did not start after the reboot, first check the logs for the current boot:

 sudo journalctl -u redis-server -b --no-pager

This allows you to quickly determine whether the issue is with redis.conf, permissions for the persistence files, or the service startup itself.

Next, verify that Redis is not only running but still requires authentication.

Verify authentication

First, try accessing Redis without credentials:

 redis-cli GET app:persistence:string

If the ACLs are configured correctly, Redis should not return protected data to an anonymous client.

Now connect as app_user:

 redis-cli \
  --user app_user \
  --pass StrongPassword123

Once connected, run:

 PING

Expected response:

 PONG

Then read an allowed key:

 GET app:persistence:string

If the login succeeds and data is accessible only within the permissions defined by the ACLs, authentication has persisted correctly across the reboot.

Now we can move on to the data itself.

Verifying the Data

Earlier, we created the following test keys:

 app:persistence:string
app:persistence:counter
app:persistence:user

Check the first key:

 redis-cli \
  --user app_user \
  --pass StrongPassword123 \
  GET app:persistence:string

Expected output:

 "survives-restart"

Check the counter:

 redis-cli \
  --user app_user \
  --pass StrongPassword123 \
  GET app:persistence:counter

And the hash:

 redis-cli \
  --user app_user \
  --pass StrongPassword123 \
  HGETALL app:persistence:user

If the values are still present, the persisted data survived a full VPS reboot.

You can also check the RDB/AOF status:

 redis-cli INFO persistence

We need to confirm that persistence is enabled and that Redis reports no errors from the most recent snapshot or AOF rewrite.

The final step is to verify that the memory settings were not reset either.

Checking maxmemory and the eviction policy

First, check the limit:

 redis-cli CONFIG GET maxmemory

If we retained the configured value:

 maxmemory 2gb

Redis should return the equivalent number of bytes.

Now check the policy:

 redis-cli CONFIG GET maxmemory-policy

For example:

 allkeys-lru

or:

 noeviction

depending on the selected use case.

You can also check memory usage:

 redis-cli INFO memory | grep -E "used_memory_human|maxmemory_human"

And evictions:

 redis-cli INFO stats | grep evicted_keys

This confirms that after a reboot, Redis not only starts but also retains the required behavior when memory is constrained.

The final checks can be summarized in a short table:

CheckCommandExpected result
Servicesystemctl is-active redis-server active 
Authenticationredis-cli --user app_user --pass ... Successful login
MemoryCONFIG GET maxmemory Configured limit
PersistenceINFO persistence RDB/AOF with no errors
DataGET app:persistence:string The test key exists

If all five checks pass after a reboot, Redis has successfully returned to an operational state automatically: the service starts, ACLs are preserved, data is restored, and the memory limit and eviction policy remain the same as before the reboot.

How to Use Redis Safely in Production

The configuration no longer resembles a test environment: network access is restricted, ACLs are configured, memory usage is controlled, and persistence has been tested through both service restarts and system reboots. Before deploying to production, establish a few rules to prevent Redis from causing unexpected issues under real-world load.

Do Not Use Redis Without Network Access Restrictions

Redis should not be exposed to the entire internet on port 6379, even if it is password-protected.

If the application runs on the same VPS, the best option is to keep:

 bind 127.0.0.1

If a separate Application VPS is used, a private network and a firewall that allows access only from a specific source IP are preferable.

Otherwise, the access scope becomes too broad and difficult to control.

The approach should be simple: the required interface, a firewall, and an ACL—not “the port is open to everyone, but at least it has a password.”

Separate Users and ACLs

The application should connect using a dedicated user account rather than the unrestricted default user.

For each service, it is best to define:

  • A dedicated username;
  • A dedicated password;
  • Allowed commands;
  • Allowed key patterns.

For example:

 app_user

→ ~app:*

→ GET / SET / DEL / EXPIRE

If a separate worker or analytics service is added later, you can create a dedicated ACL for it.

This makes it easier to change credentials, disable an individual service, and avoid granting the same permissions to every service.

Do Not Allocate All of the Server’s RAM to Redis

maxmemory should not be set equal to the VPS’s physical RAM.

Memory must be reserved for:

  • The operating system;
  • systemd and SSH;
  • Buffers;
  • Fragmentation;
  • RDB/AOF;
  • Copy-on-write during fork;
  • Other services.

For a VPS with 4 GB of RAM, we chose approximately:

 maxmemory 2gb

as a conservative starting point.

In production, the exact value should be reassessed based on actual used_memory, workload, and persistence behavior.

Choose an eviction policy based on the use case

The eviction policy must be appropriate for the type of data.

For a standard cache, you can use:

 allkeys-lru

or:

 allkeys-lfu

If data must not be evicted automatically:

 noeviction

It is safer to receive an explicit OOM error than to lose an important key through automatic eviction.

Therefore, the policy must be selected based on the role Redis serves.

Configure persistence deliberately

RDB and AOF serve different purposes.

RDB is well suited for periodic snapshots and fast state recovery.

AOF provides better durability for recent changes and is generally more useful when losing the latest writes is undesirable.

For a pure cache, persistence may not be needed at all.

For more critical data, you can use:

 appendonly yes
appendfsync everysec

or a combination of RDB and AOF.

The key is to understand how much data loss the application can actually tolerate.

Store Backups Outside the VPS

A persistence file stored on the same server is not a complete backup.

If the VPS loses its disk or is deleted, the following will be lost along with Redis:

 dump.rdb

and the AOF.

Backups should therefore be periodically transferred to separate storage:

  • Object Storage;
  • Backup VPS;
  • Separate storage;
  • A different fault domain.

A local snapshot is useful for fast recovery, but it should not be the only copy.

Monitor memory, disk space, and logs

Redis should be checked proactively, rather than waiting until it starts returning OOM errors.

Basic command set:

 redis-cli INFO memory
redis-cli INFO stats
redis-cli INFO persistence

At the system level:

 free -h
df -h

And the logs:

 sudo journalctl -u redis-server -n 100 --no-pager

Pay particular attention to:

  • used_memory;
  • maxmemory;
  • evicted_keys;
  • persistence errors;
  • available disk space;
  • swap usage.

If AOF or RDB files are growing rapidly, insufficient disk space can become just as serious a problem as insufficient RAM.

Upgrade Redis in stages

It is best not to perform major upgrades blindly.

Before upgrading, check:

  • The current version;
  • The target version;
  • Configuration compatibility;
  • ACL changes;
  • The persistence format;
  • Client library compatibility.

Redis version:

 redis-server --version

Before a major upgrade, it is advisable to have a recent external copy of the RDB/AOF files and a clear rollback plan.

After the upgrade, repeat the same checks:

 sudo systemctl status redis-server
redis-cli PING
redis-cli INFO persistence

and separately check ACLs, maxmemory, the eviction policy, and the data.

What to Check Before Production

Before launching the application, complete these three short checklists.

Network and access

  • Redis is not exposed to the entire internet;
  • bind includes only the required interfaces;
  • the firewall restricts access by source IP address;
  • the application uses a dedicated ACL user;
  • administrative commands are unavailable to the application.

Memory and persistence

  • maxmemory leaves sufficient headroom on the VPS;
  • maxmemory-policy is appropriate for the role of Redis;
  • RDB/AOF persistence is configured deliberately;
  • verification data survives restarts and reboots;
  • backups are stored outside the Redis VPS.

Operations

  • redis-server starts automatically;
  • used_memory and evicted_keys are monitored;
  • disk space does not run out;
  • logs are available for diagnostics;
  • upgrades are performed with a backup and post-upgrade verification.

Once these items are addressed, a standalone Redis instance on a VPS becomes a reasonably predictable production component: network access is restricted, privileges are separated, memory is controlled, and its behavior during restarts and RAM shortages is known in advance.

Conclusion

A standalone Redis instance on a single VPS is well suited for small and medium-sized projects where it is used as a cache, session store, rate limiter, or operational state store. However, it is important to configure ACLs in advance, restrict network access, set maxmemory, choose an appropriate eviction policy, and verify persistence after a restart.

If Redis starts storing critical data or service downtime becomes unacceptable, a single VPS is no longer sufficient. In that case, consider moving to replication, Sentinel, Redis Cluster, or managed Redis.

Thank you for your attention!

FAQ

Is persistence required if Redis is used only as a cache?

No. If all data in Redis can be retrieved again from the primary database or another source, persistence may not be needed at all.

After a restart, the cache will simply be repopulated.

However, RDB may still be useful even for a cache if warming it up again takes a long time or places a heavy load on the primary data store.

Which should you use: RDB or AOF?

It depends on your recovery requirements.

RDB creates periodic snapshots and is well suited to scenarios where losing changes made between snapshots is acceptable. AOF logs data-modifying operations, reducing the amount of recent data that could potentially be lost.

For most AOF use cases, the following is a good starting point:

 appendfsync everysec

If the benefits of both mechanisms are needed, RDB and AOF can be used simultaneously. 

Can RDB and AOF be enabled at the same time?

Yes.

For example, you can keep automatic RDB snapshots enabled while also enabling:

 appendonly yes
appendfsync everysec

Redis will then maintain an AOF while creating RDB snapshots in parallel.

This configuration requires more disk I/O and slightly complicates operations, so it may be excessive for a simple recoverable cache. 

Will data be lost after running systemctl restart redis-server?

If persistence is disabled, any data stored only in RAM will be lost.

If RDB or AOF is configured correctly and the relevant changes have been written to the persistence files, Redis will load them the next time it starts.

This is why it is best to test persistence in practice: create a test key, restart Redis, and then read the key again.

What happens when Redis reaches maxmemory?

It all depends on:

 maxmemory-policy

With noeviction, Redis will not automatically remove existing keys, and commands that require additional memory will start returning errors.

With eviction policies such as allkeys-lru or allkeys-lfu, Redis will start freeing up space by removing eligible keys. 

Which eviction policy should you choose for a standard cache?

For a general-purpose cache, it is usually best to start with:

 allkeys-lru

or:

 allkeys-lfu

LRU attempts to evict keys that have not been used recently, while LFU evicts keys that are used infrequently.

The specific choice depends on the workload: if retaining consistently high-demand hot keys is more important, LFU may be more effective. If the data exhibits stronger temporal locality, LRU often provides more predictable behavior. 

Why does Redis stop accepting writes when noeviction is enabled?

Because noeviction prevents Redis from freeing memory by automatically deleting existing keys.

When maxmemory is reached, operations that require additional memory may fail with an OOM error. However, existing data can still be read. 

Why is used_memory lower than the Redis process’s memory usage?

used_memory shows the memory allocated by Redis through the allocator, while the process’s RSS also reflects how memory is actually mapped by the operating system.

The difference may be due to fragmentation, allocator behavior, buffers, previous usage peaks, and other factors.

For comparison, check:

 redis-cli INFO memory

and the following metrics:

 used_memory
used_memory_rss
used_memory_peak
mem_fragmentation_ratio

used_memory_rss corresponds exactly to the resident set size reported by system utilities such as top and ps. 

Can maxmemory be changed without restarting Redis?

Yes, you can change the runtime configuration using:

 redis-cli CONFIG SET maxmemory 2gb

You can change the policy in the same way.

However, for the setting to persist after a restart, it must be saved in the persistent Redis configuration. Otherwise, the server may revert to the values specified in redis.conf after restarting.

Why leave some RAM available when Redis already has maxmemory configured?

Because maxmemory does not impose a hard limit on the RSS of the entire process and does not account for the operating system’s memory requirements.

Additional memory may be required for buffers, allocator overhead, fragmentation, and copy-on-write during an RDB snapshot or AOF rewrite.

Therefore, a setting such as:

 maxmemory 4gb

would be too aggressive on a VPS with exactly 4 GB of RAM.

Is it safe to expose port 6379 to the entire internet if Redis is password-protected?

This is not recommended.

Redis should be deployed on a trusted network, with access to the port restricted using at least bind, firewall rules, and ACLs. If the application is hosted on a different server, use a private network or allow access only from a specific trusted source IP address.

Redis itself is designed primarily to work with trusted clients, rather than to be accessed directly by arbitrary clients over the internet. 

Can Redis be used as the primary data store?

You can build systems in which Redis stores critical state, but in that case, it can no longer be treated as a standard disposable cache.

This requires an appropriate persistence strategy, a controlled eviction policy, backups, and a well-designed recovery process. If data loss is unacceptable, you should also consider the fault tolerance of the server itself, as a standalone Redis instance remains a single point of failure.

What should you do if the AOF is corrupted?

First, do not delete the original persistence file or run arbitrary recovery commands on the only available copy.

Make a copy of the AOF, stop writes to the affected instance, and only then begin recovery. It is also useful to have a separate backup, such as an RDB snapshot or a copy of the persistence data on another server.

This is another reason not to treat a local AOF as a complete replacement for backups.

Where Does Redis Store RDB and AOF Files?

The directory is specified by the following parameter:

 dir

You can check it as follows:

 redis-cli CONFIG GET dir

The RDB filename:

 redis-cli CONFIG GET dbfilename

The AOF settings:

 redis-cli CONFIG GET appendonly
redis-cli CONFIG GET appendfilename

For package-based installations, the active persistence files are typically stored in the Redis directory under /var/lib, but it is best to check the current configuration for the exact path.

Does Redis Need Swap?

A small amount of swap can be retained as an emergency fallback for the entire VPS, but it should not be used as an extension of RAM for Redis.

Redis is designed to work with data stored in RAM. If a significant portion of its memory pages is continually swapped out, latency can increase dramatically.

Therefore, if the working dataset regularly exceeds the available physical RAM, it is better to adjust maxmemory, reduce the dataset size, or increase the server’s memory capacity.

Sources

  1. Redis Docs — Install Redis Open Source on Linux
  2. Redis Docs — Redis Security
  3. Redis Docs — Key Eviction
  4. Redis Docs — Redis Persistence
  5. Redis Docs — INFO Command

Subscribe to our newsletter and receive articles and news

    Check out our other materials