How to secure Redis on your own PHP server (Ubuntu and Debian)

Redis keeps your cache, sessions and queued jobs. Keep it on the server only, give each site its own name and password, and block the commands a site never needs.

Plan steps 3, 7 and 8 together. Once Redis asks for a password, every site that connects without one gets an error. Have the new passwords ready, then restart Redis and update the sites right after.

Why it matters

An open Redis lets anyone read your sessions and cached data, or delete all of it with one command. Bots scan the internet for Redis on port 6379 all day. Even when Redis is private, any program on the server can talk to it without a password, so one hacked site can read another site's sessions.

Before you start

  • The examples use the Redis that comes with the system: Redis 7.0 on Ubuntu 24.04 and Redis 8.0 on Debian 13. Install it with sudo apt install redis-server.
  • The site is called shop (Laravel) and a second one blog (WordPress). Change the names to yours.
  • Keep a copy of the settings: sudo cp /etc/redis/redis.conf /root/redis.conf.backup

1. See what you have

redis-server --version
sudo ss -ltnp | grep 6379

Good: only 127.0.0.1:6379 and [::1]:6379. That means only the server itself can connect. Bad: 0.0.0.0:6379, *:6379 or [::]:6379. Then Redis listens on every network, and step 2 is urgent.

Now see the main settings:

sudo grep -nE '^(bind|protected-mode|port|requirepass|aclfile|unixsocket)' /etc/redis/redis.conf

2. Keep Redis on the server

On Ubuntu and Debian the defaults are already right. Make sure these two lines are in /etc/redis/redis.conf, exactly like this:

bind 127.0.0.1 -::1
protected-mode yes
  • bind says which addresses Redis listens on. These two mean "this server only".
  • protected-mode is a safety net. If Redis does listen on a public address and has no password, it refuses everyone who isn't on the server itself.

If you changed something, restart Redis and check again with the ss command from step 1:

sudo systemctl restart redis-server

Does another server really need Redis, for example a second web server? Then allow only that one address in the firewall (step 5), and do step 3 first. Never open Redis to the whole internet.

3. Give each site its own name and password

Since Redis 6 you can make users, each with a password and its own limits. Redis calls this ACL. We make three users: admin for you, and one for each site.

Make a password for each user. Run this once per user and write down both lines:

PASS=$(openssl rand -hex 32); echo "Password: $PASS"; echo "Hash: $(printf %s "$PASS" | sha256sum | cut -d' ' -f1)"

The password goes into the site's settings (steps 7 and 8). Only the hash goes into Redis, so the Redis file holds no passwords. Now create /etc/redis/users.acl:

sudo nano /etc/redis/users.acl

Put in these lines, with your own hashes after the #:

user default off
user admin on #ADMIN_HASH ~* &* +@all
user shop on #SHOP_HASH ~shop:* &shop:* +@all -@dangerous +flushdb +info
user blog on #BLOG_HASH ~blog:* &blog:* +@all -@dangerous +flushdb +info

What each part means:

  • user default off turns off the user without a name. Nobody gets in without a name and password.
  • ~shop:* means the shop user can only read and change keys whose names start with shop:. The blog's data is off limits.
  • -@dangerous blocks the commands a site never needs (next step).
  • +flushdb +info allows two of them back, because Laravel and WordPress need them (steps 7 and 8).

Let only Redis read the file, and tell Redis to use it. In /etc/redis/redis.conf, find the line # aclfile /etc/redis/users.acl and remove the # in front. Then:

sudo chown redis:redis /etc/redis/users.acl
sudo chmod 640 /etc/redis/users.acl
sudo systemctl restart redis-server

Test it. The first command should fail with NOAUTH Authentication required. The second asks for the admin password and should print PONG:

redis-cli ping
redis-cli --user admin --askpass ping

Use users, not the older requirepass line. With requirepass, every site shares one password and can do everything. Don't use both: in our test, the users file won and the requirepass password didn't work.

Changing users later? Edit the file and load it again. No restart needed:

redis-cli --user admin --askpass acl load

4. Block dangerous commands

-@dangerous in step 3 already did this for the site users. It blocks commands such as CONFIG (change Redis settings), FLUSHALL (delete everything), KEYS (list every key), MONITOR (watch everything others send) and SHUTDOWN. We tested it:

$ redis-cli --user shop --askpass flushall
NOPERM User shop has no permissions to run the 'flushall' command

Older guides tell you to use rename-command in redis.conf instead. Redis now calls that way deprecated and points to users. Users are also better: you keep full access as admin, while the sites are limited.

Two things the limits don't stop. Keep them in mind if your sites belong to different people:

  • FLUSHDB empties one whole Redis database. That's why each site gets its own database number in steps 7 and 8. A hacked site could still empty another site's cache, but it can't read it, and a cache fills up again on its own.
  • A site can see the names of other sites' keys with SCAN, but not what's in them.

5. Check the firewall

Redis listens on the server only, so the firewall is a second lock. With UFW (see our firewall guide), port 6379 should not be open:

sudo ufw status numbered

