Memcached was designed for trusted internal networks. It has no authentication by default, no per-key access control, and no per-connection logging. Anyone who can open a TCP connection to the daemon can read every cached value, overwrite any key, enumerate key names and sizes, and issue flush_all to wipe the entire cache in one command. Session tokens, PII, and application secrets cached in plaintext are all readable by that caller.

The common cause of accidental exposure is the bind address. Upstream memcached defaults to INADDR_ANY (0.0.0.0) on all interfaces. Debian and Ubuntu ship package defaults that override this to -l 127.0.0.1. Source compiles, minimal container images, and some other distributions inherit the upstream 0.0.0.0 default. A single -l flag, or a Docker port publish without an IP restriction, is the difference between a loopback-only cache and a service reachable from every interface the host owns.

This guide is a detection, audit, and lockdown procedure. It assumes you have shell access to a host running memcached and can read its startup configuration. Every command in the detection section is read-only. The lockdown section is marked, and the one destructive step (a restart) is called out explicitly.

What this exposes

On an unauthenticated memcached reachable from the network, a connected client gets the full administrative surface:

CapabilityImpact
Read any key (get, stats cachedump)Bulk extraction of session tokens, PII, internal identifiers
Write any key (set)Poison application state, inject cached responses, overwrite rate-limit counters
flush_allInstant cold-cache event and backend thundering herd
stats, stats items, stats slabsReveal working set size, item counts, slab distribution, key metadata
incr / decr on countersManipulate rate limiters, distributed locks, quotas

None of these operations are logged with a source address. Memcached stats expose no client IP information. The daemon will serve a full cache dump to an attacker and leave no trace in its own counters beyond aggregate byte and command totals. Detection has to come from OS-level connection tracking, not from memcached itself.

One historical amplifier makes an open UDP port especially dangerous. Before 1.5.6, UDP was enabled by default on port 11211, and CVE-2018-1000115 turned reachable instances into DDoS reflectors with amplification factors in the tens of thousands. UDP is off by default in modern releases, but older installations and configs that explicitly set a UDP port still exist in the wild.

Prerequisites

  • Shell access to the host running memcached, or the container host.
  • Permission to read the startup configuration: the systemd unit, /etc/memcached.conf (or distro equivalent), or docker inspect for containerized deployments.
  • nc (netcat), ss, and standard coreutils.
  • Read-only intent during the audit phase.

Detecting exposure

Run these checks in order. Each one closes a gap.

1. Confirm the bind address from the running process

# Inspect the actual listening sockets owned by memcached
ss -tlnp | grep 11211

Read the Local Address:Port column. 127.0.0.1:11211 means loopback only. 0.0.0.0:11211 (or *:11211) means every interface. A specific internal IP, for example 10.0.0.5:11211, means it is bound to one interface, which is better but still needs firewalling.

ss shows the truth at runtime. The config file may say one thing while a systemd override or a docker run -p 11211:11211 says another.

2. Check the startup configuration

# Common config locations and the flags that matter here
grep -nE -- '(-l|-U|-p|-S)' /etc/memcached.conf
# Systemd unit and any drop-in overrides
systemctl cat memcached

If -l is absent, the upstream default applies: bind to all interfaces. If -U is absent and the version is older than 1.5.6, UDP may be on.

For containers, the host-side binding is what matters:

# Show published ports for a container
docker inspect <container> --format '{{json .NetworkSettings.Ports}}'

-p 11211:11211 publishes on all host interfaces. -p 127.0.0.1:11211:11211 restricts to loopback. The in-container bind is almost always 0.0.0.0; the host-side publish rule is the real boundary.

3. Verify UDP is disabled

# Query the running daemon's effective settings
echo "stats settings" | nc -w 2 127.0.0.1 11211 | grep udpport

udpport 0 means UDP is off. Any non-zero value means UDP is listening, and on a routable host that is an immediate incident. Cross-check with the kernel:

ss -ulnp | grep 11211

4. Check whether authentication is enforced

echo "stats" | nc -w 2 127.0.0.1 11211 | grep -E 'auth_(cmds|errors)'

If both counters are zero, authentication is either unused or not compiled in. Absence of auth errors does not mean access is controlled; it means the daemon never asked for credentials. Confirm SASL support exists in the binary at all:

memcached -h 2>&1 | grep -E -- '-S'

If -S is not listed, the binary was built without SASL and no in-process authentication is possible.

5. Enumerate current connections and build an allowlist

Memcached will not tell you who is connected. Use OS tooling.

# All established TCP connections to the memcached port, with peer addresses
ss -tnp | grep ':11211'
# Count connections by source IP
ss -tn | grep ':11211' | awk '{print $5}' | cut -d: -f1 | sort | uniq -c | sort -rn

Compare the source IPs against your expected application server ranges. Anything unexpected is a misconfigured client, a scanner, or lateral movement. Maintain this list as an explicit allowlist and feed it into your firewall and your monitoring.

6. Look for exfiltration patterns

