sysctl 是那个「抄来的配置」,也是最容易抄错的地方
几乎每个站长的服务器上都有一份从某篇教程复制下来的 /etc/sysctl.conf。参数名字长得像天书,注释写着「优化网络性能」「提升并发」,你照着贴上去,跑 sysctl -p,没报错,然后就把这件事忘了。
问题是,这些参数里的每一个都有明确的适用场景和副作用,而教程往往只给结论不给前提。同一个 net.ipv4.tcp_tw_reuse=1,在短连接高并发的爬虫站上是解药,在某些有状态防火墙后面却是毒药;同一个 vm.swappiness=10,在 8G 内存的机器上是对的,在 1G 内存的小机器上会让 OOM 提前几个小时到来。抄错参数的代价不是报错,而是服务器在某个特定负载下表现诡异,而你永远想不到问题出在那份「优化配置」上。
这篇文章把 sysctl 里和个人站长最相关的几类参数拆开讲清楚:它到底改的是什么、什么时候该改、改错了会怎样、以及怎么验证改动真的生效了。
先搞清楚 sysctl 改的是什么,以及它为什么会「看起来没生效」
sysctl 的本质是读写内核的运行时参数,这些参数在 /proc/sys/ 下都能看到对应的文件。比如 net.ipv4.tcp_syncookies 就对应 /proc/sys/net/ipv4/tcp_syncookies。理解了这层映射,排查问题会简单很多:
# 查看某个参数的当前值(两种方式等价)
sysctl net.ipv4.tcp_syncookies
cat /proc/sys/net/ipv4/tcp_syncookies
# 查看所有可调参数
sysctl -a | wc -l
# 现代内核大约有 1000 个左右可调项配置文件是有明确优先级的,这是很多人改了半天没生效的根本原因。加载顺序从低到高是:/etc/sysctl.conf → /etc/sysctl.d/*.conf(按文件名字母序)。后面的会覆盖前面的。所以在 /etc/sysctl.d/99-custom.conf 里写的值,会盖掉 /etc/sysctl.d/50-default.conf 里的同名项。如果你的改动莫名不生效,第一个检查动作就是翻一遍所有配置文件里有没有第二个地方也设了这个参数:
# 找出到底哪些文件设置了 tcp_tw_reuse
grep -rn "tcp_tw_reuse" /etc/sysctl.conf /etc/sysctl.d/ 2>/dev/null另一个经典陷阱是内核已经在别处锁定了这个值。某些参数在系统启动早期就被 systemd 或发行版的默认配置写死,你手动改完之后,重启会变回原值。这种要改 /etc/default/grub 里的内核命令行参数(GRUB_CMDLINE_LINUX)而不是 sysctl 文件。
还有一点必须记住:sysctl -p 只加载默认文件,不会自动加载 /etc/sysctl.d/ 下的所有文件。正确的全量加载方式是:
# 重新加载所有 sysctl.d 配置(按顺序,和开机时一致)
sysctl --system很多「改完没生效」的案例,就是改了 sysctl.d 下的文件却只跑了 sysctl -p。记住这条,能省掉一半的困惑。
连接数与文件句柄:别让服务器在 1000 个连接上撞墙
对个人站长来说,最常撞到的第一个硬限制是文件句柄数。在 Linux 里每个 TCP 连接、每个打开的文件都占一个文件描述符,默认上限通常是 1024。当 Nginx 或 MySQL 想打开第 1025 个连接时,它会拿到 EMFILE: Too many open files,而这个错误在不同程序里的表现完全不一样:Nginx 可能直接 502,PHP 可能静默失败,MySQL 可能报连接错误——这就是为什么「服务器一上量就出各种怪问题」经常是同一个根因。
# 看当前上限
ulimit -n # 当前 shell 的软限制
cat /proc/sys/fs/file-max # 系统全局上限(通常几十万,一般不用改)
# 看某个进程实际用到多少
ls /proc/$(pgrep -f nginx | head -1)/fd | wc -l调法分两层。系统级写进 /etc/security/limits.conf 或者 /etc/security/limits.d/99-nofile.conf:
* soft nofile 65535
* hard nofile 65535
root soft nofile 65535
root hard nofile 65535服务级则在 systemd unit 里写,因为 systemd 管理的服务不读 limits.conf(这是个大坑):
# /etc/systemd/system/nginx.service.d/limits.conf
[Service]
LimitNOFILE=65535改完 systemctl daemon-reload && systemctl restart nginx,然后验证——注意要验证的是进程的限制,不是 shell 的:
cat /proc/$(cat /run/nginx.pid)/limits | grep "open files"另一个高频参数是内核的连接跟踪表大小。开了防火墙(iptables/nftables)之后,每个连接都会在 conntrack 表里留一条记录,表满了就会丢包,表现是「明明带宽和 CPU 都很闲,但新连接就是建立不起来」:
# 当前连接跟踪状态
sysctl net.netfilter.nf_conntrack_count net.netfilter.nf_conntrack_max
# 调大(假设 4G 内存,可以设到 262144)
net.netfilter.nf_conntrack_max = 262144
net.netfilter.nf_conntrack_tcp_timeout_established = 86400把 established 的超时从默认的 5 天调到 1 天,能让空闲连接更快从表里释放,对小型服务器是更划算的取舍。
TCP 参数:哪几个真的有用,哪几个是过时迷信
网络参数是 sysctl 谣言的重灾区,这里逐条说清楚:
net.ipv4.tcp_tw_reuse = 1——这个是真有用的,且在现代内核(4.x 之后)上是安全的。它允许 TIME_WAIT 状态的套接字在确认安全时被复用于新连接。为什么 TIME_WAIT 会堆积?因为主动关闭连接的一方要等 2MSL(默认 60 秒)才能完全释放端口。高并发短连接的场景下(比如 API 服务器),TIME_WAIT 能堆到几万个,吃掉大量端口。开启 reuse 后可以显著缓解。用 ss -s 或 netstat -an | grep TIME_WAIT | wc -l 确认实际堆积量再决定。
net.ipv4.tcp_tw_recycle——请不要用这个。它在 Linux 4.12 之后已经被内核彻底移除,任何教你设它的教程都是至少八年以上的老文章。它在 NAT 环境下会导致随机丢连接,是臭名昭著的故障源。如果你在服务器上看到这个参数且没报错,说明你按 sysctl -p 的静默忽略成功骗过了自己。
net.core.somaxconn——控制 accept 队列的长度,默认通常是 128 或 4096(取决于发行版)。当瞬时并发建连超过这个数,多出来的连接会被内核直接丢弃,客户端看到的是「连接超时」而不是「连接被拒绝」。用 ss -lnt 看 LISTEN 状态的 Send-Q 列(对监听套接字来说它表示 accept 队列上限),如果它已经满或者 netstat -s | grep -i "listen" 里有 overflow 计数在涨,就该调大:
net.core.somaxconn = 4096
net.ipv4.tcp_max_syn_backlog = 8192注意 somaxconn 是「应用程序请求值和这个值的较小者」。Nginx 的 listen ... backlog= 和 PHP-FPM 的 listen.backlog 如果配得比 somaxconn 大,实际生效的还是 somaxconn。两边要一起看。
net.ipv4.tcp_fin_timeout = 30——把 FIN_WAIT2 状态的超时从默认 60 秒缩到 30 秒。这个改动的收益不大,但副作用也小,属于可改可不改。真正需要注意的是它不会缩短 TIME_WAIT,很多人误以为改了它 TIME_WAIT 就消失了,这是把两个状态搞混了。
改完之后一定要验证,而且要用对方法
sysctl 的改动不会给你任何反馈——写错了参数名,sysctl --system 只会默默跳过那一行,不报错。所以验证是必须的,而且要验证两层:
# 第一层:确认内核实际接受的值
sysctl net.core.somaxconn net.ipv4.tcp_tw_reuse net.netfilter.nf_conntrack_max
# 第二层:确认服务进程受到的限制(systemd 服务不读 limits.conf!)
systemctl show nginx -p LimitNOFILE
# 第三层:确认业务层面真的变了
ss -s # 看 TIME_WAIT 总数是否下降
awk '{print $1}' /proc/net/nf_conntrack | sort | uniq -c # conntrack 分布
cat /proc/net/sockstat # sockets: used / TCP 各状态计数一个特别容易忽略的点:改完 sysctl 之后,已经建立的连接不会受新参数影响。新的 TCP 参数只对改动之后新建的连接生效。所以在生产环境里改网络参数,正确姿势是先改配置、再滚动重启服务、然后观察至少半小时的指标曲线,而不是改完立刻下结论「没用」。我在自己的机器上踩过这个坑——改完 somaxconn 立刻看还是丢包,差点以为参数无效,其实是老连接还在,等它们自然过期之后新连接才开始走新队列。
最后,把 sysctl 配置纳入版本管理。个人站长经常在三台机器上手改,日子久了自己都记不清哪台是什么配置。/etc/sysctl.d/ 下的文件很小,扔进 git 或者笔记里存一份,比出事之后 SSH 到每台机器上 sysctl -a 肉眼比对要可靠得多。
小结
sysctl 不是「抄一份万能配置」就完事的东西,它是一组有明确语义和副作用的开关。真正需要的其实就三类:文件句柄数(别让连接数撞上 1024)、连接跟踪表(别让 conntrack 满导致静默丢包)、以及少数几个 TCP 队列参数(somaxconn、tcp_tw_reuse)。其余的绝大多数参数,在个人站长的负载量级下改了也没什么意义。记住那两个最关键的坑:sysctl -p 不加载 sysctl.d 目录且不报错,systemd 服务不读 limits.conf——把这两件事记住,你就能避开九成以上的「参数改了却没生效」的困惑。