TL;DR

bad_record_mac 是 TLS 加密层 HMAC 校验失败。四大根因:(1) 客户端 / 服务端时间偏差 > 5 分钟;(2) 证书链不完整 (用了 cert.pem 而不是 fullchain.pem);(3) OpenSSL 版本差异;(4) QUIC 路径上 MTU/UDP 问题。本文按这四个层级排查。本文 2026-05-20 实测核对。

bad_record_mac 是 V2Ray / Xray + TLS 部署最容易出但最难排查的错误。本文按”症状识别 → 四层根因 → 修复脚本”梳理。

一、识别症状

错误日志

服务端 Xray 日志 (verbose 模式):

[Warning] [xxxx] proxy/vless/inbound: connection ends > 
  proxy/vless/encoding: tls: bad record MAC

客户端日志:

[Warning] proxy/vless/outbound: failed to dial > 
  tls: bad record MAC

现象特点

  • 握手能完成 (TLS 1.3 SCT 都过)
  • 第一个应用数据包就报错
  • 偶发性 (某些时段更频繁)
  • 一旦报错连接立即断开

二、排查 1: 时间偏差

TLS 1.3 的密钥派生 (HKDF) 在某些边缘场景下对时间敏感。

检查时间偏差

# 服务端
date
# 输出当前时间

# 客户端
date

# 两者差距应 < 1 秒

修复时间同步

# Linux 服务端
sudo timedatectl set-ntp true
sudo systemctl restart systemd-timesyncd

# 验证
timedatectl status
# 输出应包含 "System clock synchronized: yes"

# Windows 客户端
w32tm /resync

# macOS 客户端
sudo sntp -sS pool.ntp.org

如果偏差 > 5 分钟,强制重新同步:

sudo ntpdate -u pool.ntp.org

三、排查 2: 证书链完整性

最常见的”低级错误”。

检查证书链

# 服务端
CERT="/etc/letsencrypt/live/yourdomain.com/fullchain.pem"

# 查看证书数量 (应 >= 2: leaf + 中间)
openssl crl2pkcs7 -nocrl -certfile $CERT | openssl pkcs7 -print_certs -noout | grep -c "subject="

# 应输出 2 或更多

如果只有 1 个证书,说明用错了文件。

Let’s Encrypt 证书文件区分

/etc/letsencrypt/live/yourdomain.com/
├── cert.pem          # 只有 leaf 证书 (错!)
├── chain.pem         # 只有中间证书
├── fullchain.pem     # leaf + 中间 (正确)
└── privkey.pem       # 私钥

Xray / V2Ray 配置应用

{
  "streamSettings": {
    "security": "tls",
    "tlsSettings": {
      "certificates": [{
        "certificateFile": "/etc/letsencrypt/live/yourdomain.com/fullchain.pem",
        "keyFile": "/etc/letsencrypt/live/yourdomain.com/privkey.pem"
      }]
    }
  }
}

必须用 fullchain.pem,不能用 cert.pem

客户端验证证书链

# 模拟客户端连接
openssl s_client -connect yourdomain.com:443 -servername yourdomain.com -showcerts

# 应输出 2 个 "BEGIN CERTIFICATE"
# 第一个是 leaf,第二个是中间
# 最后输出 "Verify return code: 0 (ok)"

如果 Verify return code 不是 0,证书链有问题。

四、排查 3: 密码套件兼容性

某些密码套件在特定 OpenSSL 版本下有 HMAC 兼容性 bug。

检查当前用的密码套件

# 客户端连接时看 cipher
openssl s_client -connect yourdomain.com:443 -servername yourdomain.com < /dev/null 2>&1 | grep "Cipher    :"

常见输出:

  • Cipher : TLS_AES_128_GCM_SHA256 (TLS 1.3 默认,最稳)
  • Cipher : TLS_AES_256_GCM_SHA384 (TLS 1.3 高强度)
  • Cipher : TLS_CHACHA20_POLY1305_SHA256 (TLS 1.3 ChaCha,有时有问题)

ChaCha20 兼容性问题

ChaCha20-Poly1305 在 OpenSSL 1.1.1 与 3.0 之间偶尔有 HMAC 边缘 bug。如果你的服务端是 OpenSSL 1.1.1 编译,客户端是 OpenSSL 3.0,可以临时禁用 ChaCha20:

