怎么降低TTFB:从后端处理到网络链路的实操调优
TTFB(Time To First Byte,首字节时间)是浏览器发出请求到收到服务器第一个字节的间隔。
降低它的核心思路不是盲目换服务器,而是先测出时间消耗在哪一段:如果是服务端处理慢,就优化代码、数据库和缓存;
如果是网络握手慢,就启用CDN、HTTP/2和合理的连接复用。
下面按“先定位、再动手、后验证”的顺序讲具体操作。
先测清楚TTFB到底慢在哪
在终端用 curl 可以拿到分阶段耗时,这是排查TTFB最直接的入口:
curl -o /dev/null -s -w "DNS: %{time_namelookup}s\nTCP: %{time_connect}s\nTLS: %{time_appconnect}s\nTTFB: %{time_starttransfer}s\n总计: %{time_total}s\n" https://你的域名/
time_namelookup到time_connect的差值大,说明DNS解析或TCP建连慢,优先检查DNS服务商和机房线路。time_connect到time_appconnect的差值大,说明TLS握手耗时高,考虑开启TLS 1.3、会话复用或把证书链精简到必要层级。time_appconnect到time_starttransfer的差值大,说明请求已经到达服务器,但后端处理慢,重点查应用、数据库和PHP-FPM。
浏览器开发者工具的“网络”面板也能看到“等待服务器响应”这一项,它大致对应服务端处理时间。
建议同时用 curl 和浏览器各测三次,取中间值,避免单次网络抖动造成误判。
服务端处理慢:从Nginx和PHP-FPM入手
如果定位结果指向服务端,先看Nginx和PHP-FPM的配置。
对常见的PHP站点,下面几项调整往往收益明显。
开启FastCGI缓存和合理的缓冲
在Nginx站点配置的 server 块中加入缓存路径和FastCGI缓存规则(路径按实际环境调整):
fastcgi_cache_path /var/cache/nginx/fastcgi levels=1:2 keys_zone=php_cache:100m inactive=60m;
fastcgi_cache_key "$scheme$request_method$host$request_uri";
location ~ \.php$ {
fastcgi_cache php_cache;
fastcgi_cache_valid 200 301 302 10m;
fastcgi_cache_use_stale error timeout updating;
add_header X-FastCGI-Cache $upstream_cache_status;
include fastcgi_params;
fastcgi_pass unix:/run/php/php-fpm.sock;
}
改完执行 nginx -t 检查语法,再 systemctl reload nginx 重载。
注意:登录态、购物车、后台等动态页面不要套用整页缓存,否则会串用户数据。
可以用 fastcgi_cache_bypass 按Cookie或URL排除。
调整PHP-FPM进程数
进程数不足时,请求会排队等待,TTFB直接升高。
打开 /etc/php/8.1/fpm/pool.d/www.conf(版本号按实际路径),重点看:
pm = dynamic
pm.max_children = 50
pm.start_servers = 10
pm.min_spare_servers = 5
pm.max_spare_servers = 20
pm.max_requests = 500
pm.max_children 不是越大越好,它受内存限制。
粗略估算:单个PHP进程约占30–80MB,用 free -m 看可用内存后倒推上限。
改完 systemctl reload php8.1-fpm,再用 systemctl status php8.1-fpm 确认没有启动失败。
数据库和对象缓存是动态页的大头
如果页面每次都要查数据库,服务端处理时间很难降下来。
先做两件事:给高频查询字段加索引,把重复查询结果放进Redis或Memcached。
用慢查询日志找出耗时SQL:
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1;
然后检查 EXPLAIN 输出里是否出现 type: ALL 的全表扫描。
对 WHERE、ORDER BY 常用字段建立联合索引时,注意把选择性高的字段放在前面。
索引不是越多越好,写入频繁的表要控制数量。
对象缓存方面,WordPress等常见程序可以直接装Redis对象缓存插件;
自研应用则在代码层把“读多写少”的查询结果缓存起来,并设置合理的过期时间。
缓存命中率上来后,TTFB通常会明显下降。
网络链路慢:CDN和HTTP/2怎么配
当 curl 显示DNS和TLS阶段占比高,而服务端处理时间并不长时,优化重点应放在链路上。
- 接入CDN:把静态资源域名CNAME到CDN提供的地址,源站只处理动态请求。CDN节点离用户更近,能显著缩短建连和首字节等待。
- 开启HTTP/2:在Nginx的
listen 443 ssl后加http2,减少多资源请求的队头阻塞。 - 启用TLS 1.3和会话复用:在
ssl_protocols中保留TLSv1.2 TLSv1.3,并配置ssl_session_cache shared:SSL:10m;。
配置片段示例:
listen 443 ssl http2;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1h;
改完同样先 nginx -t 再重载。
如果站点已接入CDN,记得在CDN控制台也开启HTTP/2和TLS 1.3,并确认回源协议与源站配置一致,否则可能出现回源失败。
改完怎么验证有没有效果
每次调整后,用同一组命令复测,并对比调整前后的数值:
for i in 1 2 3; do
curl -o /dev/null -s -w "TTFB: %{time_starttransfer}s\n" https://你的域名/
done
同时观察Nginx的 X-FastCGI-Cache 响应头是否出现 HIT,以及PHP-FPM状态页里的活跃进程数是否接近上限。
如果TTFB没有下降,先回退最近一次改动,再重新分段测量,不要同时改多个地方导致无法判断原因。
几个容易踩的坑
- 只加缓存不设过期:缓存内容长期不更新,用户看到旧数据,反而引发新问题。
- 忽略登录态页面:对带用户信息的页面做整页缓存,可能造成隐私泄露。
- 盲目调大进程数:内存被吃满后触发OOM,服务直接不可用。
- CDN回源未优化:CDN节点回源到源站仍然慢,用户侧TTFB改善有限,需要检查回源线路和源站带宽。
降低TTFB没有一招通吃的方案,关键是先用 curl 把时间拆开,再针对占比最大的那一段做配置或代码调整,最后用同一套测量方法验证。
按这个顺序操作,新手也能一步步把首字节时间压下来。