前言

最近使用 Docker 部署 NodeBB 时遇到了一个非常隐蔽的问题:

NodeBB 安装完成后,网站页面可以正常访问,但账号始终无法登录

浏览器提示:

登录失败
可能是由于会话过期,登录失败。请重试。

NodeBB 后端日志则出现:

POST /login
invalid csrf token

同时浏览器 Network 中还能看到:

/socket.io/?_csrf=xxxxx&EIO=4&transport=polling
403 Forbidden

一开始很容易怀疑:

  • Cloudflare SSL 配置
  • Nginx 反向代理
  • WebSocket
  • Redis Session
  • 浏览器 Cookie
  • NodeBB URL
  • MongoDB
  • HTTPS 证书

但最终发现,真正原因非常简单:

NodeBB Docker 实际使用的 config.json 中没有开启 trust_proxy

加入:

"trust_proxy": true

重启 NodeBB 后,登录立即恢复正常。

下面记录完整排查过程和最终解决方法。


一、问题环境

环境大致如下:

NodeBB 4.14.x
Docker
MongoDB
Redis
Nginx 反向代理
HTTPS
Cloudflare(可选)

访问结构:

浏览器
   ↓ HTTPS
Cloudflare
   ↓ HTTPS
Nginx
   ↓ HTTP
NodeBB :4567

NodeBB 本身运行在:

127.0.0.1:4567

Nginx 负责 HTTPS 和反向代理。


二、错误特征

这个问题有几个非常典型的特征。

1️⃣ NodeBB 页面可以正常打开

访问:

https://example.com

首页、登录页等都可以正常加载。

这说明:

Nginx → NodeBB

基本是通的。


2️⃣ 登录时提示 Session 过期

前端提示类似:

登录失败

可能是由于会话过期,登录失败。请重试。

英文版可能显示:

Login Unsuccessful

We were unable to log you in,
likely due to an expired session.
Please try again.

这个提示很容易让人误以为:

Cookie 过期了。

但实际上未必如此。


3️⃣ NodeBB 日志出现 invalid csrf token

执行:

docker logs nodebb --tail 100

可以看到:

error: POST /login
invalid csrf token

或者浏览器最终跳转:

/login?error=csrf-invalid

这是最关键的错误特征。


4️⃣ Socket.IO 返回 403

浏览器开发者工具中可能同时出现:

GET /socket.io/?_csrf=xxxxxxxx&EIO=4&transport=polling
403 Forbidden

因此很容易误判为:

WebSocket 被 Cloudflare 或 Nginx 阻断。

实际上这里甚至还处在 Socket.IO 的 polling 阶段。

所以:

Socket.IO 403

很多时候只是 CSRF / Session 错误的结果,并不是 WebSocket 本身的问题。


三、先确认 NodeBB 本身有没有坏

可以直接绕过 Nginx:

curl -I http://127.0.0.1:4567/login

如果返回:

HTTP/1.1 200 OK
X-Powered-By: NodeBB

说明:

NodeBB 本体 ✅
端口 4567 ✅
Docker 网络 ✅
HTTP 服务 ✅

因此没有必要因为登录失败就重新安装 NodeBB。


四、为什么清 Redis 没用

因为出现:

invalid csrf token

自然会想到 Session,于是可以尝试:

docker exec -it redis redis-cli

然后:

FLUSHDB

再重启:

docker restart nodebb

如果还是:

POST /login
invalid csrf token

那么问题大概率不是旧 Session。

我这里清空 Redis 后问题依旧存在。


五、为什么 Cloudflare 也不是根因

最开始也怀疑 Cloudflare。

尤其是如果使用:

Flexible SSL

同时源站又强制 HTTPS,很容易产生:

ERR_TOO_MANY_REDIRECTS

因此正常情况下推荐:

Cloudflare SSL/TLS
→ Full

或者:

Full (strict)

但是即使临时将 Cloudflare DNS 改成:

DNS only

让浏览器直接连接服务器,NodeBB 登录仍然:

csrf-invalid

那么 Cloudflare 基本就可以排除了。


六、Nginx 反向代理看起来也是正常的

常见 NodeBB Nginx 配置类似:

location / {
    proxy_pass http://127.0.0.1:4567;

    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;

    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
}

检查最终 Nginx 配置:

nginx -T | grep X-Forwarded-Proto -n

如果能看到:

proxy_set_header X-Forwarded-Proto $scheme;

说明 Nginx 确实已经向 NodeBB 发送了代理协议头。

这里就引出了真正的问题:

Nginx 发了 X-Forwarded-*,但 NodeBB/Express 不一定信任它。


七、真正根因:trust_proxy 没有开启

这是本次问题的核心。

在反向代理环境中,NodeBB 后面的 Express 需要信任代理服务器。

否则即使 Nginx 已经传递:

X-Forwarded-Proto
X-Forwarded-For
X-Forwarded-Host

应用也可能无法按照预期使用这些信息。

最终可能导致:

HTTPS 判断
↓
Session
↓
Cookie
↓
CSRF Token

之间状态不一致。

表现出来就是:

