noVNC 谷歌浏览器

一、目标方案

在无桌面的 Linux 服务器上,提供一个可长期运行、可网页接管、可自动化调试、可持久保存登录态的 Chrome 环境。

• Xvfb:虚拟显示器
• Chrome:真实浏览器进程
• 独立 Profile:保存 Cookie、登录态、缓存、插件数据
• x11vnc:把虚拟显示导出成 VNC
• noVNC:浏览器里直接访问 VNC
• 9222 DevTools:给 Playwright / Puppeteer / 手工调试接管

二、推荐架构

进程关系

• Xvfb :99
• google-chrome --display=:99
• x11vnc -display :99
• noVNC/websockify → 6080
• Chrome DevTools → 127.0.0.1:9222

目录规划

建议统一放这里:

/opt/headless-browser/
├── bin/
├── run/
├── logs/
├── profiles/
│ └── main/
└── conf/

说明

• profiles/main/:Chrome 独立用户数据
• logs/:各组件日志
• run/:pid、lock、socket 之类
• conf/:密码、端口、环境变量

三、端口规划

• 6080:noVNC Web 访问入口
• 5900 或 5999:VNC 内部端口
• 9222:Chrome 远程调试端口,仅本机监听

建议:

• noVNC 可以对外
• VNC 端口不要直接对公网
• 9222 只监听 127.0.0.1

四、安装方案

下面按 Debian/Ubuntu 系来写。

安装依赖

apt update
apt install -y xvfb x11vnc novnc websockify wget curl unzip procps

安装 Chrome

如果机器上还没装:

wget -O /tmp/google-chrome.deb https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
apt install -y /tmp/google-chrome.deb

如果依赖没补齐,可执行:

apt -f install -y

安装字体

apt install -y fonts-noto-cjk fonts-noto-core fonts-noto-color-emoji fonts-liberation fonts-dejavu-core
fc-cache -fv

五、目录初始化

mkdir -p /opt/headless-browser/{bin,run,logs,profiles/main,conf}

如果打算专门建个用户跑,也可以这样:

useradd -r -m -d /opt/headless-browser -s /bin/bash browseruser
chown -R browseruser:browseruser /opt/headless-browser

如果先图省事,也可以先用 root 跑通,但生产不太建议。

六、核心启动脚本

我建议你别拆太碎,先一个总启动脚本,把整条链路跑通。

vim /opt/headless-browser/bin/start-browser-stack.sh

脚本内容

#!/usr/bin/env bash
set -euo pipefail

BASE_DIR="/opt/headless-browser"
RUN_DIR="$BASE_DIR/run"
LOG_DIR="$BASE_DIR/logs"
PROFILE_DIR="$BASE_DIR/profiles/main"

DISPLAY_NUM=":99"
SCREEN_RESOLUTION="1440x900x24"

VNC_PORT="5900"
NOVNC_PORT="6080"
DEBUG_PORT="9222"

CHROME_BIN="/usr/bin/google-chrome"

mkdir -p "$RUN_DIR" "$LOG_DIR" "$PROFILE_DIR"

cleanup_old() {
pkill -f "Xvfb $DISPLAY_NUM" || true
pkill -f "x11vnc.*$DISPLAY_NUM" || true
pkill -f "websockify.*$NOVNC_PORT" || true
pkill -f "$CHROME_BIN.*--user-data-dir=$PROFILE_DIR" || true
rm -f /tmp/.X99-lock || true
}

start_xvfb() {
nohup Xvfb "$DISPLAY_NUM" -screen 0 "$SCREEN_RESOLUTION" -ac +extension RANDR \
> "$LOG_DIR/xvfb.log" 2>&1 &
echo $! > "$RUN_DIR/xvfb.pid"
sleep 2
}

start_chrome() {
DISPLAY="$DISPLAY_NUM" nohup "$CHROME_BIN" \
--user-data-dir="$PROFILE_DIR" \
--remote-debugging-address=127.0.0.1 \
--remote-debugging-port="$DEBUG_PORT" \
--no-first-run \
--no-default-browser-check \
--disable-dev-shm-usage \
--window-size=1440,900 \
--start-maximized \
--password-store=basic \
--disable-features=Translate,AutomationControlled \
--no-sandbox \
> "$LOG_DIR/chrome.log" 2>&1 &
echo $! > "$RUN_DIR/chrome.pid"

sleep 3

if ! kill -0 "$(cat "$RUN_DIR/chrome.pid")" 2>/dev/null; then
echo "Chrome failed to start. Log:"
tail -n 50 "$LOG_DIR/chrome.log"
exit 1
fi
}


