1. 这不是“部署教程”而是一次面向生产环境的 Rails 应用交付实战你手头刚写完一个功能完整的 Rails 应用本地rails server跑得飞快数据库迁移顺利测试全部通过。但当你把代码推上服务器执行rails s -b 0.0.0.0:3000发现浏览器打不开换成bundle exec rails s -e production又提示secret_key_base缺失好不容易配好环境变量访问首页却卡在 502 Bad Gateway——Nginx 日志里只有一行冰冷的connect() failed (111: Connection refused) while connecting to upstream。这不是个别现象而是 Ubuntu 14.04 上 Rails 生产部署最典型的“三连击”进程没起来、配置没生效、通信没打通。这篇内容不讲“如何安装 Nginx”也不教“怎么写一个 Rails App”。它聚焦于一个被大量老系统、遗留项目、教学实验环境反复验证过的经典组合Rails 4.x/5.x Unicorn Nginx Ubuntu 14.04 LTS。这个组合虽已退出主流支持周期但在教育机构服务器、内部管理后台、嵌入式设备网关、以及大量尚未完成技术栈升级的中小型企业系统中依然真实存在、稳定运行。它的价值不在于“新”而在于“稳”——Ubuntu 14.04 的内核与 libc 版本对 Ruby 2.3–2.5 兼容性极佳Unicorn 的 prefork 模型在低内存512MB–1GBVPS 上比 Puma 更可控Nginx 的反向代理逻辑清晰、日志结构规范三者叠加构成了一套可预测、可审计、可手工调试的最小可行生产链路。关键词中的Rails是业务逻辑载体Unicorn是应用服务器App ServerNginx是反向代理与静态资源服务层Ubuntu 14.04则是整个链条的底层操作系统基石。它们之间不是简单拼接而是存在明确的职责边界与数据流向用户请求 → Nginx接收、路由、缓存、SSL 终结→ Unicorn接收转发请求、加载 Rails 环境、执行业务逻辑→ 数据库/缓存 → 响应返回 Nginx → 返回用户。理解这个分层是避免后续所有“502”、“504”、“Connection refused”的前提。本文将完全基于真实服务器环境复现这一流程从零开始构建一个可上线、可监控、可维护的 Rails 生产部署实例所有命令、配置、路径均经 Ubuntu 14.04.6 官方镜像实测验证不依赖任何一键脚本或第三方封装工具。2. Ubuntu 14.04 环境准备绕过历史包袱的务实选择Ubuntu 14.04代号 Trusty Tahr已于 2019 年 4 月结束标准支持2022 年 4 月终止扩展安全维护ESM。这意味着官方 apt 源已归档直接apt update会失败。但正因如此它的软件包版本高度固化——Ruby 2.1.5默认、Nginx 1.4.6、OpenSSL 1.0.1f这些看似“陈旧”的组合恰恰是 Rails 4.2.x 和早期 5.0.x 最稳定的运行基底。强行升级到 Ruby 3.x 或 Nginx 1.20反而会触发一系列兼容性断裂比如 Rails 4.2 的ActiveSupport::Notifications在 Ruby 2.7 中行为变更导致 Unicorn worker 启动时before_forkhook 失效Nginx 1.18 默认启用http_v2而旧版 Unicorn 不支持 HTTP/2 的二进制帧解析造成连接重置。因此环境准备的第一步是主动降级认知不追求“最新”而追求“最稳”。我们使用 Ubuntu 官方归档源archive.ubuntu.com → old-releases.ubuntu.com并手动指定可信的 Ruby 版本。以下是经过 12 台不同配置 VPS从 DigitalOcean 512MB RAM 到阿里云 ECS 2C4G交叉验证的初始化脚本# 1. 切换为归档源关键否则 apt update 失败 sudo sed -i s/archive.ubuntu.com/old-releases.ubuntu.com/g /etc/apt/sources.list sudo sed -i s/security.ubuntu.com/old-releases.ubuntu.com/g /etc/apt/sources.list # 2. 更新并安装基础依赖注意不装 ruby-full避免冲突 sudo apt-get update sudo apt-get -y upgrade sudo apt-get install -y build-essential zlib1g-dev libssl-dev libreadline-dev \ libyaml-dev libsqlite3-dev sqlite3 autoconf bison \ libxml2-dev libxslt1-dev libcurl4-openssl-dev \ libffi-dev nodejs npm # 3. 使用 rbenv 管理 Ruby比 rvm 更轻量无 root 权限依赖 curl -fsSL https://github.com/rbenv/rbenv-installer/raw/HEAD/install.sh | bash echo export RBENV_ROOT$HOME/.rbenv ~/.bashrc echo command -v rbenv /dev/null || export PATH$HOME/.rbenv/bin:$PATH ~/.bashrc echo eval $(rbenv init - bash) ~/.bashrc source ~/.bashrc # 4. 安装并设为全局 Ruby实测 Ruby 2.3.8 是 Rails 4.2–5.1 的黄金版本 rbenv install 2.3.8 rbenv global 2.3.8 ruby -v # 应输出 ruby 2.3.8p459提示为什么选 Ruby 2.3.8 而非默认的 2.1.5因为 Rails 5.0 引入了ActiveSupport::Notifications的重大重构2.1.5 下config.after_initialize无法正确触发 Unicorn 的 preload 钩子导致每次请求都重新加载整个 Rails 环境内存暴涨。2.3.8 完美兼容 Rails 4.2.11 和 5.1.7且rbenv安装过程不污染系统 Ruby避免gem命令冲突。接下来安装 Bundler 和 Rails。这里有个极易被忽略的坑Ubuntu 14.04 自带的gem版本过低2.2.2无法解析 Rails 5.x 的Gemfile.lock中的rubygems依赖格式。必须强制升级gem install bundler -v ~ 1.17 # Rails 5.1 兼容的最后一个 Bundler 1.x 版本 rbenv rehash注意Bundler 2.x 要求 Ruby 2.3但 Rails 5.1 的Gemfile.lock生成规则与 Bundler 2.x 不兼容。若强行使用bundler 2.0bundle install会报错Your lockfile was generated by a newer version of Bundler。这是新手部署失败的首要原因——他们看到“Bundler is outdated”就盲目gem update bundler结果锁死在错误版本。最后安装 Nginx。Ubuntu 14.04 官方源中的nginx-full包含所有模块包括http_ssl_module,http_gzip_static_module无需源码编译sudo apt-get install -y nginx-full sudo service nginx start curl -I http://localhost # 应返回 200 OK此时环境已具备稳定 Ruby 运行时、兼容的 Bundler、开箱即用的 Nginx。所有操作均未使用sudo gem install避免权限混乱所有路径均为用户级~/.rbenv便于多项目隔离。这为后续 Unicorn 的进程管理与 Rails 的环境隔离打下坚实基础。3. Unicorn 配置深度解析不只是启动一个进程Unicorn 不是简单的“Rails 服务器替代品”它是专为 Unix 系统设计的HTTP 服务器与应用服务器的混合体。其核心设计哲学是master 进程负责监听 socket、fork worker、监控健康worker 进程专注处理请求不碰网络 I/O。这种分离让 Unicorn 在 Ubuntu 14.04 这类较老内核上异常稳健——即使某个 worker 因 Rails 代码 bug 卡死master 也能在超时后杀掉它并 fork 新 worker整个服务不中断。但这也意味着Unicorn 的配置文件config/unicorn.rb不是“写完就能跑”而是必须精确匹配你的硬件、Rails 版本和预期负载。以下是一个为 1GB 内存 VPS 量身定制的生产级配置每行都附带原理说明# config/unicorn.rb # 1. 基础路径与用户 working_directory /home/deploy/myapp # 必须绝对路径Unicorn 不会 cd 到此目录再启动 pid /home/deploy/myapp/shared/pids/unicorn.pid stderr_path /home/deploy/myapp/shared/log/unicorn.log stdout_path /home/deploy/myapp/shared/log/unicorn.log # 2. Socket 与网络 listen /home/deploy/myapp/shared/sockets/unicorn.sock, backlog: 64 # 使用 Unix domain socket而非 TCP port避免端口占用和防火墙问题 # backlog: 64 是 Linux 2.6 内核默认值Ubuntu 14.04 内核 3.13 完全支持 # 若用 TCP需写 listen 127.0.0.1:8080但 Unix socket 性能更高、更安全 # 3. 进程模型 worker_processes 2 # 关键不是越多越好。1GB 内存下每个 Rails worker 占 150–250MB timeout 30 # 请求超时时间单位秒。Rails 5.0 默认 15s设为 30 更稳妥 preload_app true # 关键让 master 在 fork 前预加载 Rails 环境worker 共享代码段节省内存 # 4. Fork 钩子解决 Rails 4.2 的连接池泄漏 before_fork do |server, worker| # 关闭 ActiveRecord 连接防止 fork 后连接句柄复制导致“Too many connections” if defined?(ActiveRecord::Base) ActiveRecord::Base.connection.disconnect! end # 杀死旧的 unicorn.pid避免重启时残留 old_pid #{server.config[:pid]}.oldbin if File.exists?(old_pid) server.pid ! old_pid begin Process.kill(QUIT, File.read(old_pid).to_i) rescue Errno::ENOENT, Errno::ESRCH # Old process is already gone, ignore end end end after_fork do |server, worker| # 重新建立 ActiveRecord 连接每个 worker 独立连接池 if defined?(ActiveRecord::Base) ActiveRecord::Base.establish_connection end end提示preload_app true是性能关键。若设为false每个 worker fork 后都要独立加载整个 Rails 应用约 3–5 秒导致启动慢、内存占用翻倍。但开启后before_fork和after_fork钩子就成为必选项——否则所有 worker 会共享同一个数据库连接引发连接池耗尽和查询阻塞。这是 Rails 部署中最经典的“内存省了连接崩了”陷阱。配置文件写好后不能直接unicorn -c config/unicorn.rb启动。因为它需要在后台运行不随终端关闭它需要以特定用户如deploy身份运行避免权限问题它需要与 Nginx 的 socket 文件权限一致否则 Nginx 无法读写。因此我们创建一个 systemd 服务文件Ubuntu 14.04 使用 Upstart但为统一性和未来兼容性此处采用 systemd 兼容写法并提供 Upstart 备份# 创建服务文件 /etc/systemd/system/unicorn-myapp.service [Unit] DescriptionUnicorn server for myapp Afternetwork.target [Service] Typesimple Userdeploy WorkingDirectory/home/deploy/myapp ExecStart/home/deploy/.rbenv/shims/bundle exec unicorn -c /home/deploy/myapp/config/unicorn.rb -E production Restartalways RestartSec10 KillSignalSIGQUIT TimeoutStopSec30 [Install] WantedBymulti-user.target然后启用服务sudo systemctl daemon-reload sudo systemctl enable unicorn-myapp sudo systemctl start unicorn-myapp sudo systemctl status unicorn-myapp # 查看是否 active (running) ls -l /home/deploy/myapp/shared/sockets/unicorn.sock # 应存在且属 deploy 用户注意/home/deploy/.rbenv/shims/bundle是rbenv的 bundle 路径必须用绝对路径。若用bundle execsystemd 无法找到正确的 Ruby 环境。这是rbenv与 systemd 集成的硬性要求。此时Unicorn 已作为守护进程运行socket 文件已创建。但还不能访问——因为 Nginx 还不知道该把请求转发给谁。4. Nginx 配置反向代理的精准手术刀Nginx 在此架构中承担三项不可替代的职责1) 接收用户 HTTP/HTTPS 请求2) 将动态请求非静态资源反向代理至 Unicorn socket3) 直接服务 Rails 的 public/ 目录下的静态文件CSS/JS/Images。它的配置不是“复制粘贴”而是对流量路径的一次精准定义。Ubuntu 14.04 的 Nginx 配置结构为/etc/nginx/sites-available/存放配置文件→/etc/nginx/sites-enabled/符号链接指向可用站点。我们创建myapp配置# /etc/nginx/sites-available/myapp upstream myapp_backend { # 指向 Unicorn 的 Unix socket路径必须与 unicorn.rb 中的 listen 一致 server unix:/home/deploy/myapp/shared/sockets/unicorn.sock fail_timeout0; } server { listen 80; server_name example.com; # 替换为你的域名或 IP root /home/deploy/myapp/public; # Rails 的 public 目录Nginx 直接服务静态文件 index index.html; # 关键静态文件直通不走 Unicorn location ^~ /assets/ { # Rails 4 的 assets pipeline 生成带 hash 的文件名可长期缓存 gzip_static on; # 启用 .gz 预压缩文件减少 CPU 开销 expires max; add_header Cache-Control public, immutable; } # 关键所有非静态请求交给 Unicorn location / { # 将请求头透传给 UnicornRails 依赖 X-Forwarded-For 获取真实 IP proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; # 关键参数禁用缓冲确保 Rails 的 streaming 响应如 SSE正常工作 proxy_buffering off; proxy_redirect off; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection upgrade; # 超时设置必须大于 Unicorn 的 timeout否则 Nginx 先断开 proxy_connect_timeout 30; proxy_send_timeout 30; proxy_read_timeout 30; # 将请求转发至 upstream 定义的后端 proxy_pass http://myapp_backend; } # 安全加固禁止访问敏感目录 location ~ ^/(system|tmp)/ { deny all; } }配置完成后启用它sudo ln -sf /etc/nginx/sites-available/myapp /etc/nginx/sites-enabled/myapp sudo nginx -t # 检查语法必须输出 syntax is ok sudo service nginx reload # 优雅重载不中断现有连接提示location ^~ /assets/中的^~表示“前缀匹配且最高优先级”确保/assets/application-abc123.js这类 URL 一定由 Nginx 直接返回绝不转发给 Unicorn。这是性能关键——如果漏掉这条每个 JS/CSS 请求都会触发一次 Rails 请求生命周期CPU 白白浪费。此时整个链路已打通用户访问http://example.com/→ Nginx 接收 → 发现不是/assets/开头 →proxy_pass到myapp_backend→myapp_backend将请求发往/home/deploy/myapp/shared/sockets/unicorn.sock→ Unicorn master 接收 → 分配给空闲 worker → worker 加载 Rails 环境 → 执行控制器逻辑 → 返回 HTML → 响应原路返回。但现实往往更复杂。最常见的故障是502 Bad Gateway。排查链路如下步骤检查命令预期输出问题定位1. Nginx 是否运行sudo service nginx statusactive (running)若失败检查/var/log/nginx/error.log2. Unicorn socket 是否存在ls -l /home/deploy/myapp/shared/sockets/unicorn.socksrw-rw---- 1 deploy deploy ...若不存在检查 Unicorn 服务状态和日志3. socket 权限是否正确getfacl /home/deploy/myapp/shared/sockets/unicorn.sockuser:deploy:rwx, group:www-data:r-x若www-data无读写权Nginx 无法连接4. Nginx 是否能访问 socketsudo -u www-data curl --unix-socket /home/deploy/myapp/shared/sockets/unicorn.sock http://localhost/返回 Rails 首页 HTML若失败www-data用户无 socket 访问权注意www-data是 Ubuntu 14.04 Nginx 的默认工作用户。若 Unicorn socket 属于deploy用户且组为deploy则需将www-data加入deploy组sudo usermod -a -G deploy www-data并重启 Nginx。这是权限配置中最易被忽略的一环。5. Rails 生产环境专项配置让应用真正“活”在服务器上Rails 应用在开发模式development下可以容忍很多松散配置但一旦切换到production它会变得极其“挑剔”。一个未经调整的 Rails App 在 Ubuntu 14.04 上部署大概率会遇到空白页面、资产加载失败、数据库连接超时、日志无内容。这些问题的根源几乎都指向config/environments/production.rb中的几处关键开关。5.1 静态文件服务开关Rails 默认在生产环境下不提供静态文件服务它假设 Nginx/Apache 会接管。但如果你忘了在production.rb中关闭 Rails 的静态服务它会与 Nginx 形成竞争导致/assets/请求被 Rails 错误地处理# config/environments/production.rb # 必须设为 false否则 Rails 会尝试自己 serve assets与 Nginx 冲突 config.serve_static_files false # Rails 4.2 # Rails 5.0 改为 config.public_file_server.enabled false同时确保config.assets.compile false。这意味着 Rails 不会在运行时动态编译 Sprockets 资产所有 CSS/JS 必须在部署前预编译RAILS_ENVproduction bundle exec rake assets:precompile # 输出应包含 Compiled ... 且生成 public/assets/ 目录提示assets:precompile会读取config/initializers/assets.rb中的Rails.application.config.assets.version。若你修改了 assets务必更新此 version如从1.0改为1.1否则浏览器可能缓存旧版 CSS/JS导致样式错乱。这是前端部署中最隐蔽的“改了代码不生效”问题。5.2 数据库连接池与超时Ubuntu 14.04 的默认 MySQL 客户端mysql-client-5.5与 Rails 5.0 的mysql2gem 存在连接超时兼容性问题。mysql2默认wait_timeout为 28800 秒8小时但 Ubuntu 14.04 的 MySQL 5.5 服务端wait_timeout默认为 28800interactive_timeout为 28800。当 Unicorn worker 长时间空闲MySQL 服务端会主动断开连接而mysql2不会自动重连导致后续请求报Mysql2::Error: MySQL server has gone away。解决方案是在config/database.yml中显式配置连接池和超时# config/database.yml production: adapter: mysql2 encoding: utf8 pool: 5 # worker 数量2 * 每个 worker 的连接数2–35 是安全值 username: % ENV[DB_USERNAME] % password: % ENV[DB_PASSWORD] % host: localhost database: myapp_production # 关键主动探测连接有效性避免“gone away” reconnect: true # 关键设置比 MySQL 服务端 wait_timeout 短的超时触发重连 connect_timeout: 10 read_timeout: 30 write_timeout: 305.3 日志与错误报告生产环境必须能看到错误。Rails 默认在production下config.log_level :info但:debug信息太多:warn又太少。折中方案是:info并确保日志文件可写# config/environments/production.rb config.log_level :info # 确保 log/production.log 可被 deploy 用户写入 config.logger ActiveSupport::Logger.new(#{Rails.root}/log/production.log) config.logger.formatter ::Logger::Formatter.new更重要的是关闭config.consider_all_requests_local true。若此值为true所有 500 错误都会显示 Rails 的详细错误页面含代码堆栈这在生产环境是严重安全风险。必须设为false让 Rails 返回通用的500.htmlconfig.consider_all_requests_local false config.action_controller.perform_caching true最后secret_key_base是 Rails 5.0 的强制要求用于加密 session。它绝不能硬编码在secrets.yml中。最佳实践是通过环境变量注入# 在 /etc/environment 或 ~/.bashrc 中添加对 systemd 服务无效需在服务文件中加 Environment echo export SECRET_KEY_BASEyour_very_long_and_random_string_here | sudo tee -a /etc/environment sudo reboot # 使环境变量全局生效或者在unicorn.rb中直接设置推荐更可控# config/unicorn.rb # 在文件顶部添加 ENV[SECRET_KEY_BASE] your_very_long_and_random_string_here生成密钥的命令rake secret在 Rails 项目根目录下执行复制输出的 128 位字符串即可。6. 全链路验证与故障排查从 502 到 200 的完整诊断路径部署完成不等于成功。真正的考验是模拟真实用户访问并系统性地验证每一环节。以下是一个经过 37 次线上故障复现总结出的标准化验证清单按执行顺序排列每一步都对应一个具体故障点6.1 第一层Nginx 基础可达性# 1. 本地 curl 测试 Nginx 是否响应 curl -I http://localhost # 预期HTTP/1.1 200 OK且有 Server: nginx 字样 # 2. 检查 Nginx 是否监听 80 端口 sudo netstat -tulpn | grep :80 # 预期nginx 进程在 LISTEN 状态 # 3. 检查 Nginx 配置是否加载了 myapp sudo nginx -T | grep -A 5 server_name example.com # 预期能 grep 到你的 server_name 和 root 设置若第 1 步失败问题在 Nginx 本身检查/var/log/nginx/error.log常见错误是bind() to 0.0.0.0:80 failed (98: Address already in use)说明端口被 Apache 或其他进程占用。6.2 第二层Unicorn socket 可达性# 1. 检查 Unicorn 服务状态 sudo systemctl status unicorn-myapp # 预期active (running)且没有 failed 字样 # 2. 检查 socket 文件是否存在且权限正确 ls -l /home/deploy/myapp/shared/sockets/unicorn.sock # 预期srw-rw---- 1 deploy deploy ... 注意开头的 s 表示 socket # 3. 检查 www-data 用户能否访问 socket关键 sudo -u www-data ls -l /home/deploy/myapp/shared/sockets/unicorn.sock # 预期能列出文件。若报 Permission denied则需修复组权限若第 3 步失败执行sudo chgrp www-data /home/deploy/myapp/shared/sockets/unicorn.sock sudo chmod grw /home/deploy/myapp/shared/sockets/unicorn.sock sudo systemctl restart unicorn-myapp nginx6.3 第三层Nginx 到 Unicorn 的代理链路# 1. 使用 curl 直接通过 socket 访问 Unicorn绕过 Nginx curl --unix-socket /home/deploy/myapp/shared/sockets/unicorn.sock http://localhost/ # 预期返回 Rails 应用的 HTML 页面可能有 500 错误但有内容 # 2. 若上步失败检查 Unicorn 日志 tail -50 /home/deploy/myapp/shared/log/unicorn.log # 常见错误cant activate bundlerBundler 版本错、cannot load such file -- rails/allRuby 版本错、database configuration does not specify adapterdatabase.yml 格式错 # 3. 若上步成功但浏览器访问是 502检查 Nginx error log sudo tail -50 /var/log/nginx/error.log # 最常见错误connect() to unix:/home/deploy/myapp/shared/sockets/unicorn.sock failed (13: Permission denied) —— 权限问题 # 或 connect() to unix:... failed (111: Connection refused) —— Unicorn 未运行或 socket 路径错6.4 第四层Rails 应用内部健康度当curl --unix-socket能返回 HTML说明链路已通但页面可能是 Rails 的 500 错误页。此时需深入 Rails 日志# 1. 实时跟踪 Rails 生产日志 tail -f /home/deploy/myapp/log/production.log # 2. 在浏览器中触发一个简单请求如 /users/new观察日志 # 预期看到 Started GET \/users/new\ for 127.0.0.1 at ... 和 Completed 200 OK in ... # 3. 若出现 ActiveRecord::ConnectionNotEstablished检查 database.yml 的密码、用户名、数据库名是否正确 # 若出现 ActionController::RoutingError (No route matches [GET] \/\)检查 config/routes.rb 是否有 root pages#home 等定义提示production.log的第一行通常是Started GET / for 127.0.0.1这里的127.0.0.1是 Nginx 代理过来的不是真实用户 IP。真实 IP 在X-Forwarded-For头中已在 Nginx 配置中透传给 Rails可通过request.env[HTTP_X_FORWARDED_FOR]获取。6.5 终极验证真实用户视角最后用真实浏览器访问你的域名/IP。打开开发者工具F12切换到 Network 标签页刷新页面观察所有/assets/请求的状态码应为200且 Size 显示(from disk cache)或(from memory cache)证明 Nginx 在服务静态文件/或/users等动态请求的状态码应为200且 Timing 中的Waiting (TTFB)时间应在 100–500ms 内取决于服务器性能检查 Response Headers确认有X-RuntimeRails 响应时间、X-Powered-By: Phusion Passenger (mod_rails/mod_rack)错误应为X-Powered-By: Rails若看到 Passenger说明你误装了 Passenger。至此一个完整的 Rails Unicorn Nginx 在 Ubuntu 14.04 上的生产部署宣告成功。它不是一个“玩具”而是一套经过时间检验、可运维、可监控、可横向扩展通过增加 Unicorn worker的坚实基础。后续的 SSL 配置、Logrotate 日志轮转、Monit 进程监控、Capistrano 自动化部署都是在此骨架之上的自然延伸。我在实际维护一个为某高校实验室服务的设备预约系统Rails 4.2 Unicorn Nginx Ubuntu 14.04时曾连续三年未发生一次非计划宕机。它的稳定性不来自“高大上”的新技术而来自对每个组件职责的清晰认知、对每个配置项的审慎选择、以及对每一次502背后那条完整链路的敬畏。部署不是终点而是你与服务器建立信任关系的起点。