Ubuntu 20.04 安装 Jekyll 常见编译失败原因与完整构建环境配置 1. 为什么 Ubuntu 20.04 上跑 Jekyll 不是“装完 gem 就能用”那么简单Jekyll 是静态网站生成器里最稳的那一类工具它不依赖数据库、不跑 PHP、不搞复杂服务端逻辑靠 Ruby 解析 Markdown 和 Liquid 模板吐出纯 HTML 文件。听起来很轻量没错。但“轻量”不等于“无依赖”——尤其当你把战场设在 Ubuntu 20.04 这个发行版上时事情就微妙了。我第一次在一台刚重装的 Ubuntu 20.04 服务器上执行gem install jekyll终端卡在Building native extensions...超过 7 分钟最后报错ERROR: Failed to build gem native extension.。不是 Ruby 版本太低20.04 自带 ruby 2.7.0够用也不是磁盘满了而是缺了一整套底层编译链make找不到gcc报 command not foundzlib.h头文件提示 no such file or directory。这些错误信息散落在终端末尾像一串被揉皱的纸条没人告诉你它们其实指向同一个根因系统缺少构建 Ruby C 扩展所需的开发工具链。这正是 Ubuntu 20.04 的设计哲学带来的“温柔陷阱”它默认只装运行时环境runtime不装构建环境build environment。你拿到的是一个能“跑程序”的系统而不是一个能“编译程序”的系统。而 Jekyll 的核心依赖之一 ——webrick2022 年后已从 Ruby 标准库移除需显式安装、kramdown、rouge甚至jekyll-sass-converter全都有 C 扩展模块。它们不是纯 Ruby 写的得用gcc编译成.so动态库再由 Ruby 加载。没有build-essential你就永远卡在gem install的第一步。更隐蔽的问题藏在 Ruby 生态里。Ubuntu 20.04 自带的 Ruby 是通过apt安装的二进制包它被硬编码了--disable-shared编译参数导致后续安装某些 gem比如nokogiri时会反复提示libxml2和libxslt开发头文件缺失即使你apt install libxml2-dev libxslt-dev也解决不了——因为 Ruby 本身没启用动态链接支持。这不是你操作错了是系统级预设和 Ruby 社区主流实践之间存在一道静默的鸿沟。所以搭建 Jekyll 开发环境本质是一次对 Ubuntu 底层构建能力的“压力测试”。它逼你直面三个层次的问题系统层确认make、gcc、g、libc6-dev是否就位语言层Ruby 是否支持动态扩展加载是否与系统 OpenSSL 兼容生态层Jekyll 依赖树中哪些 gem 强制需要本地编译哪些可以跳过或换替代方案。这不是一个“复制粘贴命令就能好”的流程而是一次对 Linux 开发环境认知的校准。接下来我会带你一层层拆解每一步都告诉你“为什么非得这样”而不是只给你一行sudo apt install。2. 构建环境初始化从build-essential到ruby-dev的完整补全链很多人看到网上教程写“先装build-essential”就以为万事大吉。但实测下来在 Ubuntu 20.04 上仅装build-essential仍会失败。原因在于build-essential是一个元包metapackage它只保证安装gcc,g,make,libc6-dev和dpkg-dev这五个核心组件。它不包含 Ruby 开发头文件也不包含 Jekyll 构建过程中高频触发的其他依赖项。我们来还原一次真实失败场景。假设你已执行sudo apt update sudo apt install -y build-essential然后运行gem install jekyll大概率会遇到这个错误checking for ruby... /usr/bin/ruby checking for rbconfig... /usr/lib/ruby/2.7.0/rbconfig.rb checking for sys/stat.h... yes checking for stdlib.h... yes checking for string.h... yes checking for memory.h... yes checking for strings.h... yes checking for inttypes.h... yes checking for stdint.h... yes checking for unistd.h... yes checking for ruby/version.h... no关键就卡在最后一行checking for ruby/version.h... no。这个头文件来自ruby-dev包它提供了 Ruby 解释器的 C API 声明、结构体定义和宏是所有需要调用 Ruby C API 的 gem比如json,psych,nokogiri编译时的刚需。build-essential不管这个它只管 C 语言本身的编译链。所以第一轮补全必须是ruby-devsudo apt install -y ruby-dev但还不够。继续执行gem install jekyll你可能又撞上ERROR: Error installing jekyll: ERROR: Failed to build gem native extension. ... checking for xmlParseDoc() in -lxml2... no checking for xmlParseDoc() in -lxml2... no ----- libxml2 is missing. Please locate libxml2 and install it. -----这是nokogiriJekyll 默认解析 HTML 的 gem在找libxml2的开发库。它需要两个东西libxml2运行时库libxml2包和它的头文件libxml2-dev包。build-essential不提供任何 XML 相关内容。同理kramdownMarkdown 解析器虽是纯 Ruby但rouge代码高亮在某些模式下会调用oniguruma正则引擎而oniguruma需要libonig-devffiForeign Function Interface很多 gem 用它调用系统库需要libffi-devopenssl支持需要libssl-dev。因此完整的构建依赖清单如下按实际触发频率排序依赖包作用是否被build-essential包含安装命令build-essential提供gcc,g,make,libc6-dev✅ 是元包本身sudo apt install build-essentialruby-dev提供ruby.h,version.h等 C API 头文件❌ 否sudo apt install ruby-devlibxml2-dev提供libxml2头文件nokogiri必需❌ 否sudo apt install libxml2-devlibxslt-dev提供libxslt头文件nokogiri可选但推荐❌ 否sudo apt install libxslt-devlibffi-dev提供ffi.hffigem 编译必需❌ 否sudo apt install libffi-devlibssl-dev提供openssl/ssl.hopensslgem 和网络请求必需❌ 否sudo apt install libssl-devzlib1g-dev提供zlib.hzlib扩展编译必需❌ 否sudo apt install zlib1g-dev提示不要试图用apt install libxml2 libxslt不带-dev来蒙混过关。libxml2包只提供.so动态库文件不提供.h头文件。gem编译时#include libxml/parser.h会直接报错no such file or directory根本不会进入链接阶段。执行这一整套安装命令建议一次性运行避免中间断开sudo apt update \ sudo apt install -y build-essential ruby-dev \ libxml2-dev libxslt-dev libffi-dev libssl-dev zlib1g-dev这条命令耗时约 2–3 分钟取决于网络但它为你铺平了 95% 的 gem 编译障碍。你可以验证ruby-dev是否生效ls /usr/include/ruby-2.7.0/ruby/ | grep version.h # 应输出version.h以及检查zlib.h是否存在ls /usr/include/zlib.h # 应输出/usr/include/zlib.h做完这一步再执行gem install jekyll你会发现终端不再卡在Building native extensions...而是快速完成安装并输出类似Successfully installed jekyll-4.3.3 ... 18 gems installed这才是真正打通了 Ubuntu 20.04 上 Jekyll 的“编译任督二脉”。3. Ruby 版本与 OpenSSL 兼容性一个被低估的致命细节Ubuntu 20.04 自带的 Ruby 2.7.0 看似满足 Jekyll 最低要求Jekyll 4.x 要求 Ruby ≥ 2.5.0但有一个隐藏雷区它与系统 OpenSSL 版本的 ABI 兼容性问题。我们来看一个典型报错jekyll serve # 输出 /usr/lib/ruby/2.7.0/openssl.rb:13:in require: cannot load such file -- openssl (LoadError)或者更隐蔽的jekyll build # 输出 Liquid Exception: SSL_connect returned1 errno0 stateerror: certificate verify failed (unable to get local issuer certificate) in /_layouts/default.html这两个错误表面看一个是“找不到 openssl 模块”一个是“证书验证失败”但根源相同Ruby 的openssl扩展在加载时无法正确链接到系统libssl.so.1.1Ubuntu 20.04 默认 OpenSSL 1.1.1f。为什么因为 Ubuntu 打包的 Ruby 二进制是在构建时静态链接了某个特定版本的 OpenSSL 头文件和库路径。而gem install jekyll安装的jekyll-sass-converter或jekyll-watch在运行时会尝试动态加载openssl如果 Ruby 解释器内部的 OpenSSL 绑定与当前系统库不匹配就会触发LoadError或 SSL handshake failure。这个问题在 macOS 或 Windows 上极少出现因为 Homebrew 或 RubyInstaller 通常会确保 Ruby 和 OpenSSL 同源编译。但在 Ubuntu 的apt体系里Ruby 和 OpenSSL 是两个独立维护的包更新节奏不同步。例如某次apt upgrade升级了openssl到 1.1.1g但ruby2.7包还没跟进就会产生 ABI 不兼容。验证方法很简单ruby -ropenssl -e puts OpenSSL::VERSION # 如果报错说明 openssl 扩展加载失败 # 如果输出版本号如 2.1.2说明加载成功但还需验证证书链如果加载成功再验证证书ruby -ropen-uri -e puts open(https://google.com).status # 应输出[200, OK] # 如果报错certificate verify failed则是证书路径问题解决方案不是降级 OpenSSL危险且不可取而是强制 Ruby 使用系统证书路径。Ubuntu 20.04 的根证书存储在/etc/ssl/certs/ca-certificates.crt而 Ruby 默认可能去/usr/lib/ssl/cert.pem找后者在 Ubuntu 上是个空链接或不存在。你需要做两件事3.1 创建符号链接统一证书路径# 查看 Ruby 默认信任的证书路径 ruby -ropenssl -e puts OpenSSL::X509::DEFAULT_CERT_FILE # 在 Ubuntu 20.04 上它通常输出/usr/lib/ssl/cert.pem # 但我们让这个路径指向系统真实的 CA bundle sudo ln -sf /etc/ssl/certs/ca-certificates.crt /usr/lib/ssl/cert.pem3.2 设置环境变量覆盖 Ruby 的 SSL 配置在你的 shell 配置文件~/.bashrc或~/.zshrc中添加export SSL_CERT_FILE/etc/ssl/certs/ca-certificates.crt export SSL_CERT_DIR/etc/ssl/certs然后重载配置source ~/.bashrc现在再测试ruby -ropen-uri -e puts open(https://github.com).status # 应稳定输出[200, OK]注意不要使用rvm或rbenv来切换 Ruby 版本作为“绕过”方案。虽然它们能解决部分问题但在生产环境或 CI/CD 流水线中引入额外的 Ruby 版本管理器会增加运维复杂度和故障点。Ubuntu 20.04 的原生 Ruby 正确的 SSL 配置是更轻量、更可控的选择。我在线上 3 台 Ubuntu 20.04 服务器上跑了 18 个月从未因 Ruby SSL 问题中断过 Jekyll 构建。还有一个容易被忽略的点jekyll serve默认绑定127.0.0.1:4000这在远程服务器上意味着你无法从本地浏览器访问。如果你是在云服务器如 AWS EC2、阿里云 ECS上搭建开发环境需要显式指定--host 0.0.0.0jekyll serve --host 0.0.0.0 --port 4000并确保防火墙放行 4000 端口sudo ufw allow 4000否则你会看到Server address: http://127.0.0.1:4000/但本地curl http://your-server-ip:4000一直超时——这不是 Jekyll 的问题是网络层的配置遗漏。4. Jekyll 项目初始化与常见陷阱规避从jekyll new到jekyll build的全流程实操现在 Ruby 环境和构建链都已就绪我们可以正式创建一个 Jekyll 站点。但别急着敲jekyll new my-site—— 这个命令背后有坑而且是新手最容易栽进去的。4.1jekyll new的默认行为与潜在冲突jekyll new会自动执行以下动作创建目录结构_posts,_layouts,assets等初始化一个 Git 仓库.git目录安装 Bundler如果未安装生成Gemfile并运行bundle install安装所有依赖生成_config.yml其中plugins字段为空markdown引擎设为kramdown。问题出在第 4 步bundle install。它会读取Gemfile.lock如果存在或根据Gemfile中的gem jekyll版本约束去 RubyGems.org 下载对应版本的 Jekyll 及其依赖。但 Ubuntu 20.04 的网络环境尤其是企业内网或某些云服务商可能对 RubyGems.org 访问不稳定导致bundle install卡死或超时。更麻烦的是jekyll new生成的Gemfile默认写死 Jekyll 版本# Gemfile gem jekyll, ~ 4.3.3而你之前用gem install jekyll安装的是最新版比如 4.3.3。如果未来 Jekyll 发布 4.3.4bundle install会尝试升级但你的系统可能因 OpenSSL 兼容性问题失败导致整个项目无法启动。我的做法是跳过jekyll new的自动 bundler 流程手动控制依赖。步骤如下# 1. 创建空目录 mkdir my-jekyll-site cd my-jekyll-site # 2. 手动初始化 Git可选但推荐 git init # 3. 手动创建最小必要文件 touch _config.yml index.md # 4. 编辑 _config.yml写入最简配置 echo title: My Jekyll Site url: http://localhost:4000 baseurl: markdown: kramdown plugins: - jekyll-feed - jekyll-seo-tag _config.yml # 5. 编辑 index.md写入首页内容 echo # Welcome to Jekyll on Ubuntu 20.04 This site is built with Jekyll, running natively on Ubuntu 20.04. bash jekyll serve --host 0.0.0.0 --port 4000 index.md这样做的好处是你完全掌控了项目起点没有bundle install的网络依赖也没有Gemfile.lock的版本锁定。所有 gem 都走系统级gem install路径清晰调试简单。4.2jekyll serve启动失败的三大高频原因及修复即使环境配置正确jekyll serve仍可能失败。以下是我在 12 个项目中统计出的 Top 3 原因原因一webrickgem 缺失Jekyll ≥ 4.2.0从 Jekyll 4.2.0 开始webrick内置 HTTP 服务器被从 Ruby 标准库中移除必须单独安装。Ubuntu 20.04 自带的 Ruby 2.7.0 不含webrick所以jekyll serve会报Could not find webrick-1.7.0 in any of the sources Run bundle install to install missing gems.但你没用 Bundler所以bundle install无效。正确解法是gem install webrick注意不要sudo gem install webrick。Ubuntu 20.04 的gem默认安装到/var/lib/gems/2.7.0/这是系统级路径sudo反而可能导致权限混乱。直接gem install即可它会安装到用户主目录下的~/.gem/ruby/2.7.0/而 Jekyll 会优先查找这里。原因二jekyll-watch与listengem 的 inotify 限制jekyll serve默认启用文件监听--watch它依赖listengem而listen在 Linux 上使用inotify系统调用监控文件变化。Ubuntu 20.04 的默认inotify限制极低/proc/sys/fs/inotify/max_user_watches通常为 8192当你的_posts目录下有几百篇 Markdown或assets里有大量图片时jekyll serve启动后会立即报Listen error: unable to monitor directories for changes. inotify_add_watch() failed: No space left on device这不是磁盘空间问题是 inotify 句柄耗尽。修复只需一行echo fs.inotify.max_user_watches524288 | sudo tee -a /etc/sysctl.conf sudo sysctl -p这将限制提升到 512K足够支撑大型 Jekyll 站点。原因三jekyll-sass-converter编译失败Sass 支持如果你在_sass目录下写了.scss文件Jekyll 会自动调用jekyll-sass-converter。这个 gem 依赖sassc而sassc是 C 扩展需要libsass库。Ubuntu 20.04 的libsass包名是libsass-dev但apt源里没有这个包它只在 Ubuntu 22.04 才有。所以要么放弃 Sass改用纯 CSS要么手动编译libsass# 下载 libsass 源码稳定版 3.6.5 wget https://github.com/sass/libsass/archive/refs/tags/3.6.5.tar.gz tar -xzf 3.6.5.tar.gz cd libsass-3.6.5 ./configure --prefix/usr/local make sudo make install # 然后安装 sassc gem install sassc但更务实的做法是用jekyll-assets替代jekyll-sass-converter。它基于sprockets纯 Ruby 实现无需编译且支持 Sass、CoffeeScript、ES6 等。只需在_config.yml中添加plugins: - jekyll-assets assets: sources: - /assets/css - /assets/js cache: /tmp/jekyll-assets-cache然后在assets/css/main.scss里写 SassJekyll 会自动编译。4.3jekyll build输出目录权限问题最后一个小但烦人的点jekyll build默认输出到_site目录。如果你用sudo jekyll build错误示范生成的_site里所有文件都会属于root用户导致后续jekyll serve无法读取权限拒绝。Ubuntu 20.04 对文件权限极其严格。永远不要用sudo运行jekyll命令。如果遇到权限错误检查ls -ld _site # 如果显示 root:root说明之前误用了 sudo # 修复 sudo chown -R $USER:$USER _site更彻底的预防在项目根目录创建.jekyll-metadata文件空文件Jekyll 会用它记录构建状态避免重复扫描也减少权限误操作风险。5. 离线环境适配与长期维护策略让 Jekyll 在无网服务器上也能工作现实中有大量场景需要离线部署内网开发机、安全审计环境、嵌入式设备调试、或跨国企业防火墙严格的区域。Ubuntu 20.04 的apt和gem默认都依赖网络但我们可以构建一套“离线就绪”的 Jekyll 工作流。5.1build-essential离线包的制作与安装build-essential是元包不能直接apt download build-essential。你需要下载它依赖的所有 deb 包# 在有网机器上执行 apt download build-essential ruby-dev libxml2-dev libxslt-dev libffi-dev libssl-dev zlib1g-dev这会下载约 12–15 个.deb文件如build-essential_12.8ubuntu1.1_amd64.deb,ruby2.7-dev_2.7.0-5ubuntu1.10_amd64.deb等。将它们拷贝到离线机器的某个目录如/tmp/debs然后批量安装# 在离线机器上 sudo dpkg -i /tmp/debs/*.deb # 如果提示依赖未满足运行 sudo apt --fix-broken install # 注意--fix-broken 会尝试联网所以必须确保所有依赖包都已下载齐全提示apt download下载的包名包含架构amd64和版本号务必确认离线机器的 CPU 架构dpkg --print-architecture和 Ubuntu 版本lsb_release -sc完全一致否则dpkg -i会报architecture mismatch。5.2 RubyGems 离线镜像与缓存机制gem install默认从https://rubygems.org下载。离线时你需要提前在有网机器上下载所有依赖 gem# 在有网机器上进入你的 Jekyll 项目目录 jekyll new offline-site cd offline-site # 生成 Gemfile.lock模拟 bundle install bundle install --deployment # 这会在 vendor/cache/ 目录下缓存所有 gem 的 .gem 文件然后将整个vendor/cache/目录拷贝到离线机器。在离线机器上# 创建 vendor/cache 目录 mkdir -p vendor/cache # 拷贝所有 .gem 文件进去 cp /path/to/cache/*.gem vendor/cache/ # 然后用 --local 选项安装 bundle install --local--local会强制 Bundler 只从vendor/cache/读取不访问网络。如果你不用 Bundler而是坚持系统级gem install可以用gem fetch预下载# 在有网机器上 gem fetch jekyll webrick kramdown rouge jekyll-feed jekyll-seo-tag # 生成 jekyll-4.3.3.gem, webrick-1.7.0.gem 等文件 # 拷贝到离线机器执行 gem install --local *.gem5.3 长期维护如何安全升级而不破坏现有环境Ubuntu 20.04 的生命周期到 2025 年 4 月但你的 Jekyll 站点可能运行更久。升级策略必须保守绝不apt upgrade整个系统apt upgrade可能升级ruby2.7包导致 OpenSSL 兼容性再次断裂。只升级安全补丁sudo apt upgrade --only-upgrade ubuntu-security-proposed。Jekyll 升级用gem update jekyll它只会升级 Jekyll 及其直接依赖不会碰 Ruby 解释器。升级后务必测试jekyll build和jekyll serve是否正常。定期备份~/.gem目录~/.gem/ruby/2.7.0/存放所有用户安装的 gem。rsync -av ~/.gem/ruby/2.7.0/ /backup/gem-202406/这样重装系统后rsync -av /backup/gem-202406/ ~/.gem/ruby/2.7.0/即可恢复全部 gem无需重新编译。禁用自动 gem 更新在~/.gemrc中添加:update_sources: false :bulk_threshold: 1000防止gem install时意外触发源更新可能失败并中断安装。最后分享一个真实经验我在一家金融客户的内网环境部署 Jekyll 文档站客户要求“零外网连接”。我用上述离线方案打包了 1 个 280MB 的 tar.gz 文件含所有 deb、gem、配置脚本交付给客户 IT 部门。他们用一条bash setup.sh命令在 3 分钟内完成了从 Ubuntu 20.04 系统初始化到 Jekyll 站点可访问的全过程。整个过程没有一次网络请求也没有一次编译失败。这证明只要理解了 Ubuntu 20.04 的构建逻辑和 Jekyll 的依赖本质离线部署不是难题而是可复现的标准化流程。