把官网从一个虚拟空间迁到自己的 ECS,是 2016 年 5 月的事。原站 PHP 5.4 加 MySQL,跑在一台共享主机上,访问慢,后台上传图片经常超时。我们打算前面挂一层 Nginx 做反向代理,顺手上 HTTPS 证书。整个过程两天,踩的坑记一下。
1. upstream 指向 8080,后端压根没监听
本地习惯把 PHP-FPM 配 9000,Nginx 用 fastcgi_pass;这次图省事,让 Apache 继续扛 PHP,监听 8080,Nginx proxy_pass 过去。配置写完 reload,一访问全 502。查了半小时才发现 Apache 的 Listen 还是 80,没改成 8080。
改完这一行,页面出来了,但样式全丢。
2. 静态资源是相对路径
老站模板里写的是 css/style.css,代理之后浏览器把请求落到新域名根目录下,404。临时在 Nginx 加了一条 location 转发,后来干脆把模板里 17 处相对路径改成以斜杠开头的绝对路径。这 17 处是拿 grep 数出来的,不是估的。
3. HTTPS 证书链少了一级
证书只部署了站点证书,没拼中间证书。Chrome 能开,安卓自带浏览器报「证书不受信任」。把 intermediate 那一段 cat 到证书文件后面,重启 Nginx 才好。后来固定用一条命令验:openssl s_client 连一下,看 Verify return code 是不是 0。
4. 301 写成了 302
http 跳 https 一开始写的 return 302,测试没问题,上线后被提醒权重可能不传。改成 301,又在浏览器里缓存了旧跳转,清了半天缓存才看到效果。现在这条配置直接抄模板,不再手敲。
5. 上传大小限制在两处
后台传一张 3M 的图报 413。Nginx 的 client_max_body_size 改了 8m,还是报,才想起来 php.ini 里 upload_max_filesize 是 2M,post_max_size 也得一起动。两处都改完,重启 php-fpm 才生效。
这份清单后来长成什么样
五条后来变六条。2017 年做另一个站,加了一条「先确认后端在监听哪个端口」,还是第 1 条那类事:那回 Nginx 配对了,Apache 却被人改回 80,页面照样 502,查了二十分钟才想起来敲 netstat。同一个坑换个马甲,又踩了一遍。
2019 年再补一条:reload 之前先跑 nginx -t。那次配置里少了个分号,reload 没真生效,页面上还是旧配置,我们却以为改过了,白等一下午。跟证书链那次是一类毛病,都是「以为生效了」。
清单现在放在仓库的 deploy 目录,一个 md 文件,六行,每行一句话。新人配 Nginx,我们让他先照着走一遍,走完再问为什么每条这么写。多数人走到第 3 条会问证书链是什么,那时候讲,比凌晨两点讲省心。
那个 502 的截图没留下,倒是留了一行当时的错误日志:upstream prematurely closed connection。字不好看,看一次记一次。后来有回客户自己照着清单配,漏了 client_max_body_size,传 3M 的图又报 413,打电话过来第一句是「你们清单第 5 条我跳过了」。挺好,至少知道跳的是哪条。
一句没写进清单的话
六条里没有一条讲「怎么选 Nginx 还是别的」。那回没得选,客户预算只够一台 ECS,Apache 已经在跑,前面挂一层是最省事的改法。后来别的项目用过别的方案,但那份清单一直没改头,因为踩的坑是 Nginx 的,换个软件坑就不一样了。六条里最短的是第 4 条,只有六个字:跳转用 301。写得短,是因为那次清缓存清了半天,谁都不想再回忆一遍。
收尾
第二天下午把 DNS 的 A 记录切过去,TTL 提前一天调到了 600。旧主机留了整整两周没关,怕有表单提交落到老库。两周后看访问日志,确实还有零星的 POST 进来,是某个客户浏览器里存着的老书签。
那天之后我们配 Nginx 有了一份 checklist,五条,就是上面这五条。不复杂,但每次照着走一遍,比凭记忆快。