{
  "tlsSettings": {
    "cipherSuites": "TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384"
  }
}

强制 TLS 1.2 (临时定位)

{
  "tlsSettings": {
    "minVersion": "1.2",
    "maxVersion": "1.2"
  }
}

如果 TLS 1.2 不报错,说明问题在 TLS 1.3 实现层。

五、排查 4: QUIC 路径上的 MTU / UDP 问题

如果你用的是 Hysteria 2/3 / TUIC v5 (基于 QUIC):

MTU 问题

QUIC 走 UDP,中间路由器可能对超过 MTU 的包做 IP 分片,造成包损坏。

# 检测 MTU
ping -M do -s 1472 服务器IP
# 1472 = 1500 (默认以太网) - 28 (IP+ICMP header)

# 如果失败,尝试更小 MTU
ping -M do -s 1452 服务器IP  # 1480 - 28
ping -M do -s 1420 服务器IP  # 1448 - 28

找到能通的最大 MTU 后,在 Hysteria 配置里限制包大小:

# hysteria-server config.yaml
quic:
  initStreamReceiveWindow: 26843545
  maxStreamReceiveWindow: 26843545
  initConnReceiveWindow: 67108864
  maxConnReceiveWindow: 67108864
  maxIdleTimeout: 30s
  keepAlivePeriod: 10s
  disablePathMTUDiscovery: false

UDP 端口冲突

# 检查 443/udp 是否被其他服务占用
sudo ss -ulnp | grep ":443"

# 应只有 Xray / Hysteria

如果有 systemd-resolved 或其他服务占用,换端口或停止冲突服务。

六、排查 5: 服务端与客户端版本

# 服务端版本
/usr/local/bin/xray version
# 推荐: Xray 1.8.16 或更高

# 客户端版本 (sing-box 系)
sing-box version
# 推荐: 1.13.0 或更高

# 客户端 (Mihomo 系)
mihomo -v
# 推荐: 1.19.0 或更高

如果服务端是 1.8.10 + 客户端是 1.8.16,可能因为 utls 库版本差导致密钥派生路径不同。

修复

服务端 + 客户端都升级到最新稳定版本。Xray-core 1.8.16+ / sing-box 1.13+ / Mihomo 1.19+。

七、用 Wireshark 抓包定位

如果上述都不能定位:

# 服务端抓包
sudo tcpdump -i any -w xray.pcap port 443

# 让客户端复现错误几次

# 停止抓包 (Ctrl+C),传 xray.pcap 到本地

用 Wireshark 打开 xray.pcap:

  1. Edit → Preferences → Protocols → TLS → 加 RSA Keys List (如果有私钥)
  2. 找到客户端 IP 的 TLS 流
  3. 看 Client Hello → Server Hello → 加密握手完成
  4. 第一个 Application Data 包就是出错的
  5. 看 length 字段与实际 bytes 是否一致

通常能直接看到”包长度对不上”或”加密内容异常”。

八、systemd 自动重启 + 监控

bad_record_mac 偶发性高,加自动重启:

[Service]
Type=simple
ExecStart=/usr/local/bin/xray run -c /etc/xray/config.json
Restart=always
RestartSec=5
StartLimitInterval=600
StartLimitBurst=10

防止短时间内频繁重启 (StartLimitBurst=10 表示 600 秒内最多重启 10 次)。

九、配套订阅的稳定性

订阅商对 V2Ray/Xray 的部署质量直接影响 bad_record_mac 频率。一些订阅用旧 OpenSSL 1.1.1 + Let’s Encrypt 证书但只配 cert.pem,bad_record_mac 概率高。冲浪笔记长期跟踪的兼容 Clash / Singbox / V2Ray 的订阅在服务端用 Xray-core 1.8.16+ + 完整 fullchain + TLS 1.3 默认套件,bad_record_mac 几乎不出现。

总结

bad_record_mac 的五层排查:

  1. 时间同步 (timedatectl set-ntp true)
  2. 证书链完整 (用 fullchain.pem 不是 cert.pem)
  3. 密码套件兼容 (临时禁用 ChaCha20 或降级 TLS 1.2)
  4. QUIC 路径 MTU (ping -M do -s 测最大)
  5. 服务端 + 客户端版本 (都升 Xray-core 1.8.16+)

90% 问题在 (1) 与 (2)。本文 2026-05-20 实测核对。