What this script does

redis-check is a read-only Bash + redis-cli script that reports on each recommendation from the Redis hardening guide:

  • Network exposure (bind, protected-mode)
  • Authentication (default user state, any ACL user with nopass)
  • Dangerous-command accessibility (FLUSHALL, FLUSHDB, DEBUG, CONFIG, SHUTDOWN)
  • TLS (tls-port vs cleartext port)
  • Resource limits (maxmemory, maxmemory-policy, timeout)
  • Persistence (appendonly)

It uses only PING, INFO, CONFIG GET, ACL LIST, and COMMAND DOCS — no writes. If CONFIG has been renamed or disabled (recommended in the guide), the affected checks are skipped with a WARN rather than failing the whole run.

Exit codes:

Code Meaning
0 every check PASS
1 at least one FAIL
2 no FAIL but at least one WARN
3 could not authenticate

Usage

1# Download
2curl -fsSL https://stackharden.com/scripts/redis-check.sh -o redis-check
3chmod +x redis-check
4
5# Local Redis, default 127.0.0.1:6379:
6./redis-check
7
8# Remote, TLS, authenticated:
9REDIS_URL=rediss://auditor:[email protected]:6379 ./redis-check

The script masks any inline credentials in REDIS_URL before printing the audit header. The URL still travels through the environment — keep the password handling consistent with how you store other production secrets (a secrets manager, a .envrc, your CI’s variable store, etc.).

The script

