很多旧的 TCP 调优文章都带着明显的时代痕迹:

  • 把代理场景、路由转发场景、普通服务端场景混在一起
  • /etc/sysctl.conf 里塞一大堆已经废弃、默认已经合理、或者根本不该全局修改的参数
  • 只给“魔改参数”,不给验证方法

Linux 5.x / 6.x 下,更靠谱的思路是:

  1. 先确认瓶颈是在应用、内核、NIC,还是链路本身
  2. 只改少量确实相关的参数
  3. 每次改动都能用指标或压测结果验证

先看什么

先别急着改 sysctl,先看这些:

# 当前拥塞控制算法和可选项
sysctl net.ipv4.tcp_congestion_control
sysctl net.ipv4.tcp_available_congestion_control

# 当前 qdisc
sysctl net.core.default_qdisc
tc qdisc show dev eth0

# TCP 概览
ss -s
ss -lnt

# 协议计数器
nstat -az

# 网卡统计
ethtool -S eth0

如果问题其实是:

  • 进程 ulimit -n 太小
  • 应用监听 backlog 太小
  • 单进程 accept 太慢
  • 用户态线程池、数据库连接池、磁盘 IO 才是瓶颈

那改 TCP 参数通常不会有本质帮助。

一组足够保守的基线

下面这份配置,适合大多数普通 Linux TCP 服务端作为起点:

# /etc/sysctl.d/90-tcp-server.conf

# listen backlog 上限。Linux 5.4 之后默认已经是 4096,
# 显式写出来是为了避免不同发行版行为不一致。
net.core.somaxconn = 4096

# 半连接队列上限。只有在连接高峰时才会明显影响结果。
net.ipv4.tcp_max_syn_backlog = 4096

# 保留 syncookies 作为 SYN backlog 溢出时的兜底手段。
net.ipv4.tcp_syncookies = 1

# 如果内核支持 BBR,配合 fq 通常是公网链路下一个稳妥的起点。
net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr

# 对 PMTU blackhole 相对友好。
net.ipv4.tcp_mtu_probing = 1

应用后执行:

sudo sysctl --system

然后立刻确认:

sysctl net.core.somaxconn
sysctl net.ipv4.tcp_max_syn_backlog
sysctl net.ipv4.tcp_congestion_control
sysctl net.core.default_qdisc

这些参数为什么值得保留

net.core.somaxconn

这是 listen() backlog 的全局上限。内核文档说明它对应用户态常见的 SOMAXCONN,默认值在 Linux 5.4 之后已经是 4096,不再是很多老文章里写的 128

要注意两点:

  • 应用代码或框架自己传给 listen() 的 backlog 不能太小
  • 只调高 somaxconn,不改应用 backlog,没有意义

net.ipv4.tcp_max_syn_backlog

这是每个监听 socket 的半连接队列上限。内核文档明确写了:服务端如果因为过载而丢积压连接,可以考虑增加它,同时也要一起检查 somaxconn

net.ipv4.tcp_syncookies

这不是吞吐优化开关,而是 backlog 溢出时的回退机制。内核文档甚至专门提醒:它不能拿来支撑“合法高负载”,如果你因为正常流量触发大量 syncookies,优先应该排查 backlog、accept 速度、应用处理能力,而不是把它当性能参数。

net.core.default_qdisc + net.ipv4.tcp_congestion_control

如果内核支持 bbr,它依然是公网高 RTT / 带宽波动场景里一个值得先试的选择。

更精确地说:

  • Google 的 BBR 文档说明,BBR 已经在 Linux 4.9 及以后可用
  • 对于 Linux 4.20 及以后,已经不再“严格要求”安装 fq 才能使用 BBR
  • fq 对高负载服务器通常仍然更合适

所以今天更稳妥的说法不是“必须启用 fq 才能开 BBR”,而是:

  • 新内核上,BBR 不再硬依赖 fq
  • 但如果你控制的是本机发出的 TCP 流量,fq 仍然是很好的默认选择

如果你的内核没有 bbr,那就先保留默认的 cubic,不要为了“调优”去打第三方魔改补丁。

net.ipv4.tcp_mtu_probing

这个参数控制 TCP 的 PLPMTUD 行为。设为 1 时,默认关闭,只在检测到 ICMP blackhole 问题时启用探测;这是比“强行关闭 PMTU 发现”更保守的选择。

这些参数不要再无脑抄了

net.ipv4.tcp_tw_recycle

这个参数在现代 Linux 内核里已经没了。还看到有人让你打开它,可以直接判定那篇文章太老。

net.ipv4.tcp_tw_reuse

现代内核文档里它仍然存在,但默认值已经是 2,也就是仅对 loopback 流量启用。官方文档还明确写了:不要在没有明确专家建议的情况下修改它。