# Compare outbound bytes to read volume
echo "stats" | nc -w 2 127.0.0.1 11211 | grep -E 'bytes_written|cmd_get'

A spike in bytes_written without a proportional rise in cmd_get means either values got larger or someone is pulling bulk data. stats cachedump lets an attacker enumerate key names and sizes systematically, slab class by slab class. There is no built-in way to disable flush_all short of a firewall or source modification.

The decision flow for the audit:

flowchart TD
    A["ss: bound to 0.0.0.0?"] -->|Yes| B["On a routable interface?"]
    A -->|No, loopback or internal IP| F["Firewalled anyway?"]
    B -->|Yes| C["Exposed: treat as incident"]
    B -->|No| F
    C --> D["UDP on? stats settings udpport"]
    D -->|udpport non-zero| E["Amplification risk"]
    D -->|udpport 0| G["Auth enforced? auth_cmds / auth_errors"]
    E --> G
    G -->|No auth| H["Full read, write, flush to anyone"]
    G -->|SASL on| I["Verify binary protocol clients"]
    H --> J["Enumerate conns: ss -tn grep 11211"]
    I --> J
    F -->|Yes| K["Monitor for drift"]
    F -->|No| C

Locking it down

The fixes below are ordered by impact. The one restart required causes complete cache loss: every cached item disappears and the full read load shifts to your backend until the cache warms. Do not restart during a peak window without a warming plan.

Restrict the bind address

Set -l to a loopback or internal interface in your config file or systemd unit:

-l 127.0.0.1

For a cache shared across hosts on a private subnet, bind to the internal interface IP only. This is the single highest-value change. It requires a restart.

Firewall the port

Even with a restricted bind, run a host firewall that denies 11211 from all sources except the application tier. The firewall is defense in depth against future configuration drift and container republishing mistakes.

Disable UDP explicitly

In modern releases UDP is off by default, but make it explicit so an upgrade or a copied config cannot silently re-enable it:

-U 0

There is no legitimate reason to run UDP memcached on a production network in 2026. If any client uses UDP on purpose, move it to TCP.

Evaluate SASL, knowing its limits

SASL authentication (-S) requires the binary protocol. Enabling SASL will break any text-protocol client. SASL must also be compiled in, requires a configured SASL password database, and has had timing side-channel issues addressed in recent releases. Treat SASL as a layered control, not a replacement for network isolation.

ASCII auth via -Y / --auth-file exists in some builds but should not anchor a production security posture.

Fix container publishing

Replace -p 11211:11211 with -p 127.0.0.1:11211:11211, or do not publish the port at all and use a Docker network so only sibling containers can reach it. Re-run docker inspect to confirm.

Verifying the lockdown

Re-run the detection checks from the host, then test from an untrusted vantage point.

# From the host, after restart: confirm the new bind
ss -tlnp | grep 11211
# Confirm UDP is gone
ss -ulnp | grep 11211

From a host that should not have access, the port should refuse or time out:

nc -vz -w 2 <memcached-host-ip> 11211

From an allowed application host, a version probe should still succeed.

Common pitfalls

  • Distro defaults can mislead. Debian and Ubuntu look safe out of the box, but a source compile, a minimal container image, or a config copied from another host may inherit the upstream 0.0.0.0 default. Always verify with ss, never trust the package default.
  • Docker publish without an IP is wide open. -p 11211:11211 binds the published port on all host interfaces, including the public one if the host has one.
  • SASL breaks text clients. Forcing binary protocol is a client-side change. PHP clients, for example, must enable binary mode explicitly.
  • flush_all cannot be disabled in-process. Network isolation is the only control.
  • No source IP logging means no forensic trail. If you discover exposure, assume it has been abused and rotate any secrets that were cached in plaintext.

Signals to monitor

SignalWhy it mattersWarning sign
curr_connections source distribution via ssDetects unexpected clients talking to the cacheIPs outside the application-tier allowlist
udpport in stats settingsBinary flag for amplification exposureAny non-zero value on a routable host
auth_cmds / auth_errorsIndicates whether auth is even activeBoth zero means no auth is enforced
bytes_written vs cmd_get ratioBulk read pattern suggests enumeration or exfiltrationbytes_written rising faster than cmd_get
cmd_flushflush_all is destructive and unauthenticatedAny increment outside planned maintenance
Host firewall drops on 11211Confirms the port is being probedSustained drop rate from external ranges

How Netdata helps

Netdata’s per-second collection turns several of these checks into continuous signals rather than ad hoc audits.

  • The memcached collector surfaces curr_connections, cmd_flush, bytes_written, and auth_errors per second, so a sudden external reader or an unexpected flush_all appears immediately instead of at the next manual audit.
  • Anomaly detection on the bytes_written to cmd_get ratio catches the signature of bulk key enumeration.
  • Correlating memcached connection counts with host-level network metrics from the same agent helps distinguish legitimate application scaling from an unfamiliar source IP range.
  • Because memcached exposes no source IPs itself, pairing the memcached collector with the host’s socket and firewall metrics is how you reconstruct who is actually talking to the cache.