The source rendered below is the same file served at /scripts/redis-check.sh — Hugo reads the downloadable directly at build time, so what you read here is what you would run.

  1#!/usr/bin/env bash
  2#
  3# redis-check — Read-only Redis security audit.
  4#
  5# Purpose:    Reports on every hardening recommendation from
  6#             https://stackharden.com/guides/redis-hardening/
  7# Companion:  /guides/redis-hardening/
  8# Tested on:  Ubuntu 24.04 LTS, Redis 7.2.x (apt from packages.redis.io)
  9# Author:     StackHarden — https://stackharden.com
 10# Date:       2026-05-16
 11# Licence:    MIT
 12#
 13# Usage:
 14#   ./redis-check
 15#   REDIS_URL=rediss://auditor:[email protected]:6379 ./redis-check
 16#
 17# Issues only PING, INFO, CONFIG GET, ACL LIST, and COMMAND DOCS. Does NOT
 18# write to Redis. If CONFIG has been renamed/disabled (per the guide's
 19# section 3), the affected checks are skipped with a WARN.
 20#
 21# Exit codes:
 22#   0 — every check PASS
 23#   1 — at least one FAIL
 24#   2 — no FAIL but at least one WARN
 25#   3 — could not authenticate
 26
 27set -euo pipefail
 28
 29URL="${REDIS_URL:-redis://127.0.0.1:6379}"
 30
 31if [ -t 1 ]; then
 32  C_RED=$'\033[31m'; C_YEL=$'\033[33m'; C_GRN=$'\033[32m'; C_OFF=$'\033[0m'
 33else
 34  C_RED=''; C_YEL=''; C_GRN=''; C_OFF=''
 35fi
 36
 37FAILS=0; WARNS=0; PASSES=0
 38pass() { PASSES=$((PASSES+1)); printf '  [%sPASS%s] %s\n' "$C_GRN" "$C_OFF" "$1"; }
 39warn() { WARNS=$((WARNS+1));  printf '  [%sWARN%s] %s\n' "$C_YEL" "$C_OFF" "$1"; }
 40fail() { FAILS=$((FAILS+1));  printf '  [%sFAIL%s] %s\n' "$C_RED" "$C_OFF" "$1"; }
 41hdr()  { printf '\n%s\n' "$1"; }
 42
 43rcli() { redis-cli -u "$URL" "$@" 2>/dev/null; }
 44cfg()  { rcli CONFIG GET "$1" 2>/dev/null | tail -n 1; }
 45
 46# Mask any inline credentials in the URL before printing.
 47DISPLAY_URL=$(echo "$URL" | sed -E 's|://[^@]+@|://***@|')
 48
 49printf 'Redis audit — %s\n' "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
 50printf 'Target: %s\n' "$DISPLAY_URL"
 51printf 'See https://stackharden.com/guides/redis-hardening/ for context.\n'
 52
 53# ---------- Connectivity ----------
 54PONG=$(rcli PING || true)
 55if [ "$PONG" != "PONG" ]; then
 56  printf '\nERROR: could not authenticate to Redis (PING did not return PONG).\n' >&2
 57  exit 3
 58fi
 59
 60VER=$(rcli INFO server | awk -F: '/^redis_version:/{print $2}' | tr -d '\r')
 61printf '\nRedis version: %s\n' "$VER"
 62
 63# CONFIG availability gate — many checks skip if CONFIG has been renamed.
 64CONFIG_OK="yes"
 65if ! rcli CONFIG GET maxmemory >/dev/null 2>&1; then
 66  CONFIG_OK="no"
 67fi
 68
 69# ---------- Network exposure ----------
 70hdr "Network exposure"
 71if [ "$CONFIG_OK" = "yes" ]; then
 72  BIND=$(cfg bind)
 73  case "$BIND" in
 74    "127.0.0.1 -::1"|"127.0.0.1"|"127.0.0.1 ::1")
 75      pass "bind = $BIND (loopback only)" ;;
 76    "0.0.0.0"|"*"|"")
 77      fail "bind = '${BIND:-empty}' — listening on every interface" ;;
 78    *)
 79      warn "bind = $BIND — verify these are private interfaces" ;;
 80  esac
 81
 82  PMODE=$(cfg protected-mode)
 83  case "$PMODE" in
 84    yes) pass "protected-mode = yes" ;;
 85    no)  warn "protected-mode = no — relies entirely on bind + auth" ;;
 86    *)   warn "protected-mode unknown ($PMODE)" ;;
 87  esac
 88else
 89  warn "CONFIG appears renamed or disabled — bind/protected-mode checks skipped"
 90fi
 91
 92# ---------- Authentication ----------
 93hdr "Authentication"
 94ACL_LIST=$(rcli ACL LIST 2>/dev/null || true)
 95DEFAULT_LINE=$(echo "$ACL_LIST" | grep -E '^user default ' || true)
 96case "$DEFAULT_LINE" in
 97  *" off "*)
 98    pass "ACL: default user is OFF" ;;
 99  *" on "*)
