最小化安装的Linux服务器上装Chrome,最典型的场面是这样的:wget下来一个几十兆的deb包,dpkg -i一把梭,屏幕上立刻刷出一屏"依赖关系问题使得 google-chrome-stable 的配置工作不能继续",然后卡在那里,浏览器打不开,命令敲一半不知道该往下走哪步。我最早接手的一台内网机器就是这样,装完Chrome之后花的时间远比装Chrome本身多得多——不是装不上,是缺的那几个共享库得一个个去对应包名,而很多包名跟报错里写的库名压根不是一个词。这篇内容就是把Linux安装Chrome、以及依赖解决这条路完整走一遍:从包的分发形态、联网装法、报错怎么读、离线机器怎么搬,到装完之后才会暴露的沙箱、字体、中文乱码问题,最后是版本锁定和系统安全策略的相处方式。适合正在折腾Linux桌面、CI流水线、内网办公机或者爬虫环境的同学,Ubuntu/Debian和CentOS/RHEL/openEuler两条线都会覆盖,命令都可以直接抄。
1. Chrome在Linux上到底以什么形态存在
先把一个容易搞混的前提说清楚:Linux版的Chrome不是开源软件,官方只发两种二进制安装包,Debian/Ubuntu系的.deb和 RHEL/CentOS/Fedora系的.rpm,没有官方维护的 tar.gz 绿色包。这一点和Chromium完全不同,Chromium在很多发行版里是以源码包或第三方tar包形式分发的。所以你在网上看到"下载Chrome离线包"时,拿到的必然是这两种之一,如果拿到别的格式,基本是别人二次打包的,来源要打个问号。
装完之后Chrome的文件布局也很固定:所有程序文件都在/opt/google/chrome/下面,真正的可执行文件是/opt/google/chrome/chrome;/usr/bin/google-chrome其实是一个shell包装脚本,它负责设置一些环境变量再调用真正的二进制,很多人以为它是二进制,file /usr/bin/google-chrome一看就知道。桌面入口在/usr/share/applications/google-chrome.desktop,另外还会塞一个/etc/cron.daily/google-chrome的定时任务,这个任务就是自动更新的来源,后面讲到版本锁定会拿它开刀。理解这个布局很重要,因为依赖缺失、沙箱报错、绿色解包这三件事全部围绕这几个路径展开。
1.1 deb与rpm里到底塞了哪些东西
拿到包之后先别急着装,花十秒看一眼内容能省掉后面很多猜测。deb包用dpkg-deb -c google-chrome-stable_current_amd64.deb,rpm包用rpm -qlp google-chrome-stable_current_x86_64.rpm,列出来的清单几乎一模一样。清单里比较关键的是两点,一是/opt/google/chrome/chrome-sandbox,这个文件带SUID属性,是沙箱机制的核心,也是后面权限报错的根源;二是google-chrome.desktop里的启动项。至于.mo语言包、图标、resources.pak这些都是资源文件,不影响依赖,缺了也只是界面变英文或者图标缺失。
dpkg-deb还有一个-I参数可以看控制信息,重点是Depends那一行,它会明明白白列出这个包依赖什么。第一次看会觉得奇怪,明明依赖列表没那么长,为什么装的时候报出一堆库缺失?原因是Chrome的Depends里写的通常是一组"关键依赖",而真正的运行还需要一串libnss3、libgbm、libasound2这类库,它们有的是被间接依赖拉进来的,有的则是打包时声明得比较宽松。所以"报错清单比Depends长"是正常现象,不用怀疑自己下错包了。
1.2 正式版、beta、dev三条通道怎么选
Chrome在Linux上有三个更新通道:stable、beta、unstable(dev),包名分别是google-chrome-stable、google-chrome-beta、google-chrome-unstable。三条通道可以共存,装不同通道的包互不冲突,因为它们用的是不同的目录和启动器名字。对绝大多数场景,选stable就对了;需要提前验证某个新版特性对内部系统的兼容性,才考虑beta。
这里要专门提一下版本号含义,比如120.0.6099.109,第一段是大版本,最后一段小号是补丁版本。企业环境里锁版本时,锁的是这一整串,而不是只锁大版本。另外热词里反复出现"Chrome 109",这里有个容易误判的点:109是最后一个支持某些旧版桌面系统的版本,这个限制只针对Windows侧,Linux侧并没有对应的硬性门槛。但反过来,Chrome新版本对glibc有下限要求,而CentOS 7这类老系统的glibc版本偏旧,所以有人会特意去找某个较老版本的Linux包来用——这不是因为109本身特殊,而是因为老系统扛不住新包的glibc要求。判断方法很简单,ldd --version看本机glibc,再对比官方文档给出的最低要求,别凭感觉猜。
1.3 什么时候该用Chromium顶一下
有一个判断标准很实用:如果你的目标只是"在Linux上跑一个能渲染网页的内核",而不是非要Chrome的品牌功能,那优先考虑发行版仓库里的Chromium。apt install chromium或者dnf install chromium,依赖由包管理器全权负责,不存在手动补库的问题,升级也走系统统一通道。差异主要在两块:一是部分发行版的Chromium不打包某些专利编解码器,视频播放可能受限;二是Chrome特有的一些组件在Chromium里没有。CI、无头截图、自动化测试这类场景,Chromium通常完全够用。
不过这里有个坑必须提前说:Ubuntu从某个版本开始,仓库里的chromium-browser变成了一个snap过渡包,实际是通过snap安装的。在服务器上跑snap经常遇到/dev/shm不足、systemd未启动导致snapd不可用之类的问题,装完根本起不来。遇到这种情况,要么换发行版,要么改用Chrome的deb包,要么用第三方的Chromium构建。我在容器里踩过一次,apt说装好了,chromium --version直接报snap相关错误,排查半天才反应过来是snap封装。
2. 联网机器上最不折腾的安装方式
机器能正常访问软件源时,装Chrome是件很轻松的事,关键在于别用错方法。我看到过太多人一上来就dpkg -i,装完报错再去apt-get install -f救,虽然最后也能救回来,但中间多绕了一圈,而且在依赖特别复杂的环境里,-f修复有可能连带着升级或降级一批系统库,反而不如一开始就让apt接管。
正确姿势有三个层次:能加官方源就加源,让包管理器知道"Chrome从哪来";临时装一个包就用apt install ./xxx.deb这种本地路径写法,让apt自己去解析依赖;只有在完全不能联网的机器上,才回到手动dpkg -i加手补依赖的老路。下面按顺序说。
2.1 走官方仓库:加源与导入密钥的完整流程
Debian/Ubuntu上的标准流程是:先确认wget、gnupg、apt-transport-https这几个基础工具在,然后下载签名公钥并转换成keyring格式。老教程里流行apt-key add,但新版Debian和Ubuntu已经把apt-key标记为废弃,虽然还能用,但每次执行都会打印警告,长期维护的机器建议直接用新写法:把公钥下载下来,用gpg --dearmor转成二进制格式,放到/usr/share/keyrings/google-chrome.gpg。
接下来写源文件/etc/apt/sources.list.d/google-chrome.list,内容是一行deb定义,指定架构、签名文件和仓库地址。这里有个细节值得注意:架构最好显式写出来,写成[arch=amd64],在多架构环境下能避免apt去尝试拉取不存在的i386包,报一堆404。写完源之后apt update,再apt install google-chrome-stable即可。整个过程中如果apt update报签名验证失败,八成是密钥没导入成功或者路径写错,用apt-key list或者直接看/usr/share/keyrings/下的文件是否存在来确认。
提示:源文件里的通道字段(stable/beta)和包名里的通道后缀要对应,源写stable却去装beta包会找不到,这个错误信息很不直观,容易让人以为源挂了。
2.2 只拿一个deb包,让apt来补依赖
很多内网机器的软件源被改造过,加第三方源不一定被允许,这时候就用本地包。apt从1.1版本开始支持直接传文件路径,apt install ./google-chrome-stable_current_amd64.deb,注意前面的./不能省,省了会被当作包名去源里找。它做的事和在线安装几乎一样:读取deb的依赖信息,从已配置的源里把缺的依赖下下来,再一起装。
相比dpkg -i加apt-get install -f的两步走,这个写法有个明显好处:依赖解析是一次性的,apt能给出完整的方案,而且失败时会明确告诉你是哪个依赖装不上,而不是先把包拆开一半再回滚。如果你手上只有rpm,Fedora和较新的RHEL系可以直接dnf install ./xxx.rpm,dnf对本地包的处理比老yum友好得多,缺什么直接列出来并尝试从已启用仓库补。
2.3 RHEL系下的rpm路线与EPEL依赖
CentOS 7、Rocky、openEuler这些系统走的是rpm路线,yum localinstall ./google-chrome-stable_current_x86_64.rpm或者较新系统上的dnf install。这条路最常卡在libappindicator上:Chrome的rpm包声明依赖它,但RHEL基础仓库里没有,需要启用EPEL。这是个典型的"包本身没问题、源不全"的坑,报错会说依赖无法满足,看起来像包坏了,其实是仓库没开。
具体来说,系统托盘相关功能依赖libappindicator-gtk3,在EPEL里;libXScrnSaver提供libXss.so.1,这个在基础仓库里通常有,但包名和库名差得远,需要查。下面这张对照表是我自己攒的,左边是报错里出现的库文件名,右边是两个体系里对应的包名,遇到缺库时直接对着查,比一个个搜快得多。
| 报错中的库名 | Debian/Ubuntu 包名 | RHEL/CentOS 包名 |
|---|---|---|
| libnss3.so | libnss3 | nss |
| libgbm.so.1 | libgbm1 | mesa-libgbm |
| libasound.so.2 | libasound2 | alsa-lib |
| libatk-bridge-2.0.so.0 | libatk-bridge2.0-0 | atk |
| libgtk-3.so.0 | libgtk-3-0 | gtk3 |
| libXss.so.1 | libxss1 | libXScrnSaver |
| libdrm.so.2 | libdrm2 | libdrm |
| libxkbcommon.so.0 | libxkbcommon0 | libxkbcommon |
| libpango-1.0.so.0 | libpango-1.0-0 | pango |
| libcups.so.2 | libcups2 | cups-libs |
| libxshmfence.so.1 | libxshmfence1 | libxshmfence |
| libappindicator3.so.1 | libappindicator3-1 | libappindicator-gtk3(EPEL) |
| libfido2.so.1 | libfido2-1 | libfido2 |
表里的包名会随发行版版本有小幅变化,比如某些新的Ubuntu上libasound2改成了libasound2t64,这是64位时间类型迁移带来的改名,遇到时按报错提示的名字去搜即可。记住一个原则:报错说的是库文件名,apt install要的是包名,两者不能直接互换,这也是新手最容易卡住的地方。
3. 依赖报错文本的逐行读法
很多人看到依赖报错的第一反应是"看不懂",其实dpkg输出的文本结构非常规整,按行读下来信息量很大。真正需要的是耐心和一点翻译能力,把"库名"翻译成"包名",把"没有安装"和"版本不匹配"区分开,处理方式完全不同。
我遇到过最典型的一次是内网Ubuntu 20.04上装Chrome,dpkg -i之后报了五行依赖,其中三行是"没有安装",两行是"但是 1.2.3 即将被安装"。前者的处理方式是直接装,后者则意味着系统里有版本冲突,需要看清楚谁依赖谁,盲目升级可能把别的东西弄坏。这个区别如果不区分,就会陷入"装了还是报错"的循环。
3.1 从报错到修复的完整排查链路
先说dpkg -i报错的标准处理链路,按顺序走,不要跳步。第一步看报错里到底是哪几个包,把它们抄下来;第二步先跑apt update刷新索引,很多时候源索引过期会导致"明明源里有却说找不到";第三步执行apt-get install -f,让apt尝试自动修复并补全依赖,这一步能解决八成情况;第四步如果-f也失败,仔细看它给出的原因,是网络不通、源里确实没有,还是存在版本冲突。
到第四步还失败的话,就该换工具了。aptitude install google-chrome-stable是一个被低估的选择,它在遇到冲突时会给出多套解决方案让你选,比如"降级A并保留B"或者"卸载C来满足依赖",比apt的硬性拒绝友好得多。用的时候注意看它准备执行的动作列表,确认没有把重要的系统库降级再按Y。这一步特别适合那种"依赖能装但apt算不出解"的场景,我在一台老机器上就靠它解决了libnss3版本冲突的问题。
3.2 apt源本身有问题时的判断方法
还有一个方向容易被忽略:问题不在Chrome,而在源。判断方法很直接,随便找一个基础包试装,比如apt install curl,如果连这个都装不上,说明源的配置、网络或者密钥有问题,跟Chrome没关系。这时候该查的是/etc/apt/sources.list里的发行版代号是否和系统版本对得上,用lsb_release -a看代号。我见过有人把Ubuntu 22.04的源写成了20.04的代号,apt update表面成功,装包时各种找不到,排查了很久。
架构问题同样隐蔽。dpkg --print-architecture看本机架构,如果是arm64,就得找对应的Chrome包——官方对arm64的支持情况和amd64不一样,有些版本没有arm64的deb,这时候只能考虑Chromium。另外检查有没有被hold住的包,apt-mark showhold,被hold的包会阻止依赖解析,表现就是"No candidate"或者"held broken packages",这个提示很容易被当成源缺失。
3.3 手工补库的兜底方案
真到了源不可用又必须装的地步,兜底方案是手动下deb再装。知道自己缺哪个包名之后,从任意能访问的镜像站找到对应的deb,wget下来,dpkg -i libnss3_xxx_amd64.deb。这里有个顺序建议:先把依赖装完,最后装Chrome本体,避免中间状态被反复打扰。如果某个依赖本身还有依赖,就一层层往里剥,链条一般不深,两三层的居多。
还有一个比较野但有用的办法,用dpkg-deb -x把依赖包直接解出内容,把里面的.so文件放到一个自定义目录,靠LD_LIBRARY_PATH指过去。这个办法适合系统库版本敏感、不想动系统环境的场景,比如CI容器里只想跑一次截图,不想因为装依赖改变基础镜像。缺点是动态链接器的搜索路径变复杂后,某些程序可能出现意外的库版本混用,所以只建议在隔离环境里用,不要在生产机上这么干。
4. 完全离线环境下的搬迁方案
内网机器、隔离环境、没配外网出口的构建节点,这些场景下"装Chrome"实际上等于"把包和它的依赖一起搬过去"。这件事听起来麻烦,做一次之后会发现有比较标准的套路,核心是别在离线机器上试错,所有准备工作都在联网机器上完成,而且最好让两台机器的系统版本、架构完全一致。
版本一致这一点特别重要。我曾经用Ubuntu 22.04的联网机去给一台20.04的内网机准备依赖,库版本对不上,装上去Chrome能启动但渲染时崩溃,排查成本极高。经验是联网机和离线机用同一个镜像装系统,uname -r和lsb_release -a的输出对得上,包基本能通用。
4.1 在联网机上把依赖指纹抓全
抓依赖有好几个层次的方法。最省事的是apt-get install -d google-chrome-stable,-d表示只下载不安装,下载好的deb会落在/var/cache/apt/archives/里,把这个目录整个拷走就是完整的包集合。这个方法的好处是apt已经帮你算好了依赖闭包,不用自己去分析。
如果离线机的系统版本和联网机完全一致,上面这招就够用了。如果只想知道"到底需要哪些包",用apt-get install --print-uris可以只打印下载地址而不真的下载,输出里能看到完整的包列表,方便做成清单一处处核对。清点出来的包通常几十个,因为Chrome依赖gkt3,gkt3又依赖一大串图形库,闭包展开比较多,纯手工一个个找出处非常费劲,能用工具就别硬来。
4.2 dpkg-deb解包成绿色版直接跑
另一个思路是跳过包管理器,用dpkg-deb -x把Chrome解包成一个目录,程序直接从目录里运行。命令很简单,建个目录,dpkg-deb -x 包名.deb 目标目录,解出来的结构是opt/google/chrome/,直接执行里面的chrome二进制加--version就能看到版本号,说明解包本身没问题。
这个方法的优势是零侵入,不乱动系统目录,删掉目录就是卸载。劣势是依赖依然要在系统里存在——解包只解决"Chrome的程序文件放哪",不解决"Chrome要链接的.so在哪"。所以它适合配合前面提到的LD_LIBRARY_PATH方案,把一批自己带的库指向解包目录,做成一个真正的绿色版。做绿色版时还要注意用户数据目录,--user-data-dir=/tmp/chrome-profile显式指定,否则多个实例会抢同一个profile锁,报"profile appears to be in use"。
4.3 搭一个最小的本地仓库
如果内网机器不是一台而是一批,搭本地仓库的投入很快就能回本。做法是用dpkg-scanpackages扫描存放deb的目录,生成Packages.gz,然后在离线机的sources.list里写一行deb [trusted=yes] file:///path/to/repo ./,apt update之后就能像在线一样apt install google-chrome-stable,依赖自动解析。trusted=yes是因为本地仓库没签名,内网环境可以接受,公网环境不要这么干。
rpm系对应的工具是createrepo_c,扫描目录生成repodata,然后写一个.repo文件指向file://路径或者内网HTTP地址。搭好之后,装Chrome这件事就从"手工补库"变成了"一条install命令",新人上手也不用教,非常值。仓库目录建议按发行版版本分开放,比如repo/ubuntu2204、repo/rocky9,避免版本串味。
5. 装完之后才暴露的那些问题
Chrome装上了不代表能跑起来,从"安装完成"到"能正常打开网页"之间还有几道坎,而且这几道坎的报错信息往往跟安装完全无关,第一次遇到会以为是装坏了。按出现频率排,分别是沙箱权限、字体缺失导致的中文显示问题、以及无头或远程环境下的启动参数配置。
这三类问题的共同点是:错误信息不指向"依赖",而指向"运行时环境",所以要分开处理。我也见过有人明明装成功了,因为沙箱报错又去重装一遍,白折腾。先把运行时的报错信息看准,能省很多重复劳动。
5.1 root身份下的沙箱报错与权限修复
以root身份直接运行Chrome,大概率会看到"Running as root without --no-sandbox is not supported",或者"SUID sandbox helper binary was found, but is not configured correctly"。原因是Chrome的沙箱机制要求chrome-sandbox具备SUID权限并且属主是root。修复命令就两条:chown root:root /opt/google/chrome/chrome-sandbox和chmod 4755 /opt/google/chrome/chrome-sandbox,改完再跑通常就正常了。
这里说明一下为什么加了--no-sandbox也能跑通,但不推荐作为默认方案。沙箱是Chrome的安全边界,关掉之后渲染进程直接以当前用户权限运行,如果浏览器加载了恶意页面,风险敞口明显变大。在一次性容器、隔离的CI任务里,用--no-sandbox换取启动顺畅是可以接受的;但在给真人使用的办公机上,优先修权限,别关沙箱。容器环境还有个专属坑:/dev/shm默认只有64MB,Chrome跑着跑着会崩,加--disable-dev-shm-usage让它改用临时目录,一般就稳了。
5.2 中文方块、界面乱码的定位顺序
界面中文全变方块,是最常见的"装完能用但看着难受"问题,根因是系统里没有中文字体。验证方式:fc-list :lang=zh,如果输出为空或者只有一两条,那就是字体确实缺。安装也很直接,Ubuntu上apt install fonts-noto-cjk fonts-wqy-zenhei,CentOS系装上 wqy 系列的包,装完不用重启,重新打开Chrome即可。CI里做网页截图出现方块字,同一个原因,装字体就行。
还有一种长得像乱码但不是字体问题的现象:终端里中文显示成问号或奇怪字符。这是locale没配好,跟Chrome无关,locale看一下输出里有没有zh_CN.UTF-8,没有的话用localedef或改/etc/default/locale补上。这两种情况要分清楚,否则会出现"装了一堆字体还是乱码"的无效操作。
5.3 无头与远程环境下的启动参数清单
在服务器或者远程桌面上跑Chrome,参数配置决定了它能不能启动、能不能被外部控制。常用的就那么几个,我整理了张表,旁边标注了什么时候需要。
| 参数 | 作用 | 适用场景 |
|---|---|---|
| --headless=new | 不显示界面运行 | 截图、导出PDF、CI |
| --no-sandbox | 关闭沙箱 | 容器内、临时环境 |
| --disable-dev-shm-usage | 绕开小容量 /dev/shm | 容器、内存受限机器 |
| --disable-gpu | 关闭GPU加速 | 无显卡服务器 |
| --user-data-dir=路径 | 指定用户数据目录 | 多实例、root运行 |
| --remote-debugging-port=9222 | 开放调试端口 | 自动化控制、调试 |
| --window-size=1920,1080 | 指定窗口尺寸 | 截图分辨率控制 |
另外提醒一点,--remote-debugging-port监听的是本地端口,如果需要从别的机器访问,得配合端口转发或者绑定地址参数,同时注意这个端口权限很大,等同远程控制浏览器,只在内网可信环境开放,别暴露在对外可达的网卡上。
6. 版本锁定、自动更新与系统安全策略
能跑起来之后,还有一个长期问题:Chrome是自动更新的,或者说是被那个/etc/cron.daily/google-chrome定时任务驱动的,一升级,依赖需求可能跟着变,某些内网插件或者企业策略扩展可能就不兼容了。我在一台生产用的机器上就遇到过,某次自动更新后,一个内部系统依赖的旧接口行为变了,排查了半天才发现是浏览器版本变了。
处理思路是先决定"要不要让它自动更新"。如果这台机器的浏览器只是用来看网页,跟着自动更新反而更省心,安全补丁也能及时跟上;如果它承载了固定的业务系统、要跑自动化脚本,锁版本更稳妥。这两种方向的配置方式不同,下面分别说清楚。
6.1 关掉自动更新的几种做法与副作用
Debian系上最简单的做法是apt-mark hold google-chrome-stable,把包hold住,apt就不会再动它。注意这个hold只对apt生效,那个cron任务如果在,还会想办法更新,所以更彻底的做法是把/etc/cron.daily/google-chrome删掉或者去掉执行权限。删掉之后要记住,安全更新也不会自动来了,得自己定期手动升。
还有一种做法是把sources.list里的通道从stable改成别的,或者干脆注释掉源,只留已经装好的版本。这种做法在离线环境里很常见,机器本来就不联网,源写不写无所谓。但要提个醒:如果以后想升级,得先把源恢复回去,别到时候忘了自己改过什么。我习惯在源文件旁边加一行注释记录修改日期和原因,过半年回头看能救命。
6.2 固定版本包的下载与校验
需要装特定版本时,官方仓库里的deb文件名包含完整版本号,路径有规律可循,把版本号替换进去就能拿到指定版本。下载完之后用sha256sum算一遍,和页面上公布的校验值比对,一致再装。这一步在批量部署时尤其值得做,避免某台机器上拿到的是损坏文件,然后花时间排查"为什么这台装不上"。
批量场景下还有个更省事的做法:把校验过的包和依赖一起放进本地仓库,仓库里的包就是唯一可信来源,所有机器都从仓库装同一个版本,一致性由仓库保证,不用每台机器单独校验。这套东西搭一次,后面换版本只需替换仓库里的包并重新扫描,运维成本很低。
6.3 SELinux与AppArmor下的执行限制
RHEL系默认开启SELinux,Chrome装在/opt下,某些策略配置比较严的环境会拦下对chrome二进制的执行,表现是"装好了,双击没反应"。排查方式:getenforce看模式,ausearch -m avc -ts recent看有没有对应的拒绝记录,确认是SELinux的问题后,临时setenforce 0验证一下,能跑通就说明是策略拦截。正式处理要么给/opt/google/chrome打合适的上下文标签,要么加一条策略放行,别长期把SELinux设成permissive了事。
Ubuntu上对应的是AppArmor,配置文件通常在/etc/apparmor.d/下,Chrome的profile如果限制了某些路径访问,会导致扩展或者下载功能异常。遇到"部分功能莫名其妙不可用"时,dmesg里搜apparmor="DENIED"往往能找到线索。这类问题和依赖缺库的表现很像,都是功能不全,但排查方向完全不同,一看报错日志的格式就能区分开。
最后分享一个我踩过好几次才养成的习惯:在一台不熟悉的机器上装Chrome之前,先跑三行命令——lsb_release -a看系统版本,dpkg --print-architecture看架构,ldd --version看glibc版本。这三项决定了你该下哪个包、能不能用最新版本、以及要不要考虑Chromium替代。我最早是拿到包就装,装不上再回头查系统信息,来回折腾好几轮;后来养成了先看后下的习惯,同样的工作量能省掉一大半。至于依赖清单,别指望一次背下来,把上面那张库名对照表存成自己的笔记,遇到缺库往上套就是了。