1. 为什么 Ubuntu 20.04 上的 VNC 方案要先挑对再动手
远程访问一台 Ubuntu 机器的图形界面,最常见的翻车现场不是装不上,而是装完之后客户端连上了、密码也过了,屏幕上却是一片纯黑,或者只有一个孤零零的终端窗口。很多人第一反应是"配置写错了",然后开始疯狂改xstartup,改到最后连哪个文件是有效的都搞不清。我前后在十几台机器上折腾过这套东西,从 16.04 一路用到 22.04,结论很明确:Ubuntu 20.04 上跑 VNC,方案选型占七成,配置只占三成。选错组件,后面所有调试都是在填坑。
这篇内容面向的是需要在 Ubuntu 20.04(桌面版或带 GUI 的服务器版)上开启远程图形访问的开发者、测试人员和运维同学。不管你是想远程跑一下 IDE、看个仿真界面,还是做自动化测试需要图形环境,下面的整套流程都可以直接照着复现。默认你会用sudo,也默认你至少能通过 SSH 登录这台机器——后面所有操作都是命令行完成的,不需要坐在机器前面。
1.1 vnc4server 和 tightvnc 在 20.04 上已经不合时宜
你在搜索引擎里翻到的绝大多数中文教程,发布时间大概在 2015 到 2018 年之间,里面写的都是apt install vnc4server或者apt install tightvncserver。这两条命令在 Ubuntu 20.04 上还能装,但装完之后的问题非常密集。
vnc4server这个包在 Ubuntu 20.04 的官方源里其实已经不存在了,apt会直接报Unable to locate package。有些教程会让你去下载.deb手动装,装是能装上,但它依赖的Xvnc是基于很老的 X 服务器分支,跟 20.04 默认的 GNOME 3.36 会话配合时,gnome-shell经常直接崩溃,表现为连接后黑屏几秒然后断开。
tightvncserver虽然还在源里,但它对现代的 X 扩展支持不完整,尤其是缺少XKB相关的更新,会导致键盘映射错乱——你敲a出来的是别的字符,或者修饰键完全失效。我最早就是用 tightvnc 起步的,被键盘问题折磨了一下午才换掉。
1.2 TigerVNC 和 x11vnc 到底该选哪个
目前在 Ubuntu 20.04 上真正值得用的只有两个:TigerVNC和x11vnc。它们不是同一类东西,很多人一开始就搞混了。
TigerVNC 是"独立会话"模式。它自己拉起一个新的 X 服务器实例,你在 VNC 里看到的桌面和你坐在物理显示器前看到的桌面是两个完全独立的会话。好处是互不干扰,你甚至可以同时开:1、:2两个会话;坏处是你在这边打开的程序,在物理屏幕上找不到。
x11vnc 是"镜像共享"模式。它把已经运行的那个物理 X 会话直接共享出去,你在 VNC 里看到的就是物理屏幕上的画面,鼠标动一下两边同步。代价是它依赖那个已经存在的 X 会话,如果机器重启后没人登录图形界面,x11vnc 就没有东西可以共享。
我的建议很直接:需要远程操作一个真实存在的桌面,选 x11vnc;需要一台无头机器长期提供图形环境,选 TigerVNC。下面整套流程以 TigerVNC 为主线,因为它的适用场景更广,也更容易做成开机自启。
1.3 动手前必须确认会话类型是 Xorg 还是 Wayland
这是被 90% 的教程跳过的一步,也是 20.04 上最容易让人白忙活的地方。
Ubuntu 20.04 默认使用Wayland作为显示服务器,而所有基于 X 的 VNC 方案(包括 TigerVNC)在 Wayland 会话下都无法正常工作。你得先确认当前登录的会话类型,命令很简单:
echo $XDG_SESSION_TYPE loginctl show-session $(loginctl | grep $(whoami) | awk '{print $1}') -p Type如果输出是wayland,你需要切回 Xorg。改法不是去改环境变量,而是修改 GDM 的配置文件:
sudo nano /etc/gdm3/custom.conf把这一行前面的注释符号去掉:
WaylandEnable=false然后重启机器。重启后重新登录,再执行一次上面的命令确认输出变成x11。
注意:这一步改了之后,整个系统的图形会话都会跑在 Xorg 上,不只是 VNC 用得到。对于开发机来说这通常没什么影响,反而能避开 Wayland 下一些截图工具和录屏工具的兼容问题。
2. 从 apt 装包到 5901 端口跑通的全过程
选型确定之后,安装本身其实只有几分钟。但这里有几个细节如果你不注意,会在后面排查故障时多绕很多弯路。我习惯把安装拆成"装包、设密码、首次启动、验证端口"四步,每一步都留下可验证的输出,出问题时就能快速定位到是哪一层断掉了。
2.1 装包与轻量桌面环境的取舍
先更新索引再安装:
sudo apt update sudo apt install -y tigervnc-standalone-server tigervnc-common tigervnc-tools三个包的分工是:tigervnc-standalone-server提供Xvnc和vncserver脚本,tigervnc-common提供vncpasswd等工具,tigervnc-tools里有vncviewer之类的辅助程序。装完之后可以用dpkg -l | grep tigervnc确认版本,Ubuntu 20.04 源里对应的是 1.10.x。
接下来是我强烈建议做的一件事:别直接用默认的 GNOME 会话当 VNC 桌面。原因很实际——gnome-shell需要 3D 加速,而 TigerVNC 提供的是软件渲染的虚拟显示,两者配合时经常出现窗口管理器启动失败、顶栏不渲染、拖窗口有严重残影等问题。我试过各种绕法,包括用gnome-session --session=ubuntu和各种环境变量 hack,效果都不理想。
更省心的做法是额外装一个轻量桌面:
sudo apt install -y gnome-flashback # 或者用更轻的 XFCE sudo apt install -y xfce4 xfce4-goodiesgnome-flashback是 GNOME 的经典模式,用的是metacity窗口管理器,对软件渲染友好,界面跟 GNOME 2 类似,实际用起来完全不别扭。XFCE 更轻,资源占用小,适合配置不高的虚拟机。我个人的默认选择是gnome-flashback,因为它跟 Ubuntu 的主题和字体已经统一好了,不用再单独折腾外观。
2.2 用 vncpasswd 设置密码并理解它的长度限制
密码必须用这个专用命令设置,不能手写文件:
vncpasswd它会让你输入两遍密码,然后问一个view-only password,这个直接按n跳过。设置完成后会在~/.vnc/目录下生成一个passwd文件。
这里有个必须知道的事实:VNC 协议本身的认证密码只取前 8 个字符。你输入 20 位密码,第 9 位开始全部被忽略。这不是 TigerVNC 的 bug,是 RFB 协议的历史遗留设计。所以别指望靠长密码保安全,真正的防护手段是限制监听地址,或者叠加 SSH 端口转发——这个放到第 5 章细说。
顺便说一句,~/.vnc/passwd的权限会被自动设成 600,不要手动改成 644,否则vncserver会拒绝加载并报WARNING: The VNC server is not password protected。
2.3 首次启动和显示号、端口的换算关系
首次启动之前,先手动建一个空的~/.vnc/xstartup占位,防止脚本报找不到文件:
mkdir -p ~/.vnc touch ~/.vnc/xstartup chmod +x ~/.vnc/xstartup然后启动第一个会话:
vncserver :1 -geometry 1920x1080 -depth 24这条命令里的参数含义分别是::1是显示号,-geometry决定虚拟屏幕分辨率,-depth是颜色深度。启动成功后终端会输出类似这样的内容:
New 'machine-name:1 (youruser)' desktop is machine-name:1 Starting applications specified in /home/youruser/.vnc/xstartup Log file: /home/youruser/.vnc/machine-name:1.log这里有个新手最容易晕的点:显示号:1对应的端口是 5901,不是 5900。换算规则是端口 = 5900 + 显示号。所以:1→ 5901,:2→ 5902,:10→ 5910。客户端连接时要填地址:1或者地址:5901,两种写法都可以,但填错端口号是"连不上"这类问题里最高频的原因。
验证监听状态:
ss -tlnp | grep 590正常应该看到Xvnc进程监听在*:5901或者0.0.0.0:5901。如果只看到127.0.0.1:5901,说明启动时带了-localhost参数,只有本机能连,这一点在后面配置 systemd 时会用到。
3. xstartup 是黑屏和灰屏的唯一嫌疑人
到这一步,如果你的vncserver启动没报错、端口也在监听,但客户端连上去是黑屏、灰屏、只有一个终端窗口,那么问题 100% 出在~/.vnc/xstartup。这个脚本是 VNC 会话启动时执行的入口,它决定了"桌面长什么样"。
3.1 xstartup 的执行时机和它到底在干什么
vncserver脚本在初始化一个显示号时,做的事情大致是:启动Xvnc进程 → 加载~/.vnc/passwd→ 执行~/.vnc/xstartup→ 把xstartup的输出重定向到~/.vnc/*.log。
所以xstartup本质上就是一个 shell 脚本,它运行在Xvnc提供的那个 X 会话里。脚本里启动的最后一个程序如果是长期运行的(比如窗口管理器或桌面会话),vncserver就会认为会话正常运行;如果脚本跑完就退出了,Xvnc会认为没有客户端,然后把会话关掉——这就是"闪退"现象的成因。
默认生成的xstartup内容非常简陋,大概只有xrdb和twm几行。twm是一个上古时期的窗口管理器,退化到连窗口边框都很丑,很多教程里看到的灰底加一个十字光标,就是它。你要做的就是把这几行换成你要用的桌面环境的启动命令。
3.2 一份能跑起 gnome-flashback 的 xstartup
下面这份是我在 20.04 上实测可用的版本,用的是gnome-flashback:
#!/bin/sh # 清理可能冲突的会话变量 unset SESSION_MANAGER unset DBUS_SESSION_BUS_ADDRESS # 明确告诉应用这是 X11 会话 export XDG_SESSION_TYPE=x11 # 加载系统级的 X 资源 [ -x /etc/vnc/xstartup ] && exec /etc/vnc/xstartup [ -r "$HOME/.Xresources" ] && xrdb "$HOME/.Xresources" # 启动 VNC 的配置托盘工具 vncconfig -iconic & # 启动 GNOME Flashback 会话 dbus-launch --exit-with-session gnome-session --session=gnome-flashback-metacity逐行解释几个关键点。unset SESSION_MANAGER和unset DBUS_SESSION_BUS_ADDRESS是为了避免脚本继承了物理会话的环境变量,导致新会话以为自己已经存在,从而拒绝启动。XDG_SESSION_TYPE=x11是给那些检查会话类型的应用一个明确的答案,不设的话某些 GTK 应用会走 Wayland 分支然后连不上显示。
dbus-launch --exit-with-session是必须的。GNOME 会话严重依赖 D-Bus,缺了它会出现"能启动但没有顶栏、设置打不开、终端能开但一关就崩"这类诡异症状。--exit-with-session表示当会话退出时自动清理 D-Bus 守护进程,防止残留。
如果你选的是 XFCE,最后一行换成:
exec startxfce4XFCE 不需要dbus-launch包装,直接用startxfce4就能起来,更简单。
改完记得给执行权限:
chmod +x ~/.vnc/xstartup3.3 黑屏、灰屏、闪退三类现象的排查链路
这三种现象成因完全不同,别混在一起调。我按"从现象推原因"的顺序列一下我实际排查过的路径。
纯黑屏,鼠标能动但什么都没有:说明Xvnc起来了,xstartup也执行了,但桌面会话没接管屏幕。先看日志~/.vnc/*.log,如果看到gnome-session: command not found,说明装的包不对;如果看到Failed to connect to session manager,基本就是上面说的 D-Bus 问题。另外检查一下是不是漏了XDG_SESSION_TYPE。
灰底加网格,或者只有一个终端窗口:这是xstartup根本没被替换,还在跑默认的twm。直接cat ~/.vnc/xstartup确认内容,很多情况下是你改了文件但忘了加执行权限,或者改的是另一个显示号的配置。
连上两秒然后断开:xstartup里的程序启动失败后立刻退出,Xvnc检测到没有客户端就关掉了会话。打开日志看最后几行报错,常见的是窗口管理器的命令名写错了,比如gnome-flashback的会话名在不同版本里不一样。可以用ls /usr/share/gnome-session/sessions/看有哪些可用的会话文件,把对应的名字填进去。
提示:每次改完
xstartup都要先杀掉旧会话再重启,否则改的东西不会生效。命令是vncserver -kill :1然后重新vncserver :1。
4. 用 systemd 托管 VNC 会话:开机自启与日志定位
手动vncserver :1的方式适合调试,但绝对不适合长期使用。机器一重启会话就没了,而且一旦终端断开,你也不确定进程还在不在。正确做法是交给 systemd 管理。
4.1 为什么不用 rc.local 或者 crontab 启动
我见过不少教程教你在rc.local里加一行vncserver :1,或者用crontab的@reboot。这两种方式在 20.04 上都不推荐,原因有两个。
一是执行时机不对。rc.local在内核启动早期运行,那时候用户的家目录可能还没挂载好,网络也可能还没起来,vncserver会因为找不到.vnc目录或者无法解析主机名而失败。crontab虽然晚一点,但环境变量是精简过的,PATH里没有/usr/bin之外的路径,dbus-launch这类命令经常找不到。
二是管理不了生命周期。会话崩了不会自动重拉,想看日志得自己去找.vnc/*.log,还容易被日志轮转删掉。systemd 这几件事全都解决了,而且开机自启只需要一条enable。
4.2 一份可直接使用的 systemd 单元文件
在/etc/systemd/system/下创建vncserver@.service,注意文件名里的@是模板占位符:
[Unit] Description=TigerVNC Remote Desktop Service for display %i After=network.target syslog.target [Service] Type=forking User=%i Group=%i WorkingDirectory=/home/%i ExecStartPre=/bin/sh -c '/usr/bin/vncserver -kill :1 > /dev/null 2>&1 || :' ExecStart=/usr/bin/vncserver :1 -geometry 1920x1080 -depth 24 -localhost no ExecStop=/usr/bin/vncserver -kill :1 Restart=on-failure RestartSec=5 PIDFile=/home/%i/.vnc/%H:1.pid [Install] WantedBy=multi-user.target几个参数值得说明。%i会被实例名替换,所以启动时写systemctl start vncserver@youruser,User和Group就自动变成youruser,家目录也是对应的,一个模板文件能服务多个用户。
ExecStartPre里那段|| :的写法是为了让命令即使失败也返回成功,因为第一次启动时本来就没有会话可以 kill,不这样写 systemd 会直接判定启动失败。
-localhost no表示允许非本机地址连接。这个参数在 TigerVNC 1.10 上支持,如果你用的是更老的版本报参数错误,把这一段去掉即可,默认是监听所有地址。但从安全角度讲,更推荐保留-localhost默认行为,然后用 SSH 端口转发来访问,这个下一章会讲。
Type=forking是因为vncserver脚本会 fork 出Xvnc进程后自己退出。配合PIDFile指向.vnc/%H:1.pid,systemd 才能正确跟踪到真正的服务进程。
4.3 启用服务并用 journalctl 定位启动失败
创建完文件后重新加载配置:
sudo systemctl daemon-reload sudo systemctl enable vncserver@$(whoami) sudo systemctl start vncserver@$(whoami)检查状态:
sudo systemctl status vncserver@$(whoami)如果显示active (running),说明服务已经托管成功。这时候可以做一次完整的验证:sudo reboot,重启后再看状态,仍然是 running 且端口在监听,就说明开机自启配置没问题了。
如果状态是failed,先看 systemd 的日志:
sudo journalctl -u vncserver@$(whoami) -n 50 --no-pager这份日志只包含vncserver脚本层面的输出,比如找不到命令、参数错误、权限不足。如果 journalctl 里看不到具体的桌面启动错误,那就要去看 TigerVNC 自己写的日志:
cat ~/.vnc/*:1.log这里才是xstartup里各个程序的真实输出。我遇到过的一个典型情况是:systemd 启动时$HOME环境变量没被正确传递,导致dbus-launch找不到用户会话总线,日志里一堆Failed to open connection to session bus。解决办法是在 Service 段落里显式加一行Environment=HOME=/home/%i。
5. 连接侧:客户端、端口转发与分辨率调整
服务端跑通了,接下来是连接侧。这一层的问题通常表现得比较粗暴——直接连不上,或者连上了但画质和延迟让人没法用。
5.1 客户端软件选择与分层排查思路
常见的客户端有 RealVNC Viewer、TigerVNC Viewer、Remmina 等。我个人的偏好是 TigerVNC Viewer,因为跟服务端同源,兼容性最省心;如果是在 Windows 或 Mac 上办公,RealVNC Viewer 的安装和界面体验更好,免费版功能足够。
连不上时别急着改服务端配置,按这个顺序排查效率最高。
先确认服务端进程和端口:
sudo systemctl status vncserver@$(whoami) ss -tlnp | grep 590再确认防火墙。Ubuntu 默认的ufw如果开着,需要放行:
sudo ufw status sudo ufw allow 5901/tcp然后是网络可达性。从客户端机器上ping一下服务端 IP,再telnet ip 5901或者用nc -vz ip 5901测端口。端口不通但 ping 得通,问题基本在防火墙或者服务端监听地址上。
最后才考虑客户端配置。一个很容易忽略的坑是颜色深度不匹配——服务端用-depth 24启动,客户端却选了 8-bit 模式,连上后画面会花得没法看。
5.2 通过 SSH 端口转发访问 VNC 的配置方法
前面提到 VNC 的密码保护只有 8 位有效字符,而且 RFB 协议早期版本的认证握手在很多场景下是明文传输的。如果这台机器不是完全隔离的内网环境,强烈建议把 VNC 的监听限制在本地,通过 SSH 端口转发来访问。
服务端启动参数改成默认的-localhost行为,也就是只监听127.0.0.1:5901。然后在客户端执行:
ssh -L 5901:127.0.0.1:5901 -N -f youruser@server-ip这条命令的含义是:在本地开一个 5901 端口,把所有流量通过 SSH 加密转发到服务端的127.0.0.1:5901。-N表示不执行远程命令,-f表示后台运行。
然后客户端 VNC Viewer 连接localhost:5901就行。这样做的额外好处是 SSH 的认证机制本身就足够强,VNC 那 8 位密码就变成了第二道无关紧要的门,即使被猜到,没有 SSH 密钥也进不来。
如果服务端和客户端之间有跳板机,还可以叠加-J参数做多级跳转,原理是一样的。
5.3 分辨率、剪贴板和多会话残留的处理
分辨率不是随便填的。你填的-geometry决定了虚拟屏幕的大小,但如果这个尺寸超过客户端窗口显示区域,你会看到滚动条;填得太小又会很憋屈。1920x1080 是一个比较通用的起点,笔记本屏幕建议用1600x900或1440x900。
TigerVNC 支持动态调整分辨率,客户端菜单里有Resize remote session之类的选项,但前提是xstartup里启动了支持 RandR 的窗口管理器,gnome-flashback和 XFCE 都支持。
剪贴板共享需要在服务端跑vncconfig -iconic &,也就是前面xstartup里的那一行。如果发现复制粘贴不工作,先确认这个进程在不在:ps aux | grep vncconfig。缺了就补上。
多会话残留是个容易被忽略的问题。反复vncserver :1而不 kill 掉旧的,会生成:2、:3,每个都占内存和端口。查当前运行的会话:
vncserver -list清理全部:
vncserver -kill :*但注意,重启机器后.vnc目录里的锁文件和 PID 文件可能还在,如果这时用同样的显示号启动会报A VNC server is already running as :1。删掉残留的锁文件即可:
rm -f /tmp/.X1-lock /tmp/.X11-unix/X16. 一张故障对照表和我实际踩过的几个细节
把前面散落的排查点整理成表,出问题的时候可以对照着看,比从头翻文档快得多。
6.1 常见报错与对应处理
| 现象 | 大概率原因 | 处理方式 |
|---|---|---|
Unable to locate package vnc4server | 包在 20.04 源中已移除 | 改用tigervnc-standalone-server |
| 客户端连上即断开 | xstartup程序启动失败后退出 | 查~/.vnc/*.log最后几行报错 |
| 纯黑屏无任何界面 | 桌面会话未接管,常见于 D-Bus 缺失 | xstartup中补dbus-launch --exit-with-session |
| 灰底加十字光标 | 仍在跑默认的twm | 替换xstartup内容并加执行权限 |
A VNC server is already running as :1 | 锁文件残留 | 删除/tmp/.X1-lock和/tmp/.X11-unix/X1 |
| 键盘字符错乱 | 旧版 VNC 的 XKB 支持不完整 | 切换到 TigerVNC 1.10 及以上 |
| 服务重启后会话消失 | 未配置 systemd 自启 | 创建vncserver@.service并enable |
| 客户端能连但画面卡顿 | 颜色深度或压缩级别设置不当 | 服务端用-depth 24,客户端选Tight或ZRLE编码 |
6.2 部署过程中值得记住的几条经验
第一条,改配置前先备份~/.vnc/xstartup。这个文件不大,但一旦被改乱,重新推导出正确内容要花不少时间。养成习惯:cp ~/.vnc/xstartup ~/.vnc/xstartup.bak再动手。
第二条,日志一定要看,别猜。我早期排查黑屏问题时,来回改了六七次xstartup,最后发现日志里早就写了gnome-session: not found——装gnome-flashback的时候漏了一个依赖包。~/.vnc/*.log和journalctl加起来一共两处日志,覆盖了从服务启动到桌面初始化的全部环节,养成"先看日志再动手"的习惯能省掉大量无效尝试。
第三条,虚拟机的显卡配置会影响体验。如果 Ubuntu 跑在 VMware 或 VirtualBox 里,3D 加速开关状态会影响某些桌面环境的渲染。用gnome-flashback这类不依赖硬件加速的桌面可以规避这个问题,这是我在虚拟机里反复验证过的结论。
第四条,不要在同一个显示号上叠加多个配置。有些人会在/etc/vnc/xstartup和~/.vnc/xstartup里都写启动命令,结果两个脚本都执行了一遍,桌面试图启动两次然后互相冲突。记住xstartup里的[ -x /etc/vnc/xstartup ] && exec /etc/vnc/xstartup这行,如果系统级脚本存在,它会用exec替换掉当前进程,你写在后面的内容根本不会执行。我在一台机器上因为这个卡了很久,最后发现是系统级文件在起作用。
第五条,如果这台机器只是偶尔远程用一下,其实可以考虑更省事的替代路径——比如直接用带 X11 转发能力的 SSH 客户端,通过ssh -X单独转发某个图形程序,不需要完整的远程桌面。VNC 的价值在于"完整桌面体验",如果你的需求只是跑一两个 GUI 工具,ssh -X的配置成本几乎为零。这个取舍值得在动手前想清楚。