Skip to content

基于 frp + Apache Guacamole 搭建网页远程桌面

安全警告

本文中的 密码、IP 地址、端口号 均已替换为占位符。切勿直接复制粘贴到生产环境,请先将 <YOUR_*> 占位符替换为你自己的真实值。

背景

有一台 Windows 电脑在家里,一台云服务器,希望通过浏览器远程控制那台 Windows 电脑,而不需要安装任何客户端软件。

整体架构:

浏览器 → 云服务器(Guacamole) → frp 隧道 → Windows RDP

已有条件:

  • 云服务器已运行 frps
  • Windows 电脑已运行 frpc,RDP 通过隧道端口映射到服务器上
  • 传统 RDP 客户端(mstsc)已可正常连接

目标:新增网页版远程桌面,与现有 RDP 并行使用,互不影响。


整体架构

┌─────────┐     HTTPS      ┌──────────────────┐     ┌─────────────┐     frp      ┌──────────┐
│ 浏览器   │ ◄────────────► │ 云服务器          │     │ frps         │ ◄─────────── │ Windows  │
│          │               │ nginx :443        │     │ 映射端口      │              │ RDP:3389 │
│          │               │ guacamole :8080   │     └─────────────┘              └──────────┘
│          │               │ guacd :4822       │
└─────────┘               └──────────────────┘
  • frps / frpc:TCP 隧道,将 Windows 3389 映射到服务器
  • guacd:Guacamole 代理守护进程,负责实际的 RDP 连接
  • guacamole:Web 应用,提供浏览器端的远程桌面界面
  • nginx:反向代理,提供 HTTPS 加密

部署步骤

1. 环境准备

服务器要求:

  • 任意 Linux 发行版(本文以 RHEL 系列为例)
  • Docker 和 Docker Compose 已安装

确认 frp RDP 隧道正常:

bash
# 测试 frp RDP 端口是否可达
echo > /dev/tcp/<YOUR_SERVER_IP>/<YOUR_RDP_PORT> && echo "OK" || echo "FAIL"

2. 部署 Guacamole(文件认证模式)

重要

经过大量排查,不要使用 MySQL 认证模式,有一个 Bug 会导致"Connection already in use by this user"错误。详见下文踩坑记录。

创建 docker-compose.yml

yaml
services:
  guacd:
    image: guacamole/guacd:1.5.5
    container_name: guacd
    restart: unless-stopped

  guacamole:
    image: guacamole/guacamole:1.5.5
    container_name: guacamole
    depends_on:
      - guacd
    environment:
      GUACD_HOSTNAME: guacd
      GUACAMOLE_HOME: /etc/guacamole
    volumes:
      - ./user-mapping.xml:/etc/guacamole/user-mapping.xml:ro
    ports:
      - '127.0.0.1:8080:8080'   # 仅监听本地,通过 nginx 代理
    restart: unless-stopped

创建 user-mapping.xml

xml
<user-mapping>
    <authorize username="<YOUR_USERNAME>" password="<YOUR_STRONG_PASSWORD>">
        <connection name="My Windows PC">
            <protocol>rdp</protocol>
            <param name="hostname">172.17.0.1</param>
            <param name="port"><YOUR_RDP_PORT></param>
            <param name="security">any</param>
            <param name="ignore-cert">true</param>
        </connection>
    </authorize>
</user-mapping>

注意

  • 密码务必使用强密码(12 位以上,包含大小写字母、数字、特殊字符)
  • hostname172.17.0.1(Docker 宿主机网关),因为 Guacamole 容器需要通过宿主机访问 frp 端口
  • 8080 端口仅绑定 127.0.0.1,外部只能通过 nginx HTTPS 访问

启动:

bash
docker compose up -d

3. 配置 nginx HTTPS 反向代理

生成自签名证书并配置 nginx:

bash
# 生成证书
mkdir -p /etc/nginx/ssl
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
  -keyout /etc/nginx/ssl/server.key \
  -out /etc/nginx/ssl/server.crt \
  -subj '/CN=<YOUR_SERVER_IP>'

# nginx 配置 (/etc/nginx/conf.d/guacamole.conf)
server {
    listen 443 ssl;
    server_name <YOUR_SERVER_IP>;

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

    # 安全头
    add_header X-Frame-Options SAMEORIGIN;
    add_header X-Content-Type-Options nosniff;

    location /guacamole/ {
        proxy_pass http://127.0.0.1:8080/guacamole/;
        proxy_buffering off;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
        proxy_read_timeout 86400;
    }
}

# 重载
nginx -t && systemctl reload nginx

4. 开放云安全组端口

在云服务商控制台 → 安全组 → 添加入站规则。注意:仅开放必要端口,不要开放 Guacamole 直连端口(8080)。

端口协议用途建议
443TCPHTTPS 网页访问可对公网开放
frp 端口TCPfrp 连接仅限可信 IP
RDP 映射端口TCPfrp RDP 映射仅限可信 IP

强烈建议使用安全组的 IP 白名单 功能,仅允许你自己的 IP 访问 frp 相关端口。


使用方式

浏览器访问:

