本文最后更新于21 天前,其中的信息可能已经过时,如有错误请发送邮件到[email protected]
一、目标
Piik 是一个可自托管的网页屏幕共享 / 房间工具:网页与信令由服务器提供,画面走 WebRTC(默认 P2P,可选 SFU 兜底)。官方 demo 和 App 的公网邀请模式只有 P2P,遇到严格 NAT 容易出现「网页能开、画面连不上」。所以我决定在自己的 VPS 上搭一个专属节点,并预留 SFU 兜底端口。
| 服务器 | 雨云 Ubuntu x64(公网 IP 38.246.253.12) |
| 面板 | 宝塔(Nginx 反向代理) |
| DNS | Cloudflare(忆水.xyz,punycode:xn--45tq0z.xyz) |
| 部署方式 | Docker Compose,镜像 ghcr.io/tntcrafthim/piik:latest |
| 证书 | Let’s Encrypt(acme.sh + Cloudflare DNS 验证) |
二、部署本身很简单
cd /root
# compose.yaml 与 .env 放在同一目录
docker compose run --rm piik --check-config # config=ok
docker compose up -d
docker compose logs --tail=20
看到下面这行就说明服务起来了:
msg="Piik server is listening" version=v1.9.2 host=0.0.0.0 port=8787
publicUrl=https://xn--45tq0z.xyz sqlite=true sfu=false
关键端口(Compose 会自动映射):
127.0.0.1:8787 网页 / 信令(只对本机开放,由 Nginx 反代出去)
3478-3480/udp STUN(P2P 打洞必需)
7882/udp SFU 媒体兜底(可选)
三、Nginx 反向代理
宝塔里用「反向代理项目」没有成功,最后手工写 vhost(/www/server/panel/vhost/nginx/xn--45tq0z.xyz.conf)反而最稳:
server {
listen 80;
server_name xn--45tq0z.xyz www.xn--45tq0z.xyz;
location / {
proxy_pass http://127.0.0.1:8787;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade; # WebSocket 必需
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;
}
}
重要:中文域名在浏览器里发出去的是 punycode 形式的 Host,所以 server_name 必须写 xn--45tq0z.xyz,写中文匹配不上。
四、踩过的坑(重点)
| 现象 | 真正原因 | 解决 |
|---|---|---|
curl 返回 status=000(连响应都没有) | Nginx 根本没起来:旧 vhost 里有个 listen 8787,而 8787 已被 Piik 占用 → [emerg] bind() to 0.0.0.0:8787 failed | 删掉或改掉那个 vhost,然后 service nginx restart |
| 反代配了还是 Nginx 404 | 宝塔「反向代理项目」生成的 conf 没绑定成站点,nginx -T 里查不到对应 server_name | 手工写 vhost,或先在宝塔「添加站点」,再进站点内加反代 |
域名打不开,getaddrinfo ENOTFOUND | Cloudflare 里只有 www 和 * 记录,通配符不覆盖根域名 | 补一条 @ 的 A 记录 |
| 网页能开,建房报 403 Origin not allowed | PUBLIC_BASE_URL 写的是 https://…,实际却用 http://… 访问,来源不匹配 | 让 PUBLIC_BASE_URL 等于浏览器实际访问的地址(含协议);多地址用英文逗号写进 ALLOWED_ORIGINS |
| Cloudflare 橙云 + Full 模式回源 404 | CF 用 443 回源,而源站 443 上没有该域名的 server 块 | 要么切 Flexible(80 回源),要么给源站配好证书后用 Full |
acme.sh 卡在 Could not get nonce | 它默认用 ZeroSSL,国内服务器连不上 | 加 --server letsencrypt 重签,立刻成功 |
| 观众看不到画面 | Cloudflare 代理不转发 UDP,STUN 打洞失败 | stun 子域必须设成 DNS only(灰云),并放行 UDP 3478-3480 |
| 浏览器选不了屏幕 | 浏览器屏幕采集要求 HTTPS 或 localhost | 给源站签真证书,改用 HTTPS 访问 |
五、HTTPS:acme.sh + Cloudflare DNS 验证
用 DNS 验证签证书,不需要占用 80 端口:
curl https://get.acme.sh | sh -s email=你的邮箱@gmail.com
export CF_Token="CF的API令牌" # 权限:Zone → DNS → Edit
~/.acme.sh/acme.sh --issue --dns dns_cf --server letsencrypt \
-d xn--45tq0z.xyz -d www.xn--45tq0z.xyz
# 装到 Nginx 使用的路径并自动重载
~/.acme.sh/acme.sh --install-cert -d xn--45tq0z.xyz \
--key-file /www/server/panel/vhost/cert/xn--45tq0z.xyz/privkey.pem \
--fullchain-file /www/server/panel/vhost/cert/xn--45tq0z.xyz/fullchain.pem \
--reloadcmd "service nginx restart"
再补一个 443 的 server 块(listen 443 ssl; + http2 on; + 上面两个证书路径 + 同一份 proxy 配置),然后把站点地址统一成 https:
cd /root
sed -i 's|^PUBLIC_BASE_URL=.*|PUBLIC_BASE_URL=https://xn--45tq0z.xyz|' .env
docker compose up -d
自动续期(acme.sh 装 cron 失败时,用宝塔「计划任务」加一条 Shell 脚本):
"/root/.acme.sh"/acme.sh --cron --home "/root/.acme.sh" > /dev/null
六、验收清单
curl -s http://127.0.0.1:8787/healthz # {"status":"ok"}
curl -s https://xn--45tq0z.xyz/healthz # {"status":"ok"}
ss -lntp | grep -E ':80|:443|8787' # nginx 听 80/443,Piik 听 8787
docker compose ps # 容器 Up
systemctl is-enabled docker # enabled(开机自启)
docker inspect piik-piik-1 --format '{{.HostConfig.RestartPolicy.Name}}' # unless-stopped
浏览器打开 https://xn--45tq0z.xyz:无证书警告、能建房;把链接发给朋友,手机用流量加入,画面和声音都正常,收工。
七、几条经验
status=000不是 404。 前者是「连接被直接关掉」(服务没在监听)。排查顺序永远是:进程 → 监听端口 → vhost 匹配 → 上游服务。- 排查要分层。 本机
127.0.0.1:8787→ 本机带 Host 头 → 公网 IP 带 Host 头 → 域名。每层都拿到结论,不要跳步。 - 中文域名记得用 punycode(忆水.xyz = xn--45tq0z.xyz)。Nginx 的
server_name、客户端填的节点地址都建议用 punycode。 - Cloudflare 免费版不转发 UDP。 凡是 STUN、游戏加速这类服务,相关子域一定要设成 DNS only(灰云)。
- 证书必须和实际访问的协议一致,否则就是各种 403 / 526;改完
.env记得docker compose up -d重建容器才生效。









