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 (
defaultuser state, any ACL user withnopass) - Dangerous-command accessibility (
FLUSHALL,FLUSHDB,DEBUG,CONFIG,SHUTDOWN) - TLS (
tls-portvs cleartextport) - 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
CONFIGcommand. 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_URLpoints 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 authentication —
ACL LISTcovers role posture; full replication-link verification is out of scope. redis-exporter/ Prometheus integration — separate concern.