尧图精选

Redis TLS 加密传输实战:从证书体系到生产部署

🕒 发布时间:2026/9/19 8:16:42 📁 来源:尧图网络
Redis 在默认配置下客户端与服务端之间的所有通信都是明文的。这意味着只要有人能在网络链路上抓包你存进去的 session、token、业务数据就全部暴露了。很多团队在内网环境下觉得没必要上 TLS但内网横向渗透的案例越来越多一旦边界被突破明文 Redis 就是最容易被利用的跳板。这篇文章面向的是有一定 Redis 运维基础、需要在生产环境启用 TLS 加密传输的工程师我会从证书体系设计开始一步步讲到 Redis 服务端和客户端的完整配置以及我在实际部署中踩过的那些坑。1. 为什么 Redis 的 TLS 不是配一下就行1.1 明文 Redis 的真实风险场景先说一个我亲身经历的事。某次内部安全扫描扫描器直接连上了我们一台测试环境的 Redis端口 6379 对外开放且无密码扫描报告里赫然写着可未授权访问。虽然那只是测试环境但如果这是生产环境攻击者可以通过CONFIG SET dir和CONFIG SET dbfilename写入任意文件配合其他手段直接拿到服务器权限。即使加了密码明文传输依然意味着密码本身在网络上裸奔。Redis 的通信模型是典型的 C/S 架构客户端发送命令、服务端返回结果全程走 TCP。在没有 TLS 的情况下任何中间节点——交换机镜像口、路由器、甚至同机房的其他机器——都能完整还原你的每一条命令。对于存储了用户会话、支付回调地址、内部服务发现信息的 Redis 实例来说这等同于把数据库密码写在便签上贴在显示器旁边。TLS 解决的核心问题有三个机密性传输内容加密抓包看不到明文、完整性内容被篡改能被发现、身份验证客户端能确认连的是真服务端服务端也能确认客户端身份。Redis 从 6.0 版本开始原生支持 TLS不需要额外编译第三方模块这是目前最推荐的方案。1.2 Redis TLS 支持的版本分水岭Redis 6.0 是一个关键节点。在 6.0 之前想让 Redis 走 TLS只能靠 stunnel 或者 spiped 这类隧道工具做一层代理转发。这种方案的问题在于多了一层进程运维复杂度上升代理本身可能成为瓶颈而且代理到 Redis 之间仍然是明文只是把加密边界外移了。Redis 6.0 原生支持 TLS 之后配置变得直接很多。服务端通过tls-port监听加密端口客户端通过--tls参数连接。但要注意原生 TLS 支持依赖于 OpenSSL 库编译时需要确保系统有合适的 OpenSSL 版本。如果你用的是 Docker 官方镜像从 6.0 开始就已经内置了 TLS 支持直接用即可。提示Redis 7.x 在 TLS 方面没有大的架构变化主要是性能优化和配置项的微调。如果你还在用 5.x 或更早版本建议先升级到 6.0 再考虑 TLS 方案。1.3 证书体系自签还是 CA 签发这是很多人纠结的第一个问题。我的建议很明确内部服务用自建 CA 签发证书不要用自签名证书self-signed直接上生产。自签名证书的问题是每个证书都是自己的 CA客户端验证时要么关闭验证等于没验证要么把每个证书都加入信任列表维护噩梦。正确的做法是建一个内部 CA用 CA 给服务端和客户端分别签发证书。这样客户端只需要信任 CA 根证书就能验证所有由该 CA 签发的服务端证书。具体来说你需要准备这些文件文件用途持有方ca.crtCA 根证书服务端和所有客户端ca.keyCA 私钥仅 CA 管理机绝不外传redis-server.crt服务端证书Redis 服务端redis-server.key服务端私钥Redis 服务端redis-client.crt客户端证书每个客户端redis-client.key客户端私钥每个客户端如果不需要双向认证mTLS客户端证书可以省略但生产环境我强烈建议开启双向认证。原因很简单只验证服务端的话任何能连上端口的人都能尝试发送命令TLS 只保护了传输过程没有解决访问控制问题。2. 用 OpenSSL 搭建内部 CA 并签发证书2.1 生成 CA 根证书的完整命令先建目录结构把 CA 和服务端、客户端的证书分开存放避免混乱mkdir -p /etc/redis/tls/{ca,server,client} cd /etc/redis/tls/ca生成 CA 私钥。这里用 RSA 4096 位虽然 ECDSA 性能更好但 RSA 的兼容性最广不容易在某些老客户端上出问题openssl genrsa -out ca.key 4096生成 CA 自签名根证书有效期设 10 年。CA 证书不需要频繁更换设长一点减少维护成本openssl req -x509 -new -nodes -sha256 -days 3650 \ -key ca.key \ -subj /CCN/STBeijing/LBeijing/OInternal/CNRedis-Internal-CA \ -out ca.crt-subj里的字段按你实际情况填CN 写一个能标识用途的名字就行。这个 CA 证书就是整个信任链的根后面所有证书都由它签发。2.2 服务端证书的 SAN 配置要点生成服务端私钥和 CSR证书签名请求cd /etc/redis/tls/server openssl genrsa -out redis-server.key 4096 openssl req -new -key redis-server.key \ -subj /CCN/STBeijing/LBeijing/OInternal/CNredis.internal \ -out redis-server.csr关键来了必须配置 SANSubject Alternative Name。现代 TLS 客户端包括 Redis 自己用的 OpenSSL在验证证书时优先检查 SAN 字段CN 字段在很多场景下已经被忽略。如果你只填了 CN 没填 SAN客户端会报证书主机名不匹配。创建一个扩展配置文件server-ext.cnf[v3_req] basicConstraints CA:FALSE keyUsage digitalSignature, keyEncipherment extendedKeyUsage serverAuth subjectAltName alt_names [alt_names] DNS.1 redis.internal DNS.2 redis-master.internal DNS.3 localhost IP.1 10.0.1.100 IP.2 127.0.0.1把实际会用到的域名和 IP 都列进去。注意如果客户端用 IP 连接证书里必须有对应的 IP SAN否则验证失败。我见过有人只写了域名结果客户端用 IP 连就报错排查半天。用 CA 签发服务端证书openssl x509 -req -in redis-server.csr \ -CA ../ca/ca.crt -CAkey ../ca/ca.key -CAcreateserial \ -out redis-server.crt -days 825 -sha256 \ -extfile server-ext.cnf -extensions v3_req有效期设 825 天是有讲究的——这是很多浏览器和 TLS 库对证书有效期的上限建议值虽然 Redis 客户端不一定强制但养成习惯没坏处。2.3 客户端证书与双向认证客户端证书的生成流程类似但extendedKeyUsage要改成clientAuthcd /etc/redis/tls/client openssl genrsa -out redis-client.key 4096 openssl req -new -key redis-client.key \ -subj /CCN/STBeijing/LBeijing/OInternal/CNredis-client \ -out redis-client.csr创建client-ext.cnf[v3_req] basicConstraints CA:FALSE keyUsage digitalSignature extendedKeyUsage clientAuth签发openssl x509 -req -in redis-client.csr \ -CA ../ca/ca.crt -CAkey ../ca/ca.key -CAcreateserial \ -out redis-client.crt -days 825 -sha256 \ -extfile client-ext.cnf -extensions v3_req每个客户端实例最好用独立的客户端证书这样在服务端日志里能区分是哪个客户端连上来的出问题也好排查。如果客户端数量多可以写个脚本批量生成但私钥一定要每个客户端独立绝不能共用。2.4 证书权限与文件保护证书生成完之后权限设置是容易被忽略的一步。私钥文件如果权限过宽等于白做加密chmod 700 /etc/redis/tls/ca chmod 600 /etc/redis/tls/ca/ca.key chmod 644 /etc/redis/tls/ca/ca.crt chmod 600 /etc/redis/tls/server/redis-server.key chmod 644 /etc/redis/tls/server/redis-server.crt chmod 600 /etc/redis/tls/client/redis-client.key chmod 644 /etc/redis/tls/client/redis-client.crtRedis 服务端进程通常以redis用户运行需要确保该用户对服务端证书和私钥有读权限。可以用chown redis:redis把服务端证书目录的属主改掉。注意CA 私钥ca.key在签发完所有证书后应该离线保存或者放到专门的密钥管理系统中不要留在 Redis 服务器上。一旦 CA 私钥泄露攻击者可以签发任意证书冒充你的服务端。3. Redis 服务端 TLS 配置逐项拆解3.1 redis.conf 中的 TLS 相关配置Redis 6.0 的 TLS 配置项都在redis.conf里核心配置如下# 关闭普通端口只保留 TLS 端口推荐 port 0 tls-port 6380 # 证书文件路径 tls-cert-file /etc/redis/tls/server/redis-server.crt tls-key-file /etc/redis/tls/server/redis-server.key tls-ca-cert-file /etc/redis/tls/ca/ca.crt tls-ca-cert-dir /etc/redis/tls/ca # 是否要求客户端也提供证书双向认证 tls-auth-clients yes # TLS 协议版本 tls-protocols TLSv1.2 TLSv1.3 # 加密套件 tls-ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256 # TLS 1.3 专用套件 tls-ciphersuites TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256 # 会话复用提升性能 tls-session-caching yes tls-session-cache-size 20480 tls-session-cache-timeout 300 # 是否允许同时监听普通端口和 TLS 端口 tls-replication yes逐项说明几个关键点。port 0表示关闭非加密端口。如果你的迁移策略是逐步切换可以先保留普通端口等所有客户端都切到 TLS 后再关。但最终状态一定要关掉普通端口否则 TLS 就形同虚设——攻击者直接连普通端口就行了。tls-auth-clients yes开启双向认证。如果设成no服务端不验证客户端证书任何能建立 TLS 连接的客户端都能发命令。设成optional则是有证书就验证没证书也放行适合过渡期。tls-protocols明确指定 TLS 1.2 和 1.3。不要用TLSv1或TLSv1.1这两个版本已经被证实存在安全漏洞很多安全扫描会直接报高危。如果你的客户端比较老只支持 TLS 1.0/1.1正确做法是升级客户端而不是降级服务端。3.2 加密套件的选择逻辑加密套件cipher suites决定了 TLS 握手时用什么算法组合。上面配置里的套件有几个共同特点都使用 ECDHE 做密钥交换支持前向保密、都使用 AEAD 类加密算法GCM 或 ChaCha20-Poly1305、都使用 SHA256 以上做哈希。前向保密Forward Secrecy意味着即使服务端私钥将来泄露攻击者也无法解密之前抓到的历史流量。ECDHE 就是实现前向保密的关键。如果你用了 RSA 密钥交换的套件比如AES256-GCM-SHA384不带 ECDHE 前缀就没有前向保密能力。TLS 1.3 的套件是独立的配置项tls-ciphersuites因为 TLS 1.3 的套件命名和协商机制跟 1.2 不同。TLS 1.3 只保留了 AEAD 类算法安全性更高配置也简单。提示如果你不确定该用什么套件可以用openssl ciphers -v ECDHEAESGCM:ECDHECHACHA20查看 OpenSSL 支持的套件列表再结合 Mozilla 的 SSL 配置生成器搜索 Mozilla SSL Configuration Generator选择适合你兼容性要求的级别。3.3 性能影响与优化参数TLS 握手是有成本的主要体现在 CPU 消耗上。全握手需要做非对称加密运算比对称加密慢几个数量级。优化手段主要有两个会话复用和硬件加速。tls-session-caching yes开启会话缓存客户端再次连接时可以复用之前的会话密钥跳过完整的非对称运算。tls-session-cache-size设成 20480 意味着缓存约 20480 个会话对于大多数场景够用了。tls-session-cache-timeout 300表示会话缓存 5 分钟可以根据客户端连接频率调整。如果服务器 CPU 支持 AES-NI 指令集现代 x86 基本都支持OpenSSL 会自动利用硬件加速AES-GCM 的性能损耗可以降到 5% 以内。你可以用openssl speed -evp aes-256-gcm测试一下自己机器的 AES 性能。实测数据供参考在一台 4 核 8G 的云服务器上开启 TLS 后 Redis 的 QPS 从约 10 万降到约 8.5 万损耗约 15%。如果开启会话复用持续压测下损耗可以控制在 10% 以内。对于绝大多数业务场景这个损耗完全可以接受。3.4 主从复制链路的 TLS 配置如果你的 Redis 是主从架构主从之间的复制流量同样需要加密。tls-replication yes这个配置就是干这个的。开启后从节点连接主节点时会使用 TLS。从节点的配置需要额外指定tls-replication yes tls-cert-file /etc/redis/tls/server/redis-server.crt tls-key-file /etc/redis/tls/server/redis-server.key tls-ca-cert-file /etc/redis/tls/ca/ca.crt然后在replicaof命令或配置中指定主节点的 TLS 端口replicaof redis-master.internal 6380注意主从双方的证书最好由同一个 CA 签发这样互相验证时只需要信任同一个根证书。如果主从用了不同的 CA需要在tls-ca-cert-dir目录下同时放入两个 CA 证书。4. 客户端连接 TLS Redis 的实操细节4.1 redis-cli 的 TLS 连接方式命令行工具是最直接的验证手段redis-cli --tls \ --cert /etc/redis/tls/client/redis-client.crt \ --key /etc/redis/tls/client/redis-client.key \ --cacert /etc/redis/tls/ca/ca.crt \ -h redis.internal -p 6380如果服务端没开双向认证可以省略--cert和--key。--cacert指定 CA 根证书用于验证服务端证书。如果证书里的 SAN 跟你连接用的主机名不匹配会报证书验证失败。调试阶段可以用--insecure跳过证书验证但绝对不要在生产环境用这个参数。它等于关闭了身份验证只保留了加密功能中间人攻击依然可行。4.2 各语言客户端配置示例Python 的 redis-py 客户端import redis r redis.Redis( hostredis.internal, port6380, sslTrue, ssl_ca_certs/etc/redis/tls/ca/ca.crt, ssl_certfile/etc/redis/tls/client/redis-client.crt, ssl_keyfile/etc/redis/tls/client/redis-client.key, ssl_cert_reqsrequired, decode_responsesTrue ) r.ping()ssl_cert_reqsrequired表示强制验证服务端证书。如果设成none就等同于--insecure不要这么干。Java 的 Jedis 客户端import redis.clients.jedis.Jedis; import redis.clients.jedis.DefaultJedisClientConfig; import redis.clients.jedis.HostAndPort; DefaultJedisClientConfig config DefaultJedisClientConfig.builder() .ssl(true) .sslSocketFactory(SSLSocketFactoryUtil.createSSLSocketFactory( /etc/redis/tls/ca/ca.crt, /etc/redis/tls/client/redis-client.crt, /etc/redis/tls/client/redis-client.key )) .build(); try (Jedis jedis new Jedis(new HostAndPort(redis.internal, 6380), config)) { jedis.ping(); }Java 这边需要自己构建 SSLSocketFactory把 CA 证书加载到 TrustManager把客户端证书和私钥加载到 KeyManager。代码稍长但逻辑清晰。Go 的 go-redis 客户端import ( crypto/tls crypto/x509 os github.com/redis/go-redis/v9 ) func newRedisClient() *redis.Client { caCert, _ : os.ReadFile(/etc/redis/tls/ca/ca.crt) caCertPool : x509.NewCertPool() caCertPool.AppendCertsFromPEM(caCert) clientCert, _ : tls.LoadX509KeyPair( /etc/redis/tls/client/redis-client.crt, /etc/redis/tls/client/redis-client.key, ) tlsConfig : tls.Config{ RootCAs: caCertPool, Certificates: []tls.Certificate{clientCert}, MinVersion: tls.VersionTLS12, } return redis.NewClient(redis.Options{ Addr: redis.internal:6380, TLSConfig: tlsConfig, }) }Go 的写法比较直观MinVersion: tls.VersionTLS12强制最低 TLS 1.2避免协商到低版本。4.3 连接池与 TLS 会话的关系使用连接池的客户端比如 Java 的 JedisPool、Python 的 ConnectionPool要注意连接池里的每个连接在创建时都会做一次 TLS 握手。如果连接池配置了频繁的创建销毁比如maxIdle设得很小、minEvictableIdleTime设得很短会导致频繁的 TLS 全握手性能下降明显。我的建议是连接池的minIdle设成预期并发量的 1/4 到 1/2让连接尽量复用maxIdle设成跟maxTotal一样大避免连接被频繁回收。同时服务端开启会话缓存即使连接重建也能复用会话密钥。另外有些客户端库支持 TLS 会话票据session ticket这是 TLS 1.3 的特性比服务端会话缓存更高效。如果你的客户端和服务端都支持 TLS 1.3优先走这条路。5. 上线过程中最容易踩的五个坑5.1 证书 SAN 缺失导致的验证失败这是最高频的问题。现象是客户端报certificate verify failed或者hostname mismatch。根因就是证书里没有包含客户端实际连接时使用的主机名或 IP。排查方法openssl x509 -in redis-server.crt -text -noout | grep -A1 Subject Alternative Name如果输出为空说明证书没有 SAN 字段需要重新签发。如果有 SAN 但跟你连接用的地址不匹配要么改连接地址要么重新签发包含正确 SAN 的证书。注意用 IP 连接时证书的 SAN 里必须有 IP 类型的条目IP.1 10.0.1.100DNS 类型的条目对 IP 连接无效。5.2 私钥格式不兼容OpenSSL 生成的私钥默认是 PKCS#8 格式以-----BEGIN PRIVATE KEY-----开头但有些客户端库或老版本工具需要 PKCS#1 格式以-----BEGIN RSA PRIVATE KEY-----开头。如果客户端报私钥解析错误可以转换格式# PKCS#8 转 PKCS#1 openssl rsa -in redis-client.key -out redis-client-pkcs1.key -traditional # PKCS#1 转 PKCS#8 openssl pkcs8 -topk8 -inform PEM -in redis-client-pkcs1.key -outform PEM -nocrypt -out redis-client.key反过来有些场景需要把证书和私钥合并成一个 PEM 文件cat redis-client.crt redis-client.key redis-client-combined.pem5.3 时间不同步导致的证书校验异常TLS 证书验证会检查当前时间是否在证书的有效期内。如果服务器时间偏差太大比如差了几个小时甚至几天会导致证书被判定为未生效或已过期。上线前务必确认所有节点的 NTP 同步正常timedatectl status # 或 ntpq -p我遇到过一台测试机时间慢了 3 天导致刚签发的证书被判定为尚未生效排查了半小时才发现是时间问题。5.4 安全扫描报 TLS 1.0/1.1 或弱加密套件很多安全扫描工具会检测服务端支持的 TLS 协议版本和加密套件。如果扫描报告里出现 TLS 1.0/1.1 enabled 或 weak cipher suites说明配置没生效或者被覆盖了。检查方法openssl s_client -connect redis.internal:6380 -tls1_1如果这个命令能成功建立连接说明 TLS 1.1 还开着。确认redis.conf里tls-protocols只写了TLSv1.2 TLSv1.3并且配置已经生效改完要重启 Redis 或CONFIG REWRITE。关于 CVE-2016-2183SWEET32 攻击它影响的是使用 3DES 的加密套件。只要你的套件列表里没有DES-CBC3-SHA这类 3DES 套件就不会报这个漏洞。上面推荐的套件列表已经排除了 3DES。5.5 客户端连接超时但无明显报错有时候客户端连不上 TLS 端口但错误信息很模糊只报超时。可能的原因有几个防火墙没放行 TLS 端口注意端口从 6379 变成了 6380、Redis 没监听在正确的地址上、证书加载失败导致 Redis 启动时就没起来。排查顺序# 1. 确认 Redis 进程在跑 ps aux | grep redis-server # 2. 确认端口在监听 ss -tlnp | grep 6380 # 3. 查看 Redis 日志 tail -50 /var/log/redis/redis-server.log # 4. 从客户端机器测试连通性 openssl s_client -connect redis.internal:6380 -CAfile /etc/redis/tls/ca/ca.crtopenssl s_client是最有用的诊断工具它能显示完整的 TLS 握手过程包括协商的协议版本、加密套件、证书链等。如果握手失败错误信息会直接告诉你卡在哪一步。6. 证书轮换与长期维护策略6.1 证书到期前的自动化检查证书过期是运维事故的常见原因。我的做法是写一个简单的检查脚本每天跑一次距离到期 30 天开始告警#!/bin/bash CERT_FILE/etc/redis/tls/server/redis-server.crt DAYS_WARN30 EXPIRY_DATE$(openssl x509 -enddate -noout -in $CERT_FILE | cut -d -f2) EXPIRY_EPOCH$(date -d $EXPIRY_DATE %s) NOW_EPOCH$(date %s) DAYS_LEFT$(( (EXPIRY_EPOCH - NOW_EPOCH) / 86400 )) if [ $DAYS_LEFT -lt $DAYS_WARN ]; then echo WARNING: Certificate expires in $DAYS_LEFT days # 这里接入你的告警系统 fi这个脚本可以放到 cron 里每天执行也可以接入 Prometheus 的 blackbox_exporter 做证书过期监控。6.2 不停机轮换证书的步骤Redis 支持运行时重载 TLS 证书不需要重启。步骤是用同一个 CA 签发新证书保持 CA 不变客户端不需要更新 CA把新证书文件放到对应路径覆盖旧文件执行CONFIG SET tls-cert-file /path/to/new.crt和CONFIG SET tls-key-file /path/to/new.key执行CONFIG REWRITE把配置持久化但要注意CONFIG SET重载证书后已建立的连接不会自动断开它们仍然使用旧证书的会话。新连接才会用新证书。所以轮换后要观察一段时间确认新连接正常后再清理旧证书。如果是要更换 CA比如 CA 私钥泄露需要紧急更换过程会复杂一些需要先把新 CA 证书加入客户端的信任列表再签发新服务端证书等所有客户端都更新后再移除旧 CA。这个过程需要协调客户端和服务端的更新顺序。6.3 监控 TLS 连接状态Redis 提供了一些 TLS 相关的监控指标可以通过INFO命令查看redis-cli --tls --cacert /etc/redis/tls/ca/ca.crt -p 6380 INFO stats关注total_connections_received和rejected_connections的变化。如果rejected_connections持续增长可能是 TLS 握手失败导致的需要检查证书是否过期或客户端配置是否有问题。另外可以监控 Redis 进程的 CPU 使用率TLS 握手频繁时 CPU 会有明显波动。如果 CPU 长期偏高考虑增大会话缓存或优化连接池配置。7. 一些实战中的经验补充关于 Docker 环境下的 Redis TLS有一点需要特别注意容器内的证书路径和宿主机不同挂载时要确保路径一致。我通常把证书目录挂载到/etc/redis/tls然后在redis.conf里用绝对路径引用。Docker Compose 的配置大概是这样services: redis: image: redis:7.2 command: redis-server /usr/local/etc/redis/redis.conf volumes: - ./redis.conf:/usr/local/etc/redis/redis.conf - ./tls:/etc/redis/tls:ro ports: - 6380:6380注意证书目录挂载时要加:ro只读防止容器内进程意外修改证书文件。还有一个容易忽略的点Redis 的tls-ca-cert-dir配置项指定的是一个目录Redis 会加载该目录下所有.crt文件作为信任的 CA。如果你同时有多个 CA比如迁移期间新旧 CA 并存把它们都放到这个目录下即可。但要注意目录里不要放非 CA 证书否则可能导致验证逻辑混乱。最后说一个关于性能的实测经验。我在压测时发现TLS 1.3 的握手性能明显优于 TLS 1.2因为 TLS 1.3 把握手过程从 2-RTT 优化到了 1-RTT会话恢复甚至是 0-RTT。如果你的客户端支持 TLS 1.3优先让它协商到 1.3。可以通过openssl s_client -connect redis.internal:6380 -tls1_3测试服务端是否支持。在实际生产部署中我建议先在测试环境完整跑一遍证书生成、服务端配置、客户端连接、主从复制、故障切换的流程确认没问题后再上生产。TLS 配置本身不复杂但涉及的环节多任何一个环节的疏漏都可能导致连接失败。把每个步骤都验证到位比事后排查要省事得多。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →