一台装着 Ubuntu 24.04 的工作站摆在实验室角落,屏幕锁着,人不在现场,可偏偏有个图形界面的程序要在这台机器上点几下才能继续跑。SSH 进去改配置、重启服务都没问题,唯独看不到那个已经登录的桌面。这种活儿我碰上过不止一次,最后真正把问题解决掉的,是 x11vnc 这套老牌方案。
x11vnc 干的事情非常朴素:它把当前正在运行的 X11 显示会话直接"共享"出来,而不是另起一个虚拟桌面。这一点和很多人第一次接触远程桌面时的预期不一样——它连的是你已经登录的那个 session,桌面上的窗口、正在跑的程序、任务栏,原封不动地呈现给远程客户端。放在 Ubuntu 24.04 上,这套组合有几个绕不开的坑:默认会话是 Wayland,x11vnc 抓不到画面;Xauthority 路径和以前不同,认证经常失败;systemd 的启动顺序如果没理顺,服务起来的时候 X 还没准备好,客户端连上去就是一片黑。这篇内容把这些环节按我实际操作的顺序拆开讲,从会话类型判定、依赖安装、密码文件、手动跑通、systemd 常驻,一直到客户端调优和故障排查,给的是可以直接照着敲的命令和参数。适合手里有 Ubuntu 桌面机、需要在局域网内远程看屏或者做技术支持的人,也适合刚接触 Linux 远程桌面、被"黑屏"折腾过的新手。
1. 方案想清楚再动手:为什么轮到 x11vnc
1.1 需求边界:要的是"正在跑的那个桌面",不是新开一个
远程桌面这件事,先得分清楚两种完全不同的需求。第一种是"给我一个干净的新桌面",多用户场景、服务器上跑图形程序、或者干脆当远程开发机用,这种情况 xrdp、VNC 的独立会话模式都合适。第二种是"我要看到现在屏幕上到底是什么",比如某台机器上已经登录了图形会话,有个 GUI 程序卡在弹窗上等人点确定,或者需要远程协助现场人员操作,这时候新开一个会话毫无意义——你要看的是那个已经存在的 X server。
x11vnc 属于第二种。它本身不创建 X server,只是 attach 到已有的:0显示上,把帧缓冲内容读出来,编码后通过 RFB 协议推给客户端。这个特性决定了它的优势和局限都很明确:优势是零侵入,被控端不需要改动现有登录状态,装了就能用;局限是它强依赖 X11——纯 Wayland 会话下它看不到东西,因为 Wayland 客户端之间天然隔离,没有全局帧缓冲可以读。
我在选型时一般按下面这个顺序判断:先问"被控端是不是已经有人登录并保持会话",是的话 x11vnc 是首选;再问"是否需要多用户各自独立桌面",是的话换 xrdp 或者 tigervnc 的独立会话模式;最后问"要不要跨平台、要不要文件传输和打印机重定向",需要的话考虑商业方案。绝大多数"我要远程看看这台机器"的需求,落在第一类。
1.2 几种主流做法摆在一起比一比
光说 x11vnc 好没意义,得把它和常见的替代方案放在同一张表里看,取舍才清楚。
| 方案 | 连接对象 | 是否依赖 X11 | 多用户独立会话 | 典型适用场景 |
|---|---|---|---|---|
| x11vnc | 已有登录会话:0 | 是,必须 Xorg | 不支持 | 远程查看/协助已登录桌面 |
| GNOME 内置屏幕共享 | 当前会话 | 否,Wayland 也支持 | 不支持 | 临时协作,配置最简单 |
| xrdp | 新建会话 | 否,可配 Xorg 后端 | 支持 | 多人各自登录,长期使用 |
| TigerVNC 独立服务 | 新建虚拟桌面 | 否 | 支持 | 无头服务器跑图形程序 |
| 各类商业远控 | 当前会话 | 否 | 视产品而定 | 跨公网、需要完整功能 |
从这张表能看出来,x11vnc 的位置很窄但很关键。GNOME 内置的屏幕共享在 Ubuntu 24.04 上其实已经能用了,走的是 PipeWire 那条链路,配置起来比 x11vnc 简单。但它的短板在于授权流程依赖桌面内的弹窗确认,一旦桌面锁屏或者没人点确认,远程就连不上;另外部分老版本客户端对它的编码兼容性一般,画面延迟也偏高。我在需要"无人值守、开机就能连"的场景里,还是更愿意用 x11vnc 加 systemd 常驻。
注意:如果你的机器完全是 Wayland 会话,而且你不打算改回 Xorg,那么 x11vnc 不是正确工具,硬上只会得到黑屏或者 "unable to find X server" 的报错,别在这上面耗时间。
1.3 Ubuntu 24.04 带来的变数:Wayland 默认与会话判定
Ubuntu 从 21.04 起就把 Wayland 设为 GNOME 的默认会话,24.04 延续了这个设定。默认登录进去,echo $XDG_SESSION_TYPE输出的是wayland。而 x11vnc 只能读 X server 的帧缓冲,在 Wayland 会话下它 attach 到的其实是 Xwayland,那只是一个给老程序用的兼容层,画面里只有 Xwayland 自己的窗口,看不到 GNOME 桌面、看不到其他 Wayland 原生应用,连上去大概率是一片黑或者只有零星窗口。
所以第一件要做的事,是把登录会话切到 Xorg。这件事不复杂,但有两个前提得说清楚:一是切换后整机会走 Xorg 渲染链路,日常使用几乎感觉不到差别,但极少数依赖 Wayland 特性的场景(比如某些触控板手势、分数缩放)会有变化;二是切换是全局的,影响所有登录用户,不是只针对你一个人。
还有一个容易被忽略的点:GDM 登录界面本身也是一个会话(greeter),它跑在独立的用户下,Xauthority 路径和普通用户不一样。默认情况下 x11vnc 连的是已登录用户桌面,不是登录界面。如果你的需求是"开机还没人登录时就要能连",那属于另一套配置,需要以 root 身份跑 x11vnc 并指向 greeter 的认证文件,风险更高,后面第 6 节会提到但不建议作为常规方案。
2. 环境准备:把会话拉回 Xorg 再装软件
2.1 先确认当前会话到底是什么类型
动手改任何东西之前,先看清楚现状。这一步很多人跳过,结果配置完发现黑屏,回头再查,浪费半小时。最直接的办法是在图形界面里打开终端,敲三条命令:
echo $XDG_SESSION_TYPE loginctl show-session $(loginctl | awk -v u="$USER" '$3==u {print $1; exit}') -p Type ps -o cmd= -C Xorg第一条输出x11或wayland,这是最直观的判据。第二条从 logind 的会话属性里读类型,结果和第一条应该一致,如果不一致说明环境变量被污染了,以 logind 的为准。第三条是辅助判断——如果Xorg进程存在,说明至少有一个 X server 在跑;在 Wayland 会话下这个进程通常不存在,取而代之的是Xwayland。
再补一条命令看显示号:
ls /tmp/.X11-unix/输出通常会有X0,代表:0显示存在。x11vnc 的-display参数就是指向这个。如果只有一个X0,那-display :0是对的;如果还有别的,得挑对应你桌面的那个。
2.2 切换到 Xorg 会话的两条路径
切换方式有两种,选哪种取决于你需不需要让每次登录都固定走 Xorg。
临时切换用登录界面的齿轮菜单:注销回到 GDM 登录页,点用户名右下角那个齿轮图标,选 "Ubuntu on Xorg",再输密码登录。这种方式只对当次会话生效,重启后又回到默认。适合只是临时用一次、不想改动系统配置的场景。
永久切换改 GDM 配置文件:
sudo sed -i 's/^#WaylandEnable=false/WaylandEnable=false/' /etc/gdm3/custom.conf grep -n WaylandEnable /etc/gdm3/custom.conf如果配置文件里原本没有WaylandEnable这一行(24.04 的默认文件里它可能被注释掉了),那就手动补一行到[daemon]段落下:
[daemon] WaylandEnable=false改完重启 GDM:
sudo systemctl restart gdm3注意:重启 gdm3 会把你当前的图形会话整个杀掉,正在跑的程序会全部退出。别在还有未保存工作的时候执行这条命令,先存盘、先退出程序。
重启后在登录界面确认齿轮里选的是 "Ubuntu on Xorg",登录进去再用前面的命令验证一次,XDG_SESSION_TYPE应该输出x11。
2.3 安装 x11vnc 与配套工具
软件安装本身没什么难度,但有几个配套包建议一起装上,后面调优和排错会用到:
sudo apt update sudo apt install -y x11vnc net-tools x11-utils x11-xserver-utils x11vnc -versionx11vnc -version会打印版本号和编译时间,Ubuntu 24.04 仓库里给的是 0.9.16 这一支,功能完整,够用。顺带说一句,不建议自己去源码编译最新版,除非你碰到了仓库版本里的明确 bug——自己编译出来的二进制在 systemd 里跑,路径、依赖、库版本全是坑,收益不成正比。
net-tools提供netstat,排查端口监听时比ss直观一点;x11-utils里有xdpyinfo,可以用来验证 X server 是否能正常访问;x11-xserver-utils里有xhost,调试权限问题时能用上。这几个包装完不占多少空间。
装完之后先别急着跑服务,用xdpyinfo做一次连通性验证:
DISPLAY=:0 xdpyinfo | head -n 20能打印出name of display: :0、版本号、屏幕尺寸这些信息,说明当前用户有权限访问这个 X server。如果报unable to open display,那问题在权限或者DISPLAY变量,先解决这个,后面 x11vnc 才有戏。
3. 从一条命令跑通画面,再谈常驻
3.1 手动启动命令与每个参数的理由
第一次配置不建议直接写 systemd 单元,先在终端里手动跑通,看清楚输出日志,确认客户端能连上,再固化。这样一旦出问题,改动的影响面最小。
我常用的一条启动命令长这样:
x11vnc -display :0 -auth guess -rfbauth ~/.vnc/passwd \ -rfbport 5900 -localhost -forever -shared \ -noxdamage -repeat -nolookup -nowf参数逐个说清楚,因为很多人是抄了命令但不知道每一项在干什么,出问题就无从下手。
-display :0指定要共享的 X 显示号。前面用ls /tmp/.X11-unix/看到的那个编号就是它。
-auth guess让 x11vnc 自己去猜 Xauthority 文件的位置。它会去读 X 进程的命令行参数、检查常见的认证文件路径、查看$XAUTHORITY环境变量。大多数情况下能猜对,猜不对就得手动指定,下一小节专门讲。
-rfbauth ~/.vnc/passwd指定密码文件。x11vnc 支持明文密码参数-passwd,但那会把密码暴露在ps输出里,任何能登机器的用户都能看到。用密码文件是基本要求。
-rfbport 5900指定监听端口。RFB 协议默认端口和显示号有固定对应关系::0对应 5900,:1对应 5901,公式就是5900 + N。你当然可以随便改,但保持默认能让客户端少填一个参数。
-localhost只监听回环地址。这一条是安全底线,意味着 5900 端口只对本机可见,外部要连必须走 SSH 隧道或者额外做转发。直接裸奔在 0.0.0.0 上,在局域网里也许还能接受,但在任何有外网入口的环境里都是自找麻烦。
-forever让 x11vnc 在客户端断开后继续监听,而不是跟着退出。不加这个参数,第一个客户端断开服务就结束了,下次连接直接拒绝。
-shared允许多个客户端同时连接。单人使用不加也行,但加上没坏处,多人协作时有用。
-noxdamage关闭 XDamage 扩展的使用。XDamage 本来是用来做增量更新、降低带宽的,但在某些显卡驱动和桌面组合下会导致画面不刷新、局部花屏。这个参数属于"出问题就加、没出问题也可以加"的类型,代价是带宽略高。
-repeat让 x11vnc 自己处理按键重复。不加的话,从某些客户端按住方向键不会有连续移动效果,得靠客户端本地模拟,体验很差。
-nolookup跳过对客户端 IP 的反向 DNS 解析。不加的话,每次连接都可能卡几秒钟等 DNS 超时,尤其在内网 DNS 配置不规范的机器上特别明显。
-nowf关闭文件传输功能。x11vnc 的-transferfile之类的文件传输用得少,开着只会增加攻击面。不需要就别开。
启动之后终端会刷出一大段日志,里面会有类似这样的行:
PORT=5900 rfbListenOnTCP6Port: error = Address already in use如果看到Address already in use,说明 5900 被占了,换个端口或者查一下是不是已经跑了一个实例。最后出现The VNC desktop is: hostname:0和PORT=5900,基本就是起来了。
3.2 认证文件(Xauthority)怎么找
-auth guess失败的典型症状是:x11vnc 起来了,端口也监听了,但客户端连上去是黑屏,日志里有XOpenDisplay failed或者Xlib: connection to ":0.0" refused by server之类。根因是 x11vnc 没找到正确的 Xauthority,没通过 X server 的认证,自然读不到画面。
手动找的办法有这么几个,按可靠性排序。
第一,直接看 X 进程的启动参数:
ps -o args= -C Xorg输出里通常会带-auth /path/to/Xauthority,那个路径就是答案。Ubuntu 24.04 上常见的是/run/user/1000/gdm/Xauthority或者用户家目录下的.Xauthority。
第二,读环境变量:
echo $XAUTHORITY在已经登录的桌面终端里,这个变量一般是有值的,直接把它抄进 x11vnc 的-auth参数即可。
第三,从 logind 拿:
loginctl show-session $(loginctl | awk -v u="$USER" '$3==u {print $1; exit}') -p Type -p Display拿到会话号和显示号,再结合/run/user/<uid>/目录下的内容找。这个目录下每个用户都有,里面可能同时存在gdm/Xauthority和.mutter-Xwaylandauth.XXXXXX两种文件。前者是 GDM 拉起 Xorg 时用的认证文件,后者是 Wayland 会话下 Xwayland 用的,别搞混。
找到之后写死到命令里:
x11vnc -display :0 -auth "$XAUTHORITY" -rfbauth ~/.vnc/passwd \ -rfbport 5900 -localhost -forever -shared -noxdamage -repeat -nolookup注意:Xauthority 文件是会话级的,每次重启、每次重新登录都可能换路径(尤其是文件名里带随机串的那种)。所以写死路径在 systemd 单元里不是好做法,除非你确认路径稳定。更稳妥的是在服务启动脚本里动态取
$XAUTHORITY,或者继续用-auth guess加一个保底的ExecStartPre检测。
3.3 密码文件与权限收口
密码文件这一步有个细节很多人做错:x11vnc -storepasswd默认写到家目录下的.vnc/passwd,而且它不会帮你建目录。第一次用的时候得先建目录:
mkdir -p ~/.vnc chmod 700 ~/.vnc x11vnc -storepasswd执行后它交互式问你要密码,输两遍,然后问要不要写进文件,回y。生成的文件用-rfbauth引用。
非交互的方式适合自动化脚本:
x11vnc -storepasswd '你的密码' ~/.vnc/passwd密码限制在 8 个字符以内(RFB 协议 VNC 认证的长度限制,超出的部分会被截断),也就是你输 12 位密码实际只用了前 8 位,这是个很容易踩的认知差。想用更长的密码得换认证方式,但那要客户端配合,不在常规范围内。
权限必须收紧:
chmod 600 ~/.vnc/passwd ls -l ~/.vnc/passwd输出应该是-rw-------。如果这个文件被同组或者其他用户可读,等于把远程桌面密码交出去了。x11vnc -storepasswd生成的密码是经过 3DES 加密的,不是明文,但对方拿到加密文件一样能用来连你的机器,所以权限该收还是要收。
密码位数短是个现实问题,所以我在所有需要远程的机器上都坚持一条:x11vnc 只监听-localhost,外部访问一律走 SSH 隧道。密码只是第二道门,第一道门是 SSH 的密钥认证。这样即使 VNC 密码只有 8 位,整体安全性还是由 SSH 兜着。
4. 交给 systemd:开机自启与稳定常驻
4.1 用户级服务与系统级服务的取舍
手动跑通之后要解决的第一个问题是"别每次登录都手敲一遍",第二个是"机器重启后自动起来"。这两个需求都指向 systemd,但具体用哪种服务类型,得看你的使用场景。
用户级服务(systemctl --user)跟着登录会话走,用户登录后才启动,注销就停。它的好处是天然能拿到$XAUTHORITY这类会话环境变量,不需要额外配置;坏处是必须在用户登录之后才生效,如果机器重启后停在登录界面没人登录,服务不会运行。
系统级服务(systemctl,带User=参数)在系统启动阶段就跑,不依赖用户登录。理论上可以更早提供服务,但要自己指定DISPLAY和XAUTHORITY,而且启动时机如果早于 X server 就绪,会启动失败然后靠重启策略兜底。
我的实际选择是:用户级服务为主,系统级服务在明确需要"无人登录也要连"时才用。大部分"远程看一下已登录桌面"的需求,用户级服务已经完全覆盖,配置也简单。下面两小节分别给出可用配置。
4.2 一个能落地的用户级 unit 文件
创建目录和文件:
mkdir -p ~/.config/systemd/user vim ~/.config/systemd/user/x11vnc.service内容:
[Unit] Description=x11vnc server sharing the current X11 desktop After=graphical-session.target PartOf=graphical-session.target [Service] Type=simple Environment=DISPLAY=:0 ExecStartPre=/bin/sh -c 'sleep 3' ExecStart=/usr/bin/x11vnc -display :0 -auth guess \ -rfbauth %h/.vnc/passwd -rfbport 5900 -localhost \ -forever -shared -noxdamage -repeat -nolookup -nowf Restart=on-failure RestartSec=5 [Install] WantedBy=graphical-session.target几个关键点解释一下。
After=graphical-session.target保证它在图形会话起来之后再启动。PartOf让会话结束时这个服务也跟着停,避免残留。
ExecStartPre=/bin/sh -c 'sleep 3'是个土办法但很有效。GNOME 的会话目标在 X server 就绪和窗口管理器完全起来之间有个几秒的空窗,x11vnc 在这个窗口期启动,-auth guess可能拿不到认证文件,服务直接失败。加个短暂延迟能显著降低首次启动失败率。比这更优雅的做法是写一个循环检测脚本,但对多数人来说没必要。
%h是 systemd 的占位符,展开成用户家目录。这样单元文件不用写死用户名。
Restart=on-failure配合RestartSec=5,让它在出错后自动重试,不会因为一次失败就彻底不工作。
启用并启动:
systemctl --user daemon-reload systemctl --user enable --now x11vnc.service systemctl --user status x11vnc.servicestatus输出里Active: active (running)就对了。想看更细的日志用:
journalctl --user -u x11vnc.service -f如果希望用户没登录时服务也能跑(比如远程重启后立即能连),需要开 linger:
sudo loginctl enable-linger $USER但要注意,linger 只保证用户的服务管理器常驻,不保证图形会话存在。你的图形会话还是得有人登录或者配置自动登录才有:0显示。所以真有这个需求,得配合 GDM 的自动登录一起配置,那是另一个话题。
4.3 系统级服务的写法与启动顺序
明确需要系统级服务时,配置文件放在/etc/systemd/system/x11vnc.service:
[Unit] Description=x11vnc server for display :0 After=display-manager.service network-online.target Wants=display-manager.service [Service] Type=simple User=你的用户名 Environment=DISPLAY=:0 Environment=XAUTHORITY=/run/user/1000/gdm/Xauthority ExecStart=/usr/bin/x11vnc -display :0 -auth %E{XAUTHORITY} \ -rfbauth /home/你的用户名/.vnc/passwd -rfbport 5900 -localhost \ -forever -shared -noxdamage -repeat -nolookup -nowf Restart=always RestartSec=10 [Install] WantedBy=multi-user.target这里的难点全在XAUTHORITY。写死/run/user/1000/gdm/Xauthority只在特定环境下成立——GDM 拉起 Xorg 时的确会把认证文件放在那儿,但路径里的 uid 和文件名可能变。更稳的写法是用一个包装脚本,在脚本里动态探测:
#!/bin/bash AUTH="" for f in /run/user/$(id -u)/gdm/Xauthority \ /run/user/$(id -u)/.mutter-Xwaylandauth.* \ "$HOME/.Xauthority"; do if [ -f "$f" ]; then AUTH="$f"; break; fi done [ -z "$AUTH" ] && { echo "no Xauthority found"; exit 1; } exec /usr/bin/x11vnc -display :0 -auth "$AUTH" \ -rfbauth /home/你的用户名/.vnc/passwd -rfbport 5900 -localhost \ -forever -shared -noxdamage -repeat -nolookup -nowf把这个脚本存成/usr/local/bin/x11vnc-start.sh,chmod 755,然后ExecStart=/usr/local/bin/x11vnc-start.sh。多一层包装换来的是环境变化时不用改单元文件。
参数与启动时机对照,整理成表方便查阅:
| 配置项 | 用户级服务 | 系统级服务 |
|---|---|---|
| 触发目标 | graphical-session.target | multi-user.target |
| 依赖顺序 | After=graphical-session.target | After=display-manager.service |
| 环境变量 | 自动继承会话环境 | 需手动指定 DISPLAY/XAUTHORITY |
| 无人登录时可用 | 否 | 是(但仍需 X server 存在) |
| 调试方式 | journalctl --user -u x11vnc | journalctl -u x11vnc |
装完别忘sudo systemctl daemon-reload再enable --now,忘了 daemon-reload 会出现"改了但没生效"的假象。
5. 客户端接入与画质调优
5.1 客户端选型与基础连接
服务端跑起来了,客户端这边其实选择不多,常用的就那几个。Linux 桌面自带的 Remmina 是最省事的,Ubuntu 24.04 上装一下就有:
sudo apt install -y remmina remmina-plugin-vnc新建连接时协议选 VNC,地址填127.0.0.1:5900(走隧道的情况)或者内网 IP(直接连的情况),用户名留空——x11vnc 的 VNC 认证只用密码,不区分用户名。颜色深度选 24 位真彩,质量按网络情况调。
TigerVNC 的客户端vncviewer也常用,命令行参数更细,适合脚本化或者需要精确控制的场景:
vncviewer -Shared -ViewOnly=0 127.0.0.1::5900注意::5900是双冒号,表示直接指定端口;单冒号后跟的是显示号。这个语法坑过不少人,写成单冒号程序会去算5900 + 0,结果一样,但写127.0.0.1:1就变成连 5901 了。RealVNC Viewer 体验也不错,剪贴板和缩放做得顺滑,缺点是免费版功能有阉割。
5.2 画质与带宽的折中怎么算
VNC 的带宽不是线性增长的,理解它的更新机制才能给出合理的参数。RFB 协议的核心是"只传变化的区域":屏幕静止时几乎不传数据,有变化时按矩形脏区推送。所以真正决定带宽的是变化频率和变化面积,不是分辨率本身。
粗略算一笔账:1920×1080 的 24 位色一屏原始数据是 1920×1080×3 ≈ 6.2 MB。ZRLE 编码在桌面上典型的压缩比是 5:1 到 15:1,取中间值 10:1,一屏全刷约 620 KB。如果每秒全屏刷新 10 次,就是 6.2 MB/s,折合 50 Mbps——这显然不现实。但实际使用中很少全屏刷新,拖动窗口、滚动网页这类操作涉及的区域通常占总面积的 10%~30%,折算下来 5~15 Mbps 是常见水平,局域网千兆完全不是问题。
真正需要调参的是跨网段或者 Wi-Fi 场景下:
| 参数 | 取值范围 | 作用 | 我的建议 |
|---|---|---|---|
-compresslevel | 0-9 | 压缩强度,越高越省带宽越吃 CPU | 局域网 0-2,跨网段 6-9 |
-quality | 0-9 | JPEG 类编码的画质 | 文字办公 6-7,看图 8-9 |
-threads | 整数 | 多线程编码 | 多核机器可加,单核别加 |
-ncache | 0 或像素数 | 客户端像素缓存 | 一般填 0,容易引发黑屏 |
-norc | 无 | 不读配置文件 | 排查参数冲突时用 |
-compresslevel和-quality只对支持对应编码的客户端生效。Remmina 和 TigerVNC 都支持 ZRLE 和 Tight,所以这两个参数是管用的;如果你的客户端只支持原始 Raw 编码,加什么参数都没用,带宽照旧。
-ncache是个历史遗留的优化,把像素缓存在客户端减少重传,但它和某些客户端配合时会导致画面撕裂或者黑屏,我在生产环境里一律关掉。
5.3 走 SSH 端口转发,别把 5900 直接摊在网上
即使-localhost已经限制了监听地址,想要从另一台机器连过来,还是需要一个通道。最稳妥的做法是 SSH 本地端口转发:
ssh -N -L 5900:127.0.0.1:5900 用户名@192.168.1.50这条命令的意思是:在本地开 5900 端口,所有连到本地 5900 的流量,通过 SSH 隧道转发到远端机器的 127.0.0.1:5900。-N表示不执行远程命令,只做转发。
然后客户端连127.0.0.1:5900就行,看起来像连本机,实际数据走的是加密隧道。服务端的-localhost和这条隧道是配套的:服务端口不对外,隧道负责把流量安全地带进来。
如果想省掉每次敲命令,写进~/.ssh/config:
Host office-pc HostName 192.168.1.50 User 用户名 LocalForward 5900 127.0.0.1:5900 ServerAliveInterval 30以后ssh office-pc建立连接的同时隧道就通了,直接开客户端连本地 5900。ServerAliveInterval 30是保活,防止长时间不动被中间设备掐断。
注意:端口转发里的
127.0.0.1是站在远端机器的视角写的,指向远端自己的回环地址。写成远端的局域网 IP 会让转发绕一圈,而且如果服务端只监听-localhost,那个 IP 根本没监听,转发会失败。
5.4 防火墙规则与监听确认
如果确实需要在局域网内直连(比如同一网段的几台机器互相看屏),那就得放开防火墙,但范围要收窄。Ubuntu 默认用 ufw:
sudo ufw allow from 192.168.1.0/24 to any port 5900 proto tcp comment 'vnc lan' sudo ufw status numbered只允许特定网段,不要用ufw allow 5900,那等于对所有来源开放。改完规则用另一台机器nc -vz 192.168.1.50 5900验证端口可达,再开客户端连。
监听状态确认用:
ss -tlnp | grep 5900输出里Local Address:Port应该是127.0.0.1:5900(配了-localhost)或者0.0.0.0:5900。看到0.0.0.0而你没打算对外暴露,就说明-localhost没生效,回去检查服务参数。
6. 常见问题与排查实录
6.1 黑屏、灰屏、只有壁纸没有窗口
这是最高频的问题,表现形式是客户端连上了、认证也过了,但画面是纯黑,或者只有壁纸,桌面图标和窗口全都看不到。
根因基本可以归到三类。第一类是会话类型不对——服务端其实是 Wayland 会话,x11vnc attach 到 Xwayland 上,只能看到 Xwayland 应用。验证方法是在服务端终端看echo $XDG_SESSION_TYPE,是wayland就按第 2 节的方法切 Xorg。
第二类是认证文件错了,但错得"刚好能过"。x11vnc 用了一个能打开 display 但指向不同 screen 的认证文件,结果读到的是空区域。这种情况看日志最准,启动时加-verbose会打印它实际用的XAUTHORITY:
x11vnc -display :0 -auth guess -verbose -rfbauth ~/.vnc/passwd日志里找Xauthority那几行,确认路径和$XAUTHORITY一致。
第三类是 XDamage 或者合成器的问题。GNOME 的窗口管理器 mutter 开了合成,某些驱动下 x11vnc 读不到合成后的画面。加-noxdamage通常能解决,还不行再试-noxrecord:
x11vnc -display :0 -auth guess -noxdamage -noxrecord -rfbauth ~/.vnc/passwd如果连壁纸都没有、全黑,还有一种可能是屏幕处于 DPMS 关闭状态(显示器休眠)。x11vnc 有-forcedpms参数可以强制唤醒,但更稳的做法是在系统设置里把自动息屏关掉,或者装caffeine之类的工具做抑制。
6.2 认证失败与密码文件问题
报错信息通常长这样:
Xlib: connection to ":0.0" refused by server Xlib: No protocol specified前一条是 X server 层面的认证失败,说明 Xauthority 不对;后一条是 x11vnc 自身和 X 通信被拒,同样指向认证。
排查顺序:先在服务端用同一个用户跑DISPLAY=:0 xdpyinfo,能通说明权限没问题,问题在 x11vnc 拿的认证文件;不能通说明当前用户根本无权访问这个 X server,可能是用 sudo 切了用户,或者会话属于另一个账号。
VNC 密码错误的表现不同,是客户端提示"Authentication failure"或"password incorrect"。常见原因是密码超过 8 位被截断,你在客户端输了完整密码反而不对——试前 8 位。另一个原因是密码文件权限不对或者路径写错,服务端日志里会有rfbauth failed之类的记录。
还有一种隐蔽情况:之前跑过一个实例没退干净,新实例启动失败但 systemd 显示 active。检查:
ps aux | grep x11vnc ss -tlnp | grep 5900两个都看一下,有残留进程就kill掉再重启服务。
6.3 键盘、剪贴板与输入法
键盘出问题的典型症状是:按下的键没反应、方向键不连续、符号键错位。前两个是-repeat参数没加,第三个通常是键盘布局不匹配。
符号错位的情况,x11vnc 有个-xkb参数用于处理 XKEYBOARD 扩展,在部分环境下能修正映射:
x11vnc -xkb -repeat -display :0 ...如果还是错,检查服务端的键盘布局设置:
setxkbmap -query确认 layout 是us还是cn,客户端那边也要设成一致。有些客户端(比如老版本 Remmina)在连接时会强制用客户端本地的布局,导致服务端布局被覆盖,这种情况在客户端设置里把"使用客户端键盘布局"关掉。
剪贴板同步依赖服务端的剪贴板管理器。x11vnc 本身会尝试同步 PRIMARY 和 CLIPBOARD 两个选择区,但在 GNOME 下有时不生效。补装xclip和autocutsel能改善:
sudo apt install -y xclip autocutsel autocutsel -selection PRIMARY -fork autocutsel -selection CLIPBOARD -fork这两条命令让两个选择区保持一致,双向复制粘贴基本就通了。中文输入法在远程端一般能用,但如果服务端用的是 fcitx5 且客户端输入时出现候选框位置错乱,那是输入法自己的窗口定位问题,跟 x11vnc 关系不大,改用客户端本地输入再粘贴过去更省事。
6.4 卡顿、断连与画面延迟
卡顿的排查要分清楚是网络问题还是服务端渲染问题。判断方法:在客户端看画面静止时是否流畅(应该完全不耗带宽),如果静止时也卡,那是编码或者客户端渲染的问题;只有操作时卡,那是带宽或者延迟问题。
服务端这边能做的优化有限,主要是关掉不必要的特性:
x11vnc -display :0 -auth guess -rfbauth ~/.vnc/passwd \ -noxdamage -repeat -nolookup -nowf -noscrollcopyrect \ -compresslevel 2 -quality 7-noscrollcopyrect关掉滚动优化,在某些客户端上反而更稳。-compresslevel 2在局域网里足够,CPU 占用低。
网络这边的优化:优先用有线,Wi-Fi 下 2.4G 频段基本没法用,5G 也要看信号质量。SSH 隧道本身有加密开销,但现代 CPU 上 AES 加速后影响很小,不是瓶颈。真正影响大的是 RTT,跨城市连接即使带宽充足,操作延迟也会到 50ms 以上,鼠标跟手性明显下降,这是物理距离决定的,调参解决不了。
断连问题看日志:
journalctl --user -u x11vnc.service -n 100 --no-pager常见原因是服务端的 X 会话重启(比如用户注销又登录),导致 x11vnc 失去连接。这种情况下Restart=on-failure会把它拉起来,但会有几秒中断。要彻底避免,得让服务跟着会话走,也就是用户级服务加PartOf=graphical-session.target的配置。
6.5 排查速查表
把前面这些症状和处置方式整理成表,出问题时按图索骥能省不少时间。
| 症状 | 最可能原因 | 处置动作 |
|---|---|---|
| 客户端连接被拒 | 服务未启动或端口被占 | systemctl --user status,ss -tlnp | grep 5900 |
| 连上后纯黑屏 | 会话是 Wayland | 切 Xorg 会话后重试 |
| 只有壁纸无窗口 | Xauthority 不对 | -verbose看日志,手动指定-auth |
| 认证失败 | 密码超 8 位或文件权限 | 试前 8 位,chmod 600 |
| 画面局部花屏 | XDamage 兼容问题 | 加-noxdamage |
| 按住方向键不动 | 缺按键重复处理 | 加-repeat |
| 连接卡数秒才出画面 | 反向 DNS 超时 | 加-nolookup |
| 服务开机不自启 | 未 enable 或 linger 未开 | systemctl --user enable,loginctl enable-linger |
| 服务起来就退出 | X 会话未就绪 | 加ExecStartPre延迟或改启动目标 |
| 剪贴板不通 | 选择区不同步 | 装autocutsel并 fork |
7. 长期维护与几条实操经验
7.1 权限最小化与暴露面控制
x11vnc 本质上是一个"把桌面完整交给对方"的服务,连上之后对方能看到你屏幕上的所有内容、能操作你的鼠标键盘,权限等同于坐在机器前。所以能收的口子都要收。
监听地址坚持-localhost,这是第一道。SSH 隧道是第二道,密钥认证比密码强得多。VNC 密码是第三道,8 位虽然不长,但配合前两道已经够用。文件传输用-nowf关掉,少一个功能少一份风险。如果机器上有多人使用,用户级服务比系统级更合适,因为只有会话所有者能启动和停止它。
日志里偶尔会看到陌生 IP 尝试连接,说明端口暴露了。定期ss -tlnp看一眼监听状态,发现0.0.0.0:5900就立刻改回-localhost。
7.2 升级与桌面栈变化的应对
Ubuntu 每次大版本升级,桌面栈都可能动。24.04 到 26.04 之间,GNOME 版本、GDM 配置路径、Xorg 是否还默认提供,这些都可能变。我的做法是在升级前把当前可用的完整命令和 unit 文件备份到/root/backup/或者个人的 dotfiles 仓库里,升级后直接对比差异。
具体要备份的东西:~/.config/systemd/user/x11vnc.service、~/.vnc/passwd、/etc/gdm3/custom.conf、以及那条手动启动命令。这四个东西凑齐,换台机器十分钟就能重建环境。
升级后第一件事验证x11vnc -version和DISPLAY=:0 xdpyinfo,两个都正常再启服务。如果xdpyinfo报错但图形界面正常,多半是 Xauthority 路径变了,按 3.2 节的方法重新找一遍。
7.3 我个人踩过的几个坑
第一个坑是早期图省事把-auth guess去掉、直接用-auth /home/user/.Xauthority,结果某次系统更新后这个文件不存在了(Ubuntu 把认证文件挪到了/run/user/1000/下),服务静默失败,systemctl status显示inactive (dead),日志里只有一行XOpenDisplay failed。从那以后我写 unit 文件都留一个ExecStartPre做前置检查,路径不通就直接报错退出,而不是让服务处于半死不活的状态。
第二个坑是密码长度。给人配环境时设了一个 10 位密码,客户端死活连不上,两边都以为是网络问题,折腾了四十分钟才想起来 RFB 协议只认前 8 位。现在我的做法是密码统一设 8 位,复杂度和可靠性之间找个平衡,安全性靠 SSH 隧道补。
第三个坑是 systemd 的启动顺序。用户级服务配了After=graphical-session.target,但 GNOME 的会话目标在systemctl --user list-units里有时显示 inactive,服务跟着不启动。根因是 GNOME 的graphical-session.target需要systemd-oomd或者相关组件触发,纯命令行登录的情况下它根本不会 active。解决方式是改成WantedBy=default.target配合ExecStartPre里的循环等待,虽然不够"正统",但实际最稳。
第四个坑是分辨率变化。笔记本外接显示器拔插之后,X 的屏幕尺寸变了,x11vnc 还按老尺寸推送,客户端看到的画面会被裁掉一块。这种情况重启一下服务就好,或者加-noxinerama让它按单个屏幕处理。这不是 x11vnc 的 bug,是 X RandR 动态调整后服务端没有自动跟随。
最后一个经验是关于"要不要开机自启"这件事。如果这台机器只是偶尔需要远程看一眼,我反而不建议配成常驻服务——常驻意味着攻击面常驻,而且 systemd 拉起失败时排查成本更高。更轻量的做法是写一个别名或者小脚本,需要的时候 SSH 进去执行一下,用完就退。真正需要 7×24 可连的场景(比如远程值守的工作站),再上 systemd 常驻,并且把日志、重启策略、监控都配齐。
配好之后我一直会做一件事:拿另一台机器模拟一次完整流程,从 SSH 隧道建立、客户端连上、复制粘贴、切窗口、断线重连,全部走一遍。这套流程跑通了,才算这台机器真的"可以远程"。光看服务状态是 active 就交付,后面大概率还要被叫回来一次。