POST /login
invalid csrf token

以及:

/socket.io/
403

八、新版 NodeBB Docker 的 config.json 在哪里?

这里也是一个坑。

网上很多旧教程会让你找:

/usr/src/app/config.json

但新版 Docker 部署不一定在这里。

例如执行:

docker exec -it nodebb bash

然后:

ls /usr/src/app

可能根本没有:

config.json

可以直接搜索:

docker exec nodebb sh -lc \
'find /opt /usr/src/app -maxdepth 3 -name "config.json" -type f -print 2>/dev/null'

我的 Docker 环境中实际找到的是:

/opt/config/config.json

这才是 NodeBB 当前真正使用的配置文件。


九、检查 trust_proxy

执行:

docker exec nodebb sh -lc \
'grep -E "\"(url|trust_proxy)\"" /opt/config/config.json'

问题发生时只显示:

"url": "https://example.com"

却没有:

"trust_proxy": true

这就是关键。


十、最终解决方案

1️⃣ 先备份配置

不要直接修改。

docker cp \
nodebb:/opt/config/config.json \
/root/nodebb-config.json.bak

确认:

Successfully copied ...

2️⃣ 写入 trust_proxy: true

可以直接使用 Node 修改 JSON,避免手工编辑时把 JSON 格式弄坏:

docker exec -u 0 nodebb node -e "
const fs=require('fs');
const p='/opt/config/config.json';
const c=JSON.parse(fs.readFileSync(p,'utf8'));
c.trust_proxy=true;
fs.writeFileSync(
    p,
    JSON.stringify(c,null,4)+'\n'
);
"

3️⃣ 检查配置

执行:

docker exec nodebb sh -lc \
'grep -E "\"(url|trust_proxy)\"" /opt/config/config.json'

正常应该出现:

"url": "https://example.com",
"trust_proxy": true

4️⃣ 重启 NodeBB

docker restart nodebb

然后:

docker logs nodebb --tail 30

如果修复成功,可以看到非常关键的一行:

Setting 'trust proxy' to true

同时还应该有:

NodeBB Ready
NodeBB is now listening on: 0.0.0.0:4567
Canonical URL: https://example.com

这说明 NodeBB 已经真正读取到了:

"trust_proxy": true

十一、修改后的关键启动日志

正常启动后类似:

Initializing NodeBB v4.14.x https://example.com

[socket.io] Restricting access to origin:
https\://example.com:\*

NodeBB Ready

Setting 'trust proxy' to true

NodeBB is now listening on:
0.0.0.0:4567

Canonical URL:
https://example.com

其中最关键的就是:

Setting 'trust proxy' to true

如果没有这一行,就需要继续确认 NodeBB 到底读取的是哪个 config.json


十二、最后清理浏览器 Session

由于之前产生过错误 Session,修复后建议删除一次网站 Cookie。

或者直接使用浏览器无痕模式访问:

https://example.com

然后重新登录。

修复后:

/login

即可正常登录。

Socket.IO 也会恢复连接。


十三、emoji 插件报错和登录失败无关

排查过程中还可能看到:

[emoji] Failed to retrieve data for parse

ENOENT:
no such file or directory

或者:

Error: EACCES: permission denied,
mkdir '/usr/src/app/public/uploads/emoji'

例如:

nodebb-plugin-emoji

相关错误。

这些是 Emoji 插件文件或权限问题

它们需要单独处理,但不是:

invalid csrf token

的根本原因。

因此不要因为看到一大串红色日志,就把注意力全部放到 emoji 插件。


十四、完整排查思路

如果你也遇到:

NodeBB 登录失败
Session expired
invalid csrf token
csrf-invalid
Socket.IO 403

建议按照这个顺序排查:

1. curl 127.0.0.1:4567/login
        ↓
   确认 NodeBB 本体正常

2. 检查 Nginx 反向代理
        ↓
   X-Forwarded-Proto
   Host
   WebSocket

3. Cloudflare 临时灰云
        ↓
   排除 CF

4. 无痕浏览器
        ↓
   排除旧 Cookie

5. 清 Redis Session
        ↓
   排除旧 Session

6. 找 NodeBB 真正使用的 config.json
        ↓
   /opt/config/config.json

7. 检查 trust_proxy
        ↓
   "trust_proxy": true

8. 重启 NodeBB
        ↓
   日志出现:
   Setting 'trust proxy' to true

十五、最终结论

这次最误导人的地方在于:

网页可以正常打开

所以看起来 NodeBB、Nginx、HTTPS 全部正常。

实际上:

普通 HTTP 页面请求

和:

Session / Cookie / CSRF

不是同一层问题。

Nginx 虽然已经将:

X-Forwarded-Proto

传给 NodeBB,但是如果 NodeBB 没有:

"trust_proxy": true

代理信息无法按照预期被应用层信任,最终就可能出现:

登录失败
↓
invalid csrf token
↓
Socket.IO 403

最终只需要在 NodeBB 真正使用的 /opt/config/config.json 中加入:

"trust_proxy": true

然后:

docker restart nodebb

问题即可解决。

更多推荐