结论很简单:

  • 不要再把它当成“高并发优化必开项”
  • 更不要全局乱调 TIME_WAIT 相关参数来碰运气

net.ipv4.tcp_fin_timeout

这个参数只影响 orphaned FIN_WAIT_2 连接,不是通用“连接超时”旋钮。很多旧文把它解释成“加快连接回收”,这并不准确。

net.ipv4.tcp_fack

内核文档已经写得很直接:这是 legacy 选项,已经没有效果。可以删掉。

net.ipv4.tcp_rmem / net.ipv4.tcp_wmem / net.ipv4.tcp_mem

这些不是不能动,而是大多数机器没必要上来就手工写成几万倍大值。

内核文档说明:

  • TCP 默认就会做接收缓冲自动调优
  • tcp_mem 的默认值会在启动时根据可用内存自动计算

所以如果你没有明确的高 BDP 场景、没有抓到 buffer 不足的证据、也没有配套的压测结果,就先别碰它们。

net.core.netdev_max_backlog / net.core.netdev_budget / net.core.netdev_budget_usecs

这些参数是更偏 NIC / NAPI / PPS 的调度控制,不是普通 Web 服务“高并发优化套餐”。

内核文档对它们的定义分别是:

  • netdev_budget: 一次 NAPI polling cycle 里最多处理多少包
  • netdev_budget_usecs: 一次 polling cycle 最多跑多久
  • netdev_max_backlog: 输入侧来包速度高于内核处理速度时,可排队的最大包数

只有当你已经通过 softnet_statethtool -S、丢包计数器确认瓶颈在这里时,再动这些参数。

全局 keepalive

tcp_keepalive_timetcp_keepalive_intvltcp_keepalive_probes 并不是典型的吞吐优化参数。

它们更像“连接活性策略”:

  • 长连接 RPC、隧道、MQTT、数据库连接池可能需要
  • 普通短连接 HTTP 服务通常不是靠改这三个参数解决问题

如果你只想让某类连接更快失败,优先考虑应用层心跳或 socket 级别的 TCP_USER_TIMEOUT / keepalive 设置,而不是把整台机器的默认 keepalive 策略一起改掉。

net.ipv4.ip_local_port_range

这个参数只在你有大量 主动发起 的出站连接时才常见,比如:

  • 反向代理
  • 抓取器
  • 大量短连接的 API client
  • NAT 网关

它不是典型“服务端吞吐优化”参数。默认范围不够时再调,而且要避开保留端口。

tcp_fastopen 要不要开

可以,但不要当成默认基线。

内核文档里 tcp_fastopen 已经是 bitmap 配置:

  • 0x1: 客户端支持,默认已开
  • 0x2: 开启服务端支持
  • 0x400: 让所有 listener 默认支持 Fast Open

更现实的做法是:

  • 先确认客户端和服务端应用真的支持 TFO
  • 先按单服务逐步开,不要整机全 listener 一刀切

如果你只是普通内网服务,先把 backlog、accept、进程数、FD 限额理顺,收益通常更大。

调优时最容易忽略的两件事

1. 文件描述符上限

ulimit -n
cat /proc/$(pidof your-service)/limits | grep files

如果服务本身连足够的 socket 都开不出来,TCP 参数再漂亮也没用。

2. 应用 backlog 和 accept 模型

应用层至少要确认:

  • backlog 没被框架写死成很小的值
  • 多进程 / 多线程 accept 没有成为瓶颈
  • SO_REUSEPORT、worker 数、event loop 没明显失衡

很多“TCP 调优见效不大”的根因,其实是用户态没接住连接。

如何验证是否真的变好

不要只看“网页打开更快了”。至少做两类验证。

压测

今天优先用 iperf3,不是老 iperf

# 服务端
iperf3 -s

# 客户端,正向 30 秒
iperf3 -c your-host -t 30

# 客户端,反向测试服务端发送能力
iperf3 -c your-host -R -t 30

# 多流测试
iperf3 -c your-host -P 4 -t 30

观测

在调优前后对比这些指标:

ss -s
nstat -az
ethtool -S eth0
sar -n TCP,ETCP 1

如果你改完之后:

  • 重传没降
  • backlog 溢出没降
  • softirq / NIC drop 没改善
  • 应用 P99 延迟没改善

那这次调优基本就不算成立。

结论

如果只想要一个 2026 年还能放心保留的结论版:

  • 先排查应用 backlog、FD 限额、accept 能力
  • 保守基线只保留 somaxconntcp_max_syn_backlogtcp_syncookiestcp_mtu_probing
  • 如果内核支持,优先试 bbr + fq
  • 不要再抄 tcp_tw_recycletcp_fack、超大 tcp_mem
  • netdev_*、keepalive、端口范围都属于“有证据再改”的第二层参数

参考