#OpenClaw #FRP #nginx #HTTPS #公网访问

接上篇 《OpenClaw 配置续集:网络修复、邮件插件与 Ollama 接入》,记录第三阶段:为 OpenClaw 配置公网 HTTPS 访问。


一、整体架构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
用户浏览器
│ HTTPS 443

MyServer(Aliyun 公网,x.x.x.x
└── nginx 反向代理 → 127.0.0.1:17890


frps(已运行,bind_port=6000
│ FRP 隧道

N100(本地,192.168.0.155
└── frpc → 127.0.0.1:18789


OpenClaw 容器(network_mode: host)

关键点:

  • MyServer 已运行 frps(端口 6000,token 已配置),所有隧道复用同一个 frps,无需额外部署

  • N100 已有 frpc 运行(管理 SSH、WebDAV 等隧道),只需追加 [openclaw]

  • nginx 和 frps 在同一台机器,nginx 通过 127.0.0.1:17890 访问 frps 的代理端口(不需要对外开放 17890)


二、SSH 与服务器别名说明

1
2
3
ssh MyServer        → x.x.x.x:22(直接,真实公网服务器)
ssh MyUbuntu → x.x.x.x:6002(FRP 隧道 → N100:22
ssh MyUbuntuLocal → 192.168.0.155(局域网直连 N100)

三、N100 frpc 追加 OpenClaw 隧道

编辑 /etc/frp/frpc.ini,在已有 [common](server_addr=x.x.x.x, port=6000)后追加:

1
2
3
4
5
[openclaw]
type = tcp
local_ip = 127.0.0.1
local_port = 18789
remote_port = 17890

然后重启 frpc:

1
sudo systemctl restart frpc

四、MyServer nginx 配置

证书使用 acme.sh + Cloudflare DNS API 自动签发(域名 NS 已迁移到 Cloudflare)。

申请证书

1
2
3
4
5
6
7
8
9
10
11
sudo mkdir -p /etc/nginx/ssl/openclaw

# dns_cf 方式,CF_Token 已在 /root/.acme.sh/account.conf 中
sudo /root/.acme.sh/acme.sh --issue --dns dns_cf \
-d "your.domain.example" --force

sudo /root/.acme.sh/acme.sh --install-cert \
-d "your.domain.example" --ecc \
--key-file /etc/nginx/ssl/openclaw/openclaw.key \
--fullchain-file /etc/nginx/ssl/openclaw/openclaw.pem \
--reloadcmd "systemctl reload nginx"

nginx 站点配置

/etc/nginx/sites-available/openclaw

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
server {
listen 80;
server_name your.domain.example;
return 301 https://$host$request_uri;
}

server {
listen 443 ssl;
server_name your.domain.example;

ssl_certificate /etc/nginx/ssl/openclaw/openclaw.pem;
ssl_certificate_key /etc/nginx/ssl/openclaw/openclaw.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;

location / {
proxy_pass http://127.0.0.1:17890;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
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_read_timeout 3600s;
proxy_send_timeout 3600s;
}
}

启用站点:

1
2
sudo ln -sf /etc/nginx/sites-available/openclaw /etc/nginx/sites-enabled/openclaw
sudo nginx -t && sudo systemctl reload nginx

Aliyun 安全组

无需额外开放新端口。frps 控制端口 6000 已开放(SSH 隧道在用),nginx 的 80/443 已开放。frps 代理端口 17890 仅供 nginx 本机访问,不需要对外暴露。


五、DNS 配置

在 Cloudflare 添加 A 记录:

类型 名称 内容 代理
A claw x.x.x.x DNS only(灰云)

acme.sh 使用 DNS API 验证,无需 A 记录提前生效即可申请证书。


六、踩坑记录

1. certbot 系统版本损坏

MyServer 的 /usr/bin/certbot(v1.21.0)因 Python 依赖冲突无法启动(ImportError: cannot import name 'appengine')。改用 acme.sh,已稳定运行。

2. nginx 配置引用不存在的证书导致 nginx -t 失败

最初 nginx 配置引用了 Let’s Encrypt 路径(/etc/letsencrypt/live/...),证书未申请时 nginx 拒绝启动。改用 HTTP-only 临时配置或确保先申请证书再启用站点。

3. OpenClaw 实际监听端口非 18789

OpenClaw 配置 "bind": "lan", "port": 18789 中,port 是元数据(用于 CORS/WebSocket origin 校验),进程实际监听的端口需通过 sudo ss -tlnp 确认。N100 上同时运行了 code-server(127.0.0.1:8080),一度误判为 OpenClaw。

健康检查配置揭示真实端口:

1
2
docker inspect openclaw --format '{{json .Config.Healthcheck}}'
# 输出包含 fetch('http://127.0.0.1:18789/healthz')

4. OpenClaw ollama.auth: "none" 启动失败

现象:容器反复重启,日志:

1
models.providers.ollama.auth: Invalid input (allowed: "api-key", "aws-sdk", "oauth", "token")

原因:OpenClaw 的 doctor --fix 或版本迁移会自动在 ollama provider 写入 "auth": "none",但新版本校验不接受 "none"

修复:用 Python 原地写入(不能用 sed -i,因为 Docker bind mount 文件不支持 rename):

1
2
3
4
5
6
7
8
9
10
python3 -c "
import json
path = '/home/milin/dockerfile/openclaw/openclaw.json'
with open(path, 'r') as f:
c = json.load(f)
c['models']['providers']['ollama']['auth'] = 'api-key'
with open(path, 'w') as f:
json.dump(c, f, indent=2, ensure_ascii=False)
"
docker restart openclaw

Why sed -i fails on bind mountsed -i 内部将内容写入临时文件再 rename 替换原文件,Docker bind mount 不允许在挂载点进行 rename 操作,报 Device or resource busy。需要用 open(path, 'w') 直接原地覆写同一 inode。


七、环境变量迁移问题

OpenClaw 更新后会将 openclaw.json 中的敏感字段自动替换为 ${VAR} 环境变量引用,并期望通过 env_file 注入。若 .env 缺少对应变量则启动失败。

需要在 .env 中补充以下变量:

1
2
3
4
DEEPSEEK_API_KEY=<deepseek api key>
ANTHROPIC_API_KEY=<anthropic api key>
EMAIL_163_PASS=<163邮箱密码>
EMAIL_QQ_PASS=<QQ邮箱应用密码>

重要docker restart 不会重载 env_file,环境变量在容器创建时注入。修改 .env 后必须重建容器。

docker-compose v1.29.2 Bug:执行 docker-compose up -d 时报 KeyError: 'ContainerConfig',这是 docker-compose v1 的已知 bug。改用 docker compose(v2)或手动重建:

1
2
3
4
5
6
7
8
9
docker rm openclaw
docker run -d \
--name openclaw \
--restart unless-stopped \
--network host \
--user root \
--env-file /home/milin/dockerfile/openclaw/.env \
-v /home/milin/dockerfile/openclaw/data:/root/.openclaw \
ghcr.io/openclaw/openclaw:latest

注意:-v 只挂载 data/ 目录,不单独挂载 openclaw.json 文件(原因见九)。


八、EBUSY:OpenClaw CLI 写入失败

现象:安装插件或运行 openclaw config set 时报错:

1
EBUSY: resource busy or locked, rename '/root/.openclaw/.tmp-openclaw.json' -> '/root/.openclaw/openclaw.json'

根本原因:OpenClaw CLI 的配置写入使用原子操作(写临时文件 → rename),而 Docker 单文件 bind mount-v .../openclaw.json:/root/.openclaw/openclaw.json)不允许在挂载点执行 rename,导致所有涉及配置写入的操作失败。

修复:将 openclaw.json 移入 data/ 目录,改为目录挂载:

1
2
# 在宿主机上
cp openclaw.json data/openclaw.json

然后从 docker-compose.yml 移除单文件挂载行:

1
2
3
4
5
6
7
8
# 修改前(有问题)
volumes:
- ./data:/root/.openclaw
- ./openclaw.json:/root/.openclaw/openclaw.json # ← 删除此行

# 修改后(正确)
volumes:
- ./data:/root/.openclaw

重建容器后 OpenClaw 可正常写入配置。

修改 data/openclaw.json 的权限问题:容器以 root 运行,data/ 目录下的文件属主为 root。在宿主机上直接修改需要 sudo

1
2
3
4
5
6
7
8
9
sudo python3 -c "
import json
path = '/home/milin/dockerfile/openclaw/data/openclaw.json'
with open(path, 'r') as f:
c = json.load(f)
# 修改内容...
with open(path, 'w') as f:
json.dump(c, f, indent=2, ensure_ascii=False)
"

九、ollama.auth 问题反复出现

除容器初次启动外,OpenClaw 在运行时收到 SIGUSR1(插件安装、doctor --fix 等操作触发内部配置迁移)也会重新写入 "auth": "none",导致下次重启失败。

现象规律

  1. 容器重启失败,日志 ollama.auth: Invalid input

  2. Python 修复后 docker restart,启动成功

  3. 在容器内执行某些操作(如插件安装)后,再次重启又失败

临时修复流程(每次重启失败后执行):

1
2
3
4
5
6
7
8
sudo python3 -c "
import json
path = '/home/milin/dockerfile/openclaw/data/openclaw.json'
with open(path) as f: c = json.load(f)
c['models']['providers']['ollama']['auth'] = 'api-key'
with open(path, 'w') as f: json.dump(c, f, indent=2, ensure_ascii=False)
"
docker start openclaw

十、插件渠道配置(plugins.allow 白名单)

plugins.allow 是严格白名单,不在列表内的插件(含内置插件)全部对 Agent 不可见。

当前 openclaw.json 配置:

1
2
3
4
5
6
7
"plugins": {
"allow": ["email", "gmail", "qqbot", "feishu", "imessage", "openclaw-weixin"],
"entries": {
"email": { ... },
"gmail": { ... }
}
}

修改白名单后执行 docker restart openclaw 即生效(不需要重建容器,因为只是读配置)。

各渠道说明:

渠道 说明 状态
email 163 邮箱 IMAP/SMTP 已配置,运行中
gmail QQ 邮箱(gmail 插件兼容 QQ SMTP) 已配置,运行中
qqbot QQ 机器人(需官方 AppID/AppToken) 白名单已加,待配置
feishu 飞书机器人 白名单已加,待配置
imessage iMessage(仅 Mac 宿主机可用) 白名单已加,待配置
openclaw-weixin 微信(需扫码,不支持非交互式) 白名单已加,待配置

QQ Bot 说明qqbot 插件对接的是 QQ 开放平台 API,不是个人 QQ 账号登录。需前往 q.qq.com 注册机器人,获取 AppID 和 AppToken 后填入配置。


十一、浏览器来源被拒(controlUi.allowedOrigins)

现象:浏览器访问公网 HTTPS 地址后,连接被拒绝,控制台提示 Origin 不被允许。

原因openclaw.jsongateway.controlUi.allowedOrigins 默认只含本地地址。

修复:在 allowedOrigins 数组中加入公网 HTTPS 域名:

1
2
3
4
5
6
7
8
9
"gateway": {
"controlUi": {
"allowedOrigins": [
"http://localhost:18789",
"http://127.0.0.1:18789",
"https://your.domain.example"
]
}
}

修改 data/openclaw.jsondocker restart openclaw 生效。


十二、最终验证

1
2
curl -si https://your.domain.example/ --max-time 10 | head -3
# HTTP/2 200

浏览器访问 https://your.domain.example,输入 .env 中的 OPENCLAW_GATEWAY_TOKEN 值即可登录 OpenClaw。


十三、设备配对认证(devices approve)

现象:首次从新浏览器或新设备访问 Control UI 时,弹出:

需要设备配对 — 此浏览器需要 Gateway 主机的一次性批准后才能使用 Control UI。在 Gateway 主机上运行 openclaw devices list。批准此请求:openclaw devices approve <device-id>

原因:OpenClaw Gateway 在 Token 鉴权之外增加了设备级别的一次性信任机制,防止 Token 泄漏后被任意设备使用。

批准流程(在 N100 上执行):

1
2
3
4
5
# 列出待批准设备(含 device-id)
docker exec openclaw openclaw devices list

# 批准指定设备(device-id 从弹窗提示获取)
docker exec openclaw openclaw devices approve 6f22ef11-5c00-4bca-bd8c-ed759f02519e

批准后该浏览器永久记录在 Gateway,无需再次操作。不同浏览器(Chrome/Safari/Firefox)各为独立设备,需分别批准。


十四、硬件架构总览

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
MacBook Air M1 (192.168.0.110)
└── ssh MyServer → x.x.x.x:22 (Aliyun 公网服务器)
└── ssh MyUbuntu → x.x.x.x:6002 (FRP 隧道 → N100:22)
└── ssh MyUbuntuLocal192.168.0.155 (局域网直连 N100)

MyServer (Aliyun, x.x.x.x)
├── frps (port 6000, 管理所有 FRP 隧道)
└── nginx (443 → 127.0.0.1:17890 → frps → N100:18789)

N100 (Ubuntu 22.04, 16GB DDR5, 192.168.0.155)
├── frpc (SSH 隧道:6002, OpenClaw 隧道:17890, WebDAV 等)
├── Docker: openclaw (network_mode: host, :18789)
├── Docker: hermes-agent
└── code-server (:8080, 仅本机)

Win11 推理机 (192.168.0.195)
├── Ollama 0.30.11
├── portproxy: 0.0.0.0:11434 → 127.0.0.1:11434
└── 模型: qwen2.5-coder:7b / llava:7b / qwen3:8b / deepseek-r1:8b / nomic-embed-text

流量路径(公网访问)

1
2
3
4
5
6
浏览器 HTTPS 443
→ nginx (MyServer:443)
→ frps (127.0.0.1:17890)
→ FRP 隧道
→ frpc (N100)
→ OpenClaw (:18789)

延伸阅读