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.
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 oneblog(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
bindsays which addresses Redis listens on. These two mean "this server only".protected-modeis 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 offturns off the user without a name. Nobody gets in without a name and password.~shop:*means theshopuser can only read and change keys whose names start withshop:. The blog's data is off limits.-@dangerousblocks the commands a site never needs (next step).+flushdb +infoallows 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:
FLUSHDBempties 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_PREFIXmust match the key rule in step 3 (~shop:*). Otherwise every command fails withNOPERM.REDIS_DBandREDIS_CACHE_DBare the database numbers. Give every site numbers no other site uses. Here the shop uses 0 and 1, so the blog gets 2.REDIS_USERNAMEis inconfig/database.phpin 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, runphp artisan config:clearafterwards.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 +infoto that user's line, thenacl load.- Redis won't start after a change: read the end of
/var/log/redis/redis-server.log. A typo inusers.aclshows up there with its line number. After several failed starts, runsudo systemctl reset-failed redis-serverbefore you start it again. - "Permission denied" on the socket: the PHP user isn't in the
redisgroup yet, or PHP-FPM wasn't restarted (step 9).
Official sources
- Redis security (redis.io)
- ACL: users, passwords and command limits (redis.io)
- Redis in the official packages: Ubuntu 24.04 and Debian 13
- Redis in Laravel (laravel.com)
- Redis Object Cache settings (GitHub)
- Docker and firewalls (docs.docker.com)
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.