start_x11vnc() {
nohup x11vnc \
-display "$DISPLAY_NUM" \
-forever \
-shared \
-rfbport "$VNC_PORT" \
-rfbauth /opt/headless-browser/conf/vnc.pass \
-quiet \
> "$LOG_DIR/x11vnc.log" 2>&1 &
echo $! > "$RUN_DIR/x11vnc.pid"
sleep 2
}

start_novnc() {
nohup websockify \
--web=/usr/share/novnc/ \
"$NOVNC_PORT" \
127.0.0.1:"$VNC_PORT" \
> "$LOG_DIR/novnc.log" 2>&1 &
echo $! > "$RUN_DIR/novnc.pid"
sleep 2
}

show_status() {
echo "Browser stack started."
echo "noVNC: http://<your-server-ip>:$NOVNC_PORT/vnc.html"
echo "DevTools JSON: http://127.0.0.1:$DEBUG_PORT/json/version"
}

cleanup_old
start_xvfb
start_chrome
start_x11vnc
start_novnc
show_status

赋权

chmod +x /opt/headless-browser/bin/start-browser-stack.sh

七、停止脚本

vim /opt/headless-browser/bin/stop-browser-stack.sh

脚本内容

#!/usr/bin/env bash
set -u

BASE_DIR="/opt/h	eadless-browser"
RUN_DIR="$BASE_DIR/run"

stop_by_pidfile() {
local name="$1"
local pidfile="$2"

if [ -f "$pidfile" ]; then
local pid
pid="$(cat "$pidfile" 2>/dev/null)"

if [ -n "${pid:-}" ] && kill -0 "$pid" 2>/dev/null; then
echo "Stopping $name (pid=$pid) ..."
kill "$pid" 2>/dev/null || true

for _ in 1 2 3 4 5; do
if kill -0 "$pid" 2>/dev/null; then
sleep 1
else
break
fi
done

if kill -0 "$pid" 2>/dev/null; then
echo "$name did not exit gracefully, killing forcefully ..."
kill -9 "$pid" 2>/dev/null || true
fi

echo "$name stopped."
else
echo "$name not running, removing stale pid file."
fi

rm -f "$pidfile"
else
echo "$name pid file not found, skipping."
fi
}

echo "Stopping browser stack ..."

stop_by_pidfile "noVNC/websockify" "$RUN_DIR/novnc.pid"
stop_by_pidfile "x11vnc" "$RUN_DIR/x11vnc.pid"
stop_by_pidfile "Chrome" "$RUN_DIR/chrome.pid"
stop_by_pidfile "Xvfb" "$RUN_DIR/xvfb.pid"

rm -f /tmp/.X99-lock 2>/dev/null || true

echo "Browser stack stopped."


八、x11vnc 密码

生成 VNC 密码文件,它会让你输入一次密码,这个就是以后 noVNC 连接时要填的密码。

mkdir -p /opt/headless-browser/conf
x11vnc -storepasswd /opt/headless-browser/conf/vnc.pass
chmod 600 /opt/headless-browser/conf/vnc.pass

想改密码

直接重新生成覆盖:

x11vnc -storepasswd /opt/headless-browser/conf/vnc.pass
chmod 600 /opt/headless-browser/conf/vnc.pass
systemctl restart headless-browser

九、systemd 服务化

vim /etc/systemd/system/headless-browser.service

内容

[Unit]
Description=Headless Chrome + Xvfb + x11vnc + noVNC stack
After=network.target

[Service]
Type=forking
User=root
WorkingDirectory=/opt/headless-browser
ExecStart=/opt/headless-browser/bin/start-browser-stack.sh
ExecStop=/opt/headless-browser/bin/stop-browser-stack.sh
RemainAfterExit=yes
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target

开启自启并启动

systemctl daemon-reload
systemctl enable headless-browser
systemctl start headless-browser
systemctl status headless-browser

十、验证方式

验证 noVNC

浏览器打开,并输入密码

http://你的服务器IP:6080/vnc.html

验证 9222

在服务器本机执行:

curl http://127.0.0.1:9222/json/version

能返回 JSON 就说明 DevTools 正常。