If you see a line with 6379, delete it by its number: sudo ufw delete 3 (use your number). If another server of yours must reach Redis, allow only that address: sudo ufw allow from 203.0.113.10 to any port 6379 proto tcp.

Running Redis in Docker? A port published with -p 6379:6379 skips UFW completely, so Redis is open to the internet even if UFW says no. Publish it as -p 127.0.0.1:6379:6379 instead. See Docker's firewall notes.

6. Check from outside

Run this on your own computer, not on the server. Use the server's public IP address:

nc -zv -w3 203.0.113.5 6379
  • "Connection refused" or "timed out": good. Nobody outside can reach Redis.
  • "succeeded" or "open": Redis is reachable from the internet. Go back to steps 2 and 5 now.

If you have redis-cli on your computer, redis-cli -h 203.0.113.5 ping shows the same thing. NOAUTH or DENIED means Redis answered, so it's reachable, even if it doesn't let you in.

7. Connect Laravel

Laravel can send a user name and a password. In the site's .env file:

REDIS_CLIENT=phpredis
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
REDIS_USERNAME=shop
REDIS_PASSWORD=the-shop-password
REDIS_PREFIX=shop:
REDIS_DB=0
REDIS_CACHE_DB=1

CACHE_STORE=redis
SESSION_DRIVER=redis
QUEUE_CONNECTION=redis
  • REDIS_PREFIX must match the key rule in step 3 (~shop:*). Otherwise every command fails with NOPERM.
  • REDIS_DB and REDIS_CACHE_DB are the database numbers. Give every site numbers no other site uses. Here the shop uses 0 and 1, so the blog gets 2.
  • REDIS_USERNAME is in config/database.php in new Laravel apps. In an older app, check that the file has a 'username' => env('REDIS_USERNAME') line.

Clear the saved settings and test:

php artisan config:clear
php artisan tinker --execute 'Cache::put("hello", "world", 60); echo Cache::get("hello");'

It should print world. Restart the queue workers so they use the new settings: php artisan queue:restart.

In our test, cache, sessions and queued jobs all worked. php artisan cache:clear needs the +flushdb from step 3. Without it, the command stops with NOPERM, and so does php artisan optimize:clear in your deploy script.

8. Connect WordPress

The usual plugin is Redis Object Cache. Add these lines to wp-config.php, above the line that says "That's all, stop editing!":

define( 'WP_REDIS_HOST', '127.0.0.1' );
define( 'WP_REDIS_PORT', 6379 );
define( 'WP_REDIS_PASSWORD', [ 'blog', 'the-blog-password' ] );
define( 'WP_REDIS_PREFIX', 'blog:' );
define( 'WP_REDIS_DATABASE', 2 );

The password is a pair: the user name first, then the password. Then turn on the cache in the plugin's settings page, or with WP-CLI, and check:

wp redis enable
wp redis status

Status: Connected means it works. Open the site in a browser too.

Why +flushdb +info? We tested the plugin (version 3.0) with only -@dangerous. Turning it on failed, because it empties its database first. Worse, once it was on, every page of the site showed an error, because the plugin asks Redis for INFO on every page load. If that happens to you, delete wp-content/object-cache.php and the site works again without the cache.

9. Optional: a socket instead of a port

Redis can also listen on a socket file. A socket is a little faster, and with port 0 Redis doesn't use the network at all. In /etc/redis/redis.conf:

unixsocket /run/redis/redis-server.sock
unixsocketperm 770

To use it, the site's PHP user must join the redis group, and PHP-FPM needs a restart (use your PHP version):

sudo usermod -aG redis www-data
sudo systemctl restart redis-server php8.3-fpm

In Laravel, set REDIS_HOST=/run/redis/redis-server.sock and REDIS_PORT=0. In WordPress, use WP_REDIS_SCHEME 'unix' and WP_REDIS_PATH with the socket path.

Only do this with one site, or sites you fully trust. In our test, a member of the redis group could also read Redis's saved data file in /var/lib/redis/, with every site's data in it. With several sites, stay with 127.0.0.1 and a user for each site.

If something goes wrong

  • NOAUTH Authentication required: the site has no user name or password set. Check steps 7 and 8.
  • WRONGPASS: wrong user name or password. Copy the password again, without spaces. In Laravel, run php artisan config:clear afterwards.
  • NOPERM ... access a key: the site's prefix doesn't match its key rule. REDIS_PREFIX=shop: needs ~shop:*.
  • NOPERM ... run the 'flushdb' command (or 'info'): add +flushdb +info to that user's line, then acl load.
  • Redis won't start after a change: read the end of /var/log/redis/redis-server.log. A typo in users.acl shows up there with its line number. After several failed starts, run sudo systemctl reset-failed redis-server before you start it again.
  • "Permission denied" on the socket: the PHP user isn't in the redis group yet, or PHP-FPM wasn't restarted (step 9).

Official sources

Next steps: keep your database private too, with our MySQL and MariaDB guide, give each site its own user and PHP-FPM pool, and work through the security checklist for a new PHP server.

ServerNest is being built to set up Redis this way for you: on the server only, a separate Redis user and password for each app, and the firewall checked. Join early access to try it first.

← Back to the security blog