https://<YOUR_SERVER_IP>/guacamole/
字段
用户名你在 user-mapping.xml 中设置的用户名
密码你在 user-mapping.xml 中设置的密码

登录后点击 My Windows PC → 输入 Windows 用户名密码 → 开始远程桌面。

首次访问 HTTPS 会提示证书不安全(自签名),点击"高级"→"继续前往"即可。建议后续配置 Let's Encrypt 正式证书。


踩坑记录

坑 1:Windows Defender 误删 frpc.exe

现象:frp 远程桌面突然连不上。

排查

powershell
Get-MpThreatDetection  # 查看 Defender 威胁历史

发现 frpc.exe 被标记为潜在有害软件,自动删除。

解决

  1. 添加 Defender 排除项(将路径替换为你的 frp 安装目录):
powershell
Add-MpPreference -ExclusionPath "D:\path\to\your\frp\directory"
  1. 重新下载 frpc.exe,放入排除目录
  2. 将 bat 启动脚本改为 VBS 无窗口启动:
vbscript
' start_frp.vbs - 无窗口启动 frpc
Set WshShell = CreateObject("WScript.Shell")
WshShell.CurrentDirectory = "D:\path\to\your\frp\directory"
WshShell.Run "frpc.exe -c frpc.toml", 0, False

坑 2:Guacamole "Connection already in use by this user"(核心问题)

现象:登录 Guacamole 后,点击连接始终报错:

因为您已超出了单一用户可同时使用的连接数量,Guacamole 服务器拒绝了本连接。
请先关闭至少一个连接再重试。

排查过程(耗时最长):

  1. ❌ 怀疑是连接数限制 → 改大后发现无效
  2. ❌ 怀疑是幽灵会话 → 重启容器、清空数据库,无效
  3. ❌ 怀疑 guacd 和 guacamole 版本不匹配 → 发现 latest 标签指向了不同日期的构建,出现协议冲突
  4. ❌ 锁定具体版本号 → 仍报错
  5. ❌ 降级版本 → 仍报错
  6. 切换到文件认证模式 → 问题消失

根因分析

MySQL 认证模块在 Guacamole 中存在一个并发 Bug:

  • 浏览器发起 WebSocket 隧道请求时,Guacamole 在内存中提前注册了一个条目
  • 但同时浏览器还会发送 HTTP tunnel 作为 fallback,两者几乎同时到达
  • 第二个请求发现内存中已有条目 → 抛出 "already in use"
  • 而第一个请求也可能因其他原因失败
  • 内存中的幽灵条目在超时前会阻止所有重试

最终方案:放弃 MySQL 认证,改用文件认证模式user-mapping.xml)。

文件认证模式的特点:

  • 无需数据库,配置简单,减少攻击面
  • 连接管理更直接,不存在上述 Bug
  • 缺点是不支持动态管理用户/连接(需要修改 XML 后重启容器)
  • 对于个人使用场景完全足够

坑 3:Guacamole 容器无法访问宿主机端口

现象:Guacamole 连接 RDP 失败(白屏或超时)。

原因:容器内 127.0.0.1 指向容器自身,不是宿主机。

解决:使用 Docker 宿主机网关 IP 172.17.0.1(可通过 docker network inspect bridge | grep Gateway 查看)。

坑 4:Docker 镜像下载慢

现象:国内服务器从 Docker Hub 拉取镜像速度极慢。

解决:配置 Docker 镜像加速器:

json
// /etc/docker/daemon.json
{
  "registry-mirrors": [
    "https://docker.1ms.run",
    "https://docker.xuanyuan.me"
  ]
}
bash
systemctl restart docker

坑 5:云安全组默认只开放 22 端口

现象:服务本地运行正常但外部无法访问。

解决:每次新增端口都需要在云服务商控制台 → 安全组 → 添加对应的入站规则。建议使用 IP 白名单 限制访问来源。


日常维护

修改密码

编辑 user-mapping.xml,修改 password 属性,重启容器:

bash
docker restart guacamole

添加新用户/连接

编辑 user-mapping.xml,在 <user-mapping> 内添加新的 <authorize> 块:

xml
<authorize username="another_user" password="another_strong_password">
    <connection name="Another PC">
        <protocol>rdp</protocol>
        <param name="hostname">172.17.0.1</param>
        <param name="port">PORT_NUMBER</param>
    </connection>
</authorize>

安全建议

  • Guacamole 的 8080 端口已在 docker-compose 中绑定到 127.0.0.1,外部无法直连
  • 所有流量通过 nginx HTTPS 加密传输
  • 定期更新 Docker 镜像:docker compose pull && docker compose up -d
  • 定期检查安全组规则,移除不再需要的 IP 白名单条目
  • 建议开启 Guacamole 的连接审计日志

更新证书

自签名证书有效期为 365 天,到期前重新生成:

bash
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
  -keyout /etc/nginx/ssl/server.key \
  -out /etc/nginx/ssl/server.crt \
  -subj '/CN=<YOUR_SERVER_IP>'
systemctl reload nginx

容器维护

bash
# 查看状态
docker compose ps

# 查看日志
docker logs guacamole
docker logs guacd

# 重启
docker compose restart

参考链接

Powered by VitePress