100    if echo "$DEFAULT_LINE" | grep -q ' nopass '; then
101      fail "ACL: default user is ON with 'nopass' — passwordless access"
102    else
103      pass "ACL: default user is ON and requires a password"
104    fi
105    ;;
106  *)
107    warn "ACL: could not determine state of 'default' user" ;;
108esac
109
110# Count any ACL users with the nopass flag (excluding default if disabled).
111NOPASS_COUNT=$(echo "$ACL_LIST" | grep -cE ' nopass ' || true)
112NOPASS_COUNT=${NOPASS_COUNT:-0}
113if [ "$NOPASS_COUNT" -eq 0 ]; then
114  pass "No ACL users have 'nopass'"
115else
116  warn "$NOPASS_COUNT ACL user(s) have 'nopass' — review"
117fi
118
119# ---------- Dangerous commands ----------
120hdr "Dangerous commands"
121for cmd in FLUSHALL FLUSHDB DEBUG CONFIG SHUTDOWN; do
122  EXISTS=$(rcli COMMAND DOCS "$cmd" 2>/dev/null | head -n 1)
123  if [ -z "$EXISTS" ]; then
124    pass "$cmd appears renamed or disabled"
125  else
126    case "$cmd" in
127      CONFIG|SHUTDOWN) warn "$cmd is accessible — consider renaming for ops-only use" ;;
128      *)               fail "$cmd is accessible — rename or disable" ;;
129    esac
130  fi
131done
132
133# ---------- TLS ----------
134hdr "TLS"
135if [ "$CONFIG_OK" = "yes" ]; then
136  TLSPORT=$(cfg tls-port); TLSPORT=${TLSPORT:-0}
137  PORT=$(cfg port);        PORT=${PORT:-6379}
138  if [ "$TLSPORT" -gt 0 ] && [ "$PORT" = "0" ]; then
139    pass "tls-port = $TLSPORT, cleartext port disabled (port = 0)"
140  elif [ "$TLSPORT" -gt 0 ]; then
141    warn "TLS enabled on $TLSPORT, but cleartext port still listening on $PORT"
142  else
143    fail "TLS not configured (tls-port = $TLSPORT)"
144  fi
145else
146  warn "CONFIG unavailable — TLS check skipped"
147fi
148
149# ---------- Resource limits ----------
150hdr "Resource limits"
151if [ "$CONFIG_OK" = "yes" ]; then
152  MAXMEM=$(cfg maxmemory); MAXMEM=${MAXMEM:-0}
153  if [ "$MAXMEM" -gt 0 ]; then
154    pass "maxmemory = $MAXMEM"
155  else
156    warn "maxmemory not set — Redis may consume all RAM"
157  fi
158
159  POLICY=$(cfg maxmemory-policy)
160  case "$POLICY" in
161    noeviction|allkeys-lru|allkeys-lfu|volatile-lru|volatile-lfu|allkeys-random|volatile-random|volatile-ttl)
162      pass "maxmemory-policy = $POLICY" ;;
163    *)
164      warn "maxmemory-policy = $POLICY" ;;
165  esac
166
167  TIMEOUT=$(cfg timeout); TIMEOUT=${TIMEOUT:-0}
168  if [ "$TIMEOUT" -gt 0 ]; then
169    pass "timeout = $TIMEOUT"
170  else
171    warn "timeout = 0 — idle clients are never disconnected"
172  fi
173else
174  warn "CONFIG unavailable — resource-limit checks skipped"
175fi
176
177# ---------- Persistence ----------
178hdr "Persistence"
179if [ "$CONFIG_OK" = "yes" ]; then
180  APPEND=$(cfg appendonly)
181  case "$APPEND" in
182    yes) pass "appendonly = yes (AOF enabled)" ;;
183    no)  warn "appendonly = no — relying on RDB snapshots only" ;;
184    *)   warn "appendonly = $APPEND" ;;
185  esac
186else
187  warn "CONFIG unavailable — persistence check skipped"
188fi
189
190# ---------- Summary ----------
191printf '\nSummary: %sPASS %d%s · %sWARN %d%s · %sFAIL %d%s\n' \
192  "$C_GRN" "$PASSES" "$C_OFF" \
193  "$C_YEL" "$WARNS"  "$C_OFF" \
194  "$C_RED" "$FAILS"  "$C_OFF"
195
196if   [ "$FAILS" -gt 0 ]; then exit 1
197elif [ "$WARNS" -gt 0 ]; then exit 2
198else                          exit 0
199fi

Download redis-check.sh · Companion guide

Limitations

  • CONFIG access required for most checks. If the guide’s “rename or disable CONFIG” recommendation has been applied, the script needs to run as a role that knows the renamed CONFIG command. Otherwise the script reports CONFIG as unavailable and skips those checks with a WARN.
  • Cluster mode is not specifically tested. The script connects to one node; in cluster mode it audits whichever node REDIS_URL points at. Run it against each node, or each shard primary, to get a fleet-wide picture.

What this script deliberately does not cover

  • Sentinel / cluster topology — separate guide.
  • Replication authenticationACL LIST covers role posture; full replication-link verification is out of scope.
  • redis-exporter / Prometheus integration — separate concern.