如果你曾经在一台没有桌面环境的Linux服务器上手动连过Wi-Fi,多半会撞上wpa_supplicant这个名字。它在Wi-Fi连接链里不是一个可有可无的辅助工具,而是真正负责802.11认证、密钥握手和连接状态维护的后台守护进程。无论你是折腾树莓派、给嵌入式设备做开机自动联网,还是把淘汰笔记本改造成无线接入点旁边的常驻客户端,只要绕不开WPA/WPA2/WPA3加密的无线网络,就必须跟它打交道。
这篇文章不讲大而全的man手册翻译,而是从实际使用场景出发,把wpa_supplicant和配套的wpa_cli、配置文件、systemd服务、故障排查串成一套可以直接照搬的工作流。适合正在为"无线网卡连不上公司Wi-Fi"或者"板子开机后没自动联网"头疼的开发者,也适合刚接触Linux无线网络、想搞清楚里面究竟发生了什么的新手。
1. wpa_supplicant在无线连接栈里的位置:不只是一个命令
1.1 从网卡亮灯到获取IP,中间发生了什么
很多人第一次接触wpa_supplicant是在网上搜"Linux命令行连Wi-Fi"时看到的一段指令:
sudo wpa_supplicant -B -i wlan0 -c /etc/wpa_supplicant/wpa_supplicant.conf照抄之后发现Wi-Fi连上了,但没拿到IP,接着又得手动跑dhclient wlan0。于是有人疑惑:wpa_supplicant到底管到哪一步?
答案很干脆:它只管无线链路的建立,不负责IP地址分配。在Linux里,从无线网卡到真正能上网,至少经过三件事:
- 驱动把网卡从硬件层面拉起来,并且通过
nl80211这个内核接口暴露给用户态。这一步通常用ip link set wlan0 up完成。 wpa_supplicant对这个网卡执行扫描、选择网络、关联AP、完成WPA/WPA2/WPA3四次握手。这一步做完,你的无线链路才处于"连接已被信任"的状态。- DHCP客户端(
dhclient、udhcpc或dhcpcd)在已关联的接口上请求IP地址。
打个比方:无线网卡是门锁,驱动是锁芯,wpa_supplicant是保安。保安负责核对你的身份、确认你能进门,但进门之后坐哪个工位、领什么工牌,是前台(DHCP服务器)说了算。三者缺一不可。
1.2 为什么不能直接ip link set wlan0 up就完事
有人会问:不加密的开放网络,是不是不需要wpa_supplicant?理论上如果驱动和iw工具够新,可以用iw wlan0 connect "开放SSID"这种命令直接关联,但实际使用中几乎没人这么干。原因有几个:
- 你连的AP不一定是开放网络,绝大多数家庭路由器和企业网络都启用了WPA2或WPA3。
iw直接连接只适合快速临时测试,不处理重连、密匙协商、漫游和网络优先级这些工程化问题。- 内核里的
cfg80211只提供机制,不提供策略。而wpa_supplicant正是那个把"选哪个网络、用什么方式认证、挂了怎么办"这些策略跑起来的东西。
换句话说,wpa_supplicant不是一个简单的命令,而是一个完整实现了IEEE 802.11i/RSN(Robust Security Network)机制的守护进程。它接管了无线网卡的认证状态机,并且在状态异常时主动处理。这也是它值得花点时间搞明白的原因。
2. 配置文件实战:不同网络场景的wpa_supplicant.conf写法
2.1 配置文件的骨架与全局选项
/etc/wpa_supplicant/wpa_supplicant.conf是默认配置路径,但不同发行版可能略有差异。一个最简可用的配置文件长这样:
ctrl_interface=/run/wpa_supplicant update_config=1 country=CN network={ ssid="MyHomeWiFi" psk="my-secret-password" key_mgmt=WPA-PSK }逐项说下:
ctrl_interface:指定wpa_cli用来和守护进程通信的控制接口目录。这个字段必须存在,否则wpa_cli没法用。update_config=1:允许wpa_cli save_config把运行时的网络配置写回配置文件。如果你不想让工具改配置,可以不加。country=CN:设置国家码,直接影响无线网卡是否允许使用5GHz的DFS信道。很多人扫不到5G频段,就是缺了这个字段。network={}:定义一个要连接的网络。可以写多个,后面会说优先级。
配置文件里的引号是可选的,前提是内容没有特殊字符。但如果SSID或密码里带空格、#、"这类符号,最好老老实实用双引号包起来,否则解析时容易出问题。
2.2 高频场景:家庭PSK、开放网络、企业认证、WPA3
不同安全级别的网络,key_mgmt字段写法完全不同。我整理了几个高频可直接套用的配置。
家庭路由器最常见的WPA2-PSK:
network={ ssid="HomeWiFi" psk="12345678" key_mgmt=WPA-PSK pairwise=CCMP group=CCMP }pairwise和group是指定数据加密算法。现代路由器基本都用CCMP(也就是AES),一般不用写死,但如果你遇到过"握手成功但传不了数据"的怪问题,可以显式加上这两行。
无密码的开放网络:
network={ ssid="FreeAirportWiFi" key_mgmt=NONE }这里如果写成key_mgmt=NONE但网络实际上要网页认证(Captive Portal),wpa_supplicant依然只能帮你关联上AP,浏览器跳出的认证页面是另一个层面的事。
企业WPA/WPA2-Enterprise,典型PEAP+MSCHAPv2:
network={ ssid="CorpWiFi" key_mgmt=WPA-EAP eap=PEAP identity="yourname@company.com" password="yourpassword" phase2="auth=MSCHAPV2" }企业网络认证失败的坑很多,最常见的是缺少CA证书校验。上面的写法是匿名方式,生产环境建议加上ca_cert="/path/to/ca.pem"。如果不加,登录密码是以明文形式跑在受信任的加密隧道里,安全系数会打折扣。
WPA3-Personal(SAE):
network={ ssid="WPA3Test" key_mgmt=SAE sae_password="password2024" ieee80211w=2 }ieee80211w=2表示PMF(Protected Management Frames)必须启用,这是WPA3认证里比较常见的要求。但如果AP配置为混合WPA2/WPA3模式,key_mgmt=SAE可能会导致兼容性问题,这时需要保留WPA-PSK备选。
2.3 用wpa_passphrase生成psk:隐藏SSID和特殊字符的坑
明文psk写在配置文件里有个隐患:如果配置文件权限没设好,Wi-Fi密码就等于直接暴露了。更稳妥的做法是用wpa_passphrase生成256位PMK的预计算哈希:
wpa_passphrase MyHomeWiFi 12345678 >> /etc/wpa_supplicant/wpa_supplicant.conf输出类似:
network={ ssid="MyHomeWiFi" #psk="12345678" psk=4e1a1a5c5b6d8c2e... }wpa_supplicant支持用PSK哈希代替明文,这样即使别人看到配置文件也拿不到原始密码。不过要注意,如果密码特别简单,哈希也能被字典攻击跑出来,所以最终安全还是要靠文件权限:chmod 600。
隐藏SSID的网络需要在network块里加scan_ssid=1,否则扫描阶段因为不广播而找不到网络:
network={ ssid="HiddenNet" psk="secret" scan_ssid=1 }这里有个容易忽略的细节:如果你开启了scan_ssid=1,在某些驱动上主动探测隐藏SSID会降低扫描效率,所以没必要对每个网络都加这一行。
3. 命令行控制台:wpa_cli连接Wi-Fi的交互操作
3.1 前台启动与后台启动,各什么时候用
排障时我会用前台模式:
sudo wpa_supplicant -i wlan0 -c /etc/wpa_supplicant/wpa_supplicant.conf -D nl80211这样日志直接打到终端上,三秒钟之内能看到扫描、关联、握手的全过程。确认没问题后再用-B切到后台:
sudo wpa_supplicant -B -i wlan0 -c /etc/wpa_supplicant/wpa_supplicant.conf -D nl80211-D nl80211是指定驱动后端。现代Linux无线网卡基本都走nl80211,老掉牙的驱动可能用wext,一般不需要手动指定,但如果遇到"nl80211: Driver does not support authentication/association"这类报错,可以试试-D wext。
3.2 wpa_cli最常用命令与完整连接流程
wpa_cli是一个和wpa_supplicant进程通信的命令行客户端。进入交互模式比较简单:
sudo wpa_cli -i wlan0也可以一条一条执行,比如扫描:
sudo wpa_cli -i wlan0 scan sudo wpa_cli -i wlan0 scan_resultsscan_results输出的内容非常直观:bssid / frequency / signal level / flags / ssid。看到自己要连的网络后,可以用一组命令把网络加上并连接:
# 添加一个新网络,返回网络ID,通常从0开始 sudo wpa_cli -i wlan0 add_network # 设置SSID和PSK sudo wpa_cli -i wlan0 set_network 0 ssid '"MyHomeWiFi"' sudo wpa_cli -i wlan0 set_network 0 psk '"12345678"' # 启用并选择该网络 sudo wpa_cli -i wlan0 enable_network 0 sudo wpa_cli -i wlan0 select_network 0 # 保存配置到文件 sudo wpa_cli -i wlan0 save_config这里有一个特别容易踩的坑:set_network 0 ssid指令里,值必须带引号,而且如果你是在shell里执行,需要写成'"MyHomeWiFi"'。因为wpa_cli会把字符串中的双引号解析为边界,不加的话SSID会少一截。如果SSID里有空格、中文或特殊符号,这一步写错基本连不上。
wpa_cli的常用指令用一个表格总结:
| 指令 | 作用 |
|---|---|
status | 查看当前连接状态、SSID、IP等 |
scan | 触发一轮无线扫描 |
scan_results | 显示扫描结果 |
list_networks | 显示配置文件中的网络列表 |
add_network | 新建一个网络并返回编号 |
set_network <id> <field> <value> | 设置网络参数(ssid/psk/key_mgmt等) |
enable_network <id> | 启用某个网络 |
disable_network <id> | 禁用某个网络 |
select_network <id> | 强制连接某个网络,其他网络将被忽略 |
save_config | 把运行时的网络配置写回文件 |
disconnect/reconnect | 断开/重新连接当前无线链路 |
quit | 退出交互模式 |
3.3 读懂status输出,定位连接到了哪一步
运行wpa_cli -i wlan0 status,会得到类似这样的输出:
Selected interface 'wlan0' BSSID=xx:xx:xx:xx:xx:xx freq=2412 ssid=MyHomeWiFi id=0 key_mgmt=WPA2-PSK pairwise_cipher=CCMP group_cipher=CCMP wpa_state=COMPLETED ip_address=192.168.1.100 p2p_device_address=...重点看wpa_state字段:
DISCONNECTED:还没关联到AP,或主动断开。SCANNING:正在扫描可用网络。AUTHENTICATING:正在和AP做802.11认证。ASSOCIATING:正在关联(想到一个AP门下面)。ASSOCIATED:已经关联上AP,但还没完成WPA握手。COMPLETED:四次握手完成,无线链路已经安全建立。
如果状态卡在AUTHENTICATING,大概率是密码协商问题;如果一直SCANNING半天不动作,多半是扫描列表里没有匹配的SSID,或者AP射频被关了。记住这个状态流转,后面排障会顺很多。
4. 用systemd把Wi-Fi连接做成稳定服务
4.1 自己写wpa_supplicant.service而不是依赖发行版默认
很多发行版自带的wpa_supplicant.service是多接口通用服务,配置方式比较绕。我更推荐在嵌入式设备或服务器上给每个无线接口单独建一个服务单元。
例如创建/etc/systemd/system/wpa_supplicant@wlan0.service:
[Unit] Description=WPA supplicant for wlan0 Requires=sys-subsystem-net-devices-wlan0.device After=sys-subsystem-net-devices-wlan0.device Before=network.target [Service] Type=simple ExecStart=/sbin/wpa_supplicant -i wlan0 -c /etc/wpa_supplicant/wpa_supplicant-wlan0.conf Restart=on-failure RestartSec=5 [Install] WantedBy=multi-user.target这里有两个值得注意的细节:
Requires=sys-subsystem-net-devices-wlan0.device是一个udev生成的设备单元。写成依赖后,如果无线网卡不存在,服务就不会启动,避免出现"开机时网卡还没枚举好,wpa_supplicant先启动结果报错"的经典问题。Restart=on-failure配合RestartSec=5,意思是如果wpa_supplicant因为异常原因退出,5秒后自动拉起。无线网卡驱动偶发挂掉时,这个配置能省去手动重启服务的时间。
启动方式:
sudo systemctl daemon-reload sudo systemctl enable wpa_supplicant@wlan0 --now4.2 与NetworkManager、dhcpcd的分工和冲突
如果你的系统同时装了NetworkManager,开机后它也会尝试管理wlan0,结果就是两个进程同时去控制同一个无线网卡,日志里全是奇怪的错误。解决方式有几种:
- 最简单粗暴:禁用NetworkManager的无线管理或干脆禁用服务。在Raspberry Pi OS上我会直接
sudo systemctl disable NetworkManager,然后让wpa_supplicant接管无线。 - 如果不想停用NetworkManager,可以用
nmcli device set wlan0 managed no告诉它别管这个接口。但重启后需要再执行,最好写进rc.local或systemd里。 - 在纯命令行环境,我习惯搭配
dhcpcd来获取IP。装好dhcpcd后,只要无线链路完成,它会自动在wlan0上跑DHCP,不需要额外配置。
但要注意一点:wpa_supplicant只是负责链路层,它不会自动通知DHCP客户端"我现在连上了"。所以得靠dhcpcd监听接口事件,或者配合wpa_cli -a脚本触发DHCP请求,否则极可能出现"无线已连接但没IP"的尴尬。
4.3 开机自启后网络没起来的排查
systemd配置没问题但开机依然没网络,我碰过的情况基本是这三类:
- 配置文件路径不对:
ExecStart里写的是/etc/wpa_supplicant/wpa_supplicant-wlan0.conf,但实际配置文件在别的路径,服务启动直接失败。 - 服务起来了但没连上:
systemctl status显示running,可wpa_cli status还停在SCANNING。这种基本是扫描阶段就卡住,可能是rfkill把无线网卡禁用了。 ctrl_interface目录不存在或权限不对:wpa_cli连不上进程。对应方法是确保/run/wpa_supplicant目录存在且属于根用户,或者把配置里的ctrl_interface=DIR=/run/wpa_supplicant GROUP=netdev加上GROUP,允许特定用户组访问。
排查套路很固定:先看systemctl status,然后看journalctl -u wpa_supplicant@wlan0 -f,最后再看wpa_cli status。大多数问题都能在日志里找到答案。
5. 排查无线连接问题的完整套路
5.1 扫描不到AP时的根因定位
无线网卡扫不到任何SSID,通常先怀疑三个地方:驱动、射频开关、国家码。
先确认网卡是否被识别:
iw dev如果没有输出,说明驱动没加载或硬件被禁用。再执行:
rfkill list如果wlan0显示Soft blocked: yes或Hard blocked: yes,用rfkill unblock wifi解除软锁定。硬锁定是笔记本上的物理开关,只能手动拨。
如果网卡正常但只扫到2.4GHz扫不到5GHz,请检查配置文件里的country字段。很多网卡出厂默认在"world regulatory domain",会限制5GHz频段。设置好国家码后要重启wpa_supplicant再扫描,否则不生效。
还有一个容易忽略的点:某些USB无线网卡在虚拟机里直通时驱动有兼容性问题,iw scan结果为空但dmesg里能看到驱动报错。这种要先解决驱动,不要继续在wpa_supplicant层面浪费时间。
5.2 认证失败和四次握手超时的根因
如果扫描能看见AP,但状态一直在AUTHENTICATING或ASSOCIATING反复横跳,最常见的原因排序是:
- 密码错误。PSK输错一个字符就会导致四次握手失败。推荐用
wpa_passphrase生成哈希,避免特殊字符被配置文件解析吃掉。 - AP启用了MAC地址过滤。检查AP管理页面是否把当前网卡的MAC加入了白名单。
- 混合WPA2/WPA3模式下
key_mgmt不匹配。如果路由器的安全模式是WPA2-PSK/WPA3-SAE混合,需要在配置里用key_mgmt=WPA-PSK SAE,而不是只写SAE。 - 驱动对PMF支持不完整。可以把
ieee80211w=1改成ieee80211w=0试试,虽然降低了安全性,但很多老驱动兼容性能好一大截。
日志里如果出现4-Way Handshake failed,基本可以断定是PSK不一致或者AP端的协商策略有问题,而不是射频信号问题。信号弱通常表现为反复重连,而不是连一次就握手失败。
5.3 用调试日志把隐藏信息挖出来
很多东西藏着看不到,是因为你没开调试日志。用这种方式启动:
sudo wpa_supplicant -i wlan0 -c /etc/wpa_supplicant/wpa_supplicant.conf -D nl80211 -dd-d增加调试等级,-dd更详细。日志会爆量,但里面包含了AP协商的细节。几个常见的关键词:
| 日志内容 | 含义 |
|---|---|
CTRL-EVENT-SCAN-RESULTS | 扫描完成,结果已返回 |
Trying to associate with xx:xx:xx:xx:xx:xx | 准备关联某个BSSID |
WPA: 4-Way Handshake completed | 四次握手成功,链路已加密 |
Authentication with xx:xx:xx:xx:xx:xx timed out | 认证超时,AP没有响应 |
WPA: 4-Way Handshake failed - pre-shared key mismatch | PSK不匹配,密码有误 |
CTRL-EVENT-DISCONNECTED bssid=... reason=3 | 被AP断开,reason=3常见于AP主动踢人或链路老化 |
对reason code有疑问时,直接去查802.11规范里的reason code列表。比如reason=2是前一个认证失效,reason=6是类2帧接收失败,reason=15是4次握手超时。手边没有文档时,先在搜索引擎里搜wifi reason code 15即可。
区块好表格:我会放一个常见reason映射表。
| Reason Code | 含义 | 常见场景 |
|---|---|---|
| 2 | 之前的认证不再有效 | AP重启或客户端漫游 |
| 3 | 发送站即将离开AP | 手动断开或AP主动踢人 |
| 6 | 收到非认证帧 | 兼容性问题或干扰 |
| 15 | 四次握手超时 | PSK错误、加密套件不一致 |
| 17 | AP被太多客户端占用 | 性能问题 |
6. 自动化与应急处置:用脚本接管Wi-Fi连接
6.1 用wpa_cli -a做事件驱动脚本
wpa_cli -a /path/to/script是经常被忽略的利器。当wpa_supplicant状态发生变化时,脚本会被调用,并传入参数。最简单的脚本:
#!/bin/sh IFNAME=$1 ACTION=$2 case "$ACTION" in CONNECTED) logger "Wi-Fi connected on $IFNAME" # 这里可以触发dhclient或更新路由 ;; DISCONNECTED) logger "Wi-Fi disconnected on $IFNAME" ;; esac给脚本加执行权限后运行:
sudo wpa_cli -i wlan0 -a /usr/local/bin/wifi-event.sh -B这样当无线从断开变为连接时,脚本会立刻拿到事件。官方的事件除了CONNECTED和DISCONNECTED,还有SCAN_STARTED、SCAN_RESULTS、TERMINATING等。实际工作中,我最常用它来做"连接成功后自动执行DHCP并更新DNS"这类操作。
也可以把它做成systemd服务,让它开机常驻。比如:
[Unit] Description=wpa_cli event handler After=wpa_supplicant@wlan0.service [Service] Type=simple ExecStart=/usr/bin/wpa_cli -i wlan0 -a /usr/local/bin/wifi-event.sh -B Restart=always RestartSec=3 [Install] WantedBy=multi-user.target注意-B参数表示让wpa_cli自身后台运行,但如果你放到systemd服务里,可以不加-B,让systemd直接管理前台进程。
6.2 多网络自动切换与优先级
出差或在多个环境切换的场景,可以在一个配置文件里定义多个network块,并用priority字段决定连接顺序。数值越大优先级越高。
network={ ssid="HomeWiFi" psk="12345678" priority=10 } network={ ssid="CompanyWiFi" key_mgmt=WPA-EAP eap=PEAP identity="user@company" password="pass" priority=20 }wpa_supplicant会在开机或断线时自动按优先级选择可用网络。这个优先级只在同时扫描到多个已知SSID时起作用,如果高优先级网络不在范围内,会自动连次级网络。
隐藏SSID的scan_ssid=1配合priority也常见。不过要注意,隐藏网络每次都要主动探测,耗电和扫描时长都有代价,能不用就别用。
6.3 断线重连的保活技巧
无线网络受干扰、AP重启、物理遮挡影响,偶尔断线很难避免。wpa_supplicant本身有重连机制,但某些驱动在断线后进入"假死"状态,表现为wpa_cli status一直显示DISCONNECTED,就是不重新扫描。
我常用的保活手段是写一个简单的cron任务,每两分钟检查一次:
#!/bin/bash if ! wpa_cli -i wlan0 status | grep -q 'wpa_state=COMPLETED'; then wpa_cli -i wlan0 reconnect sleep 5 if ! wpa_cli -i wlan0 status | grep -q 'wpa_state=COMPLETED'; then systemctl restart wpa_supplicant@wlan0 fi fi第一次先尝试reconnect,如果5秒内还没状态恢复,就重启整个wpa_supplicant服务。重启虽然暴力,但对驱动级卡死是最有效的。
另外,部分USB无线网卡在长时间运行后会出现丢包率飙升的问题。此时可以尝试ip link set wlan0 down && ip link set wlan0 up,配合systemctl restart wpa_supplicant@wlan0一起用。这个操作会重置网卡内部状态,比反复切换工作模式要干净得多。
6.4 实测经验:先小步验证,再上自动化
我个人的习惯是,不管在服务器还是嵌入式板子上,第一遍都先手工跑前台wpa_supplicant,盯着日志确认能稳定连上,然后再写systemd和事件脚本。直接一步到位上自动化的代价是:出问题时日志被systemd吞掉了,排查成本反而高很多。
还有一个小经验:不要把wpa_supplicant和hostapd同时开在同一块物理网卡上,除非你非常清楚自己在做什么并行机制。无线网卡同一时刻要么作为station要么作为AP,nl80211会拒绝这种二象性配置,报错信息往往是"Device or resource busy"。真需要同时做STA和AP,要用支持虚拟接口的网卡,通过iw dev wlan0 interface add wlan1 type __ap创建第二块虚拟接口,然后再一个跑hostapd、一个跑wpa_supplicant。
我在实际使用中最大的体会是:别把wpa_supplicant当成一个"运行完就退出"的命令。它是一个有状态、有事件、需要长期驻留的守护进程。理解了它的运行模型,再配合wpa_cli和systemd,Linux下面的Wi-Fi连接管理就变得非常可控。哪怕你最后用回NetworkManager,再遇到复杂的无线问题时,也知道底层到底发生了什么。