Nginx 1.18→1.28 零停机升级:JSON 日志 + http2 修复,5 个坑一次讲透

大家好,我是老张。
前两天给服务器做了一次 Nginx 升级,从 Ubuntu 自带的 1.18.0 一路升到官方源的 1.28.0,顺带搞定了 JSON 格式访问日志。
本来以为 apt-get install 一把梭就完事了,结果踩了 5 个坑——签名密钥、http2 弃用语法、error_log 不支持 JSON、配置覆盖……
今天把完整过程整理出来,下次谁要升级 Nginx,照着这篇走就行。
📊 升级速览
| 项目 | 升级前 | 升级后 |
|---|---|---|
| Nginx 版本 | 1.18.0 (Ubuntu 源) | 1.28.0 (Nginx 官方源) |
| 访问日志 | 默认格式 | JSON 格式 |
| JSON 错误日志 | ❌ | ❌(等后续版本) |
| http2 语法 | listen ... http2; | http2 on;(消除弃用警告) |
| 网站服务 | 正常运行 | 正常运行 ✅ |
一、为什么要升级?
三个原因:
- JSON 格式日志:Nginx 默认日志格式虽然能用,但程序化解析很痛苦。JSON 日志可以让 Prometheus、Filebeat、ELK 直接消费
- 错误日志也要 JSON:错误日志的解析比访问日志更麻烦,但 Nginx 1.25.1+ 支持
error_log自定义格式 - 版本差太大:1.18.0 是 2020 年的版本,1.28.0 是 2025 年底发布的,中间跨了 10 个大版本,安全补丁和性能优化都值回票价
Ubuntu Focal (20.04) 官方源最高只提供 1.18.0,要装新版必须切到 Nginx 官方源。
二、升级全过程
1. 添加 Nginx 官方源
# 导入 Nginx 签名密钥
curl -sS https://nginx.org/keys/nginx_signing.key | gpg --dearmor \
-o /usr/share/keyrings/nginx-archive-keyring.gpg
# 添加官方 stable 源
echo "deb [signed-by=/usr/share/keyrings/nginx-archive-keyring.gpg] \
http://nginx.org/packages/ubuntu/ focal nginx" \
> /etc/apt/sources.list.d/nginx-official.list
# 更新源
apt-get update -qq
2. 备份当前配置
cp -r /etc/nginx /etc/nginx.bak.$(date +%Y%m%d)
这步绝对不能省。后面万一翻车,恢复就靠它。
3. 安装新版 Nginx
这里有个关键点——不能直接 apt-get install nginx,否则官方包会覆盖你现有的配置文件。必须用 --force-confold:
apt-get install -y nginx=1.28.0-1~focal \
-o Dpkg::Options::="--force-confdef" \
-o Dpkg::Options::="--force-confold"
--force-confdef:如果配置没被本地修改,用新版默认配置--force-confold:如果配置被本地修改过(你肯定改过),保留你的版本
两个参数一起用,实现平滑升级 + 配置不丢失。
4. 修复 http2 弃用警告
升级后 nginx -t 会报警告:
nginx: [warn] the "http2" directive is deprecated,
use the "http2" directive instead in ...
Nginx 1.25+ 弃用了 listen 443 ssl http2; 语法,改为独立指令:
# 旧语法(弃用)
listen 443 ssl http2;
# 新语法
listen 443 ssl;
http2 on;
改完 nginx -t && nginx -s reload,警告消失。
5. 验证升级
$ nginx -V
nginx version: nginx/1.28.0
$ curl -s -o /dev/null -w "%{http_code}" https://example.com/
200
三、JSON 访问日志配置
升级完就该配 JSON 日志了。直接上配置:
# JSON 格式日志
log_format json escape=json '{"time":"$time_local","remote_addr":"$remote_addr","remote_user":"$remote_user","request":"$request","status":$status,"body_bytes_sent":$body_bytes_sent,"http_referer":"$http_referer","http_user_agent":"$http_user_agent","request_time":$request_time,"upstream_status":"$upstream_status","upstream_response_time":"$upstream_response_time","http_x_forwarded_for":"$http_x_forwarded_for"}';
access_log /var/log/nginx/access_json.log json buffer=32k flush=5s;
三个关键参数:
| 参数 | 作用 |
|---|---|
escape=json | 自动转义 JSON 特殊字符(引号、换行等),没有这个你的 JSON 就是非法格式 |
buffer=32k | 缓冲写入,减少磁盘 IO,高并发场景必备 |
flush=5s | 最多 5 秒强制落盘,兼顾实时性 |
输出效果:
{
"time": "29/Jul/2026:17:00:51 +0800",
"remote_addr": "x.x.x.x",
"request": "GET /api/posts?page=1&page_size=10&status=published HTTP/2.0",
"status": 200,
"body_bytes_sent": 34433,
"request_time": 0.113,
"upstream_status": "200",
"upstream_response_time": "0.065"
}
标准 JSON,可以直接喂给任何日志采集系统。
四、5 个坑,别踩
坑 1:signed-by 语法
Ubuntu 20.04 要求 source.list 中必须用 signed-by= 指定 GPG 密钥路径,否则 apt-get update 直接报错。
不是你配置的问题,是 apt 安全策略升级了。
坑 2:http2 弃用不是报错是警告
很多人会忽略 nginx -t 输出的 warning。
但不修复的话,这个警告会在每次 reload 时出现,时间久了容易埋雷——万一哪天 Nginx 直接移除旧语法支持,你的站点就挂了。
坑 3:error_log JSON 开源版不支持(最大遗憾)
开头说"错误日志也要 JSON",结果翻车了:
- Nginx 开源版 1.28.0 不支持
error_log的 JSON 格式输出 - 这是 Nginx Plus 特性,开源版需要 1.29.8+ 才有
- 目前 apt 源未提供该版本
未来能用的配置留在这,等版本更新:
# 未来可用(Nginx 1.29.8+ 或 Nginx Plus)
error_log /var/log/nginx/error_json.log notice json;
教训:动手前先查 changelog,别凭"我记得"做判断。
坑 4:logrotate 的坑
老手都知道配置 logrotate,但新手容易漏——JSON 日志是新文件,logrotate 管不管?
答案是:如果你之前的 logrotate 配置用的是 /var/log/nginx/*.log 通配符,新 JSON 日志会自动被覆盖。但如果你写死了文件名,就得手动加一行 access_json.log。
坑 5:--force-confold 别忘了
不加这个参数,apt 会提示你"配置文件有修改,要保留还是用新版?"——如果你在非交互式脚本里跑,可能会卡住或误覆盖配置。
💡 老张的总结
- 升级前必备份:
cp -r /etc/nginx /etc/nginx.bak.$(date +%Y%m%d),这是运维的基本功 - 平滑升级 = --force-confold + --force-confdef:两个参数缺一不可,否则配置丢失或安装中断
- JSON 日志的关键是
escape=json:没有这个,生成的 JSON 可能包含未转义字符,解析直接翻车 - 开源版 ≠ Plus 版:Nginx 的 error_log JSON、动态模块等高级功能都是 Plus 专属,做方案前先确认你用的版本支不支持
💬 互动话题
- 你平时用什么方式解析 Nginx 日志?GoAccess、ELK 还是自己写脚本?
- 有没有遇到过
--force-confold翻车的情况?怎么救回来的? - Nginx 升级你最担心什么?欢迎评论区聊聊