Raspberry Pi Automatic Wi-Fi ↔ Hotspot Switching (NetworkManager)
Goal
Boot
- manual flag on → hotspot
- manual flag off:
- known Wi-Fi available → Wi-Fi
- otherwise → hotspot
Hotspot active
- manual flag on → stay on hotspot
- manual flag off:
- known Wi-Fi appears → switch to Wi-Fi
Wi-Fi active
- stay on Wi-Fi
- if Wi-Fi disappears:
- try known Wi-Fi again
- if none available → hotspot
Design
There are two connection classes on wlan0:
- one or more normal Wi-Fi client profiles
- one hotspot profile called
device-hotspot
Rules:
- Wi-Fi profiles have autoconnect enabled.
- Hotspot profile has autoconnect disabled.
- If Wi-Fi disconnects and hotspot was not manually requested, the dispatcher starts the hotspot.
- If hotspot is active and the user clears manual mode, Wi-Fi becomes normal again.
- A persistent flag file records whether hotspot is manual or automatic.
Step 1 — Install and enable NetworkManager
Install:
sudo apt update
sudo apt install -y network-manager
If dhcpcd is managing the interface on your Pi image, disable it so NetworkManager is the single authority:
sudo systemctl disable --now dhcpcd
Enable NetworkManager:
sudo systemctl enable --now NetworkManager
Step 2 — Create the hotspot profile
Create the hotspot once:
sudo nmcli dev wifi hotspot ifname wlan0 ssid <ssid> password <MyStrongPassword> autoconnect no
That creates a normal NetworkManager connection profile named ‘Hotspot’. Rename it if needed:
sudo nmcli connection modify Hotspot connection.id device-hotspot
Step 3 — Add known Wi-Fi networks and make them preferred
Add Wi-Fi networks normally:
sudo nmcli dev wifi rescan
sudo nmcli device wifi list
sudo nmcli dev wifi connect "MyWiFi" password "MyPassword"
For each normal Wi-Fi profile, keep autoconnect enabled and assign a positive priority:
sudo nmcli connection modify "MyWiFi" connection.autoconnect yes
sudo nmcli connection modify "MyWiFi" connection.autoconnect-priority 100
If you have multiple normal Wi-Fi profiles, give the more important one a higher number. NetworkManager prefers higher autoconnect-priority values when several profiles are candidates.
List profiles:
nmcli connection show
Step 4 — Create a network selection script
First create the persistent state directory:
sudo mkdir -p /var/lib/pi-network-mode
Now create the network management script:
sudo nano /usr/local/bin/wifi-logic.sh
Paste this code inside of it:
#!/bin/bash
# 1. LOCKING (Keep this to prevent the "Trigger Storm")
LOCKFILE="/tmp/wifi-logic.lock"
if [ -e ${LOCKFILE} ] && kill -0 $(cat ${LOCKFILE}) 2>/dev/null; then
exit 0
fi
echo $$ > ${LOCKFILE}
trap 'rm -f ${LOCKFILE}' EXIT
# 2. LOGGING & CONFIG
FLAG="/var/lib/pi-network-mode/manual-hotspot"
HS_CON="device-hotspot"
log_msg() {
logger -t WiFi-Logic "$1"
}
WLAN_STATE=$(nmcli -t -f DEVICE,STATE dev | grep "^wlan0:" | cut -d: -f2)
CURRENT_CON=$(nmcli -t -f NAME con show --active | head -n 1)
log_msg "State: [$WLAN_STATE] Active: [$CURRENT_CON]"
# 3. MANUAL FLAG
if [ -f "$FLAG" ]; then
if [ "$CURRENT_CON" != "$HS_CON" ]; then
log_msg "Manual Flag ON. Forcing Hotspot."
nmcli con up "$HS_CON"
fi
exit 0
fi
# 4. SMART EXIT
if [[ "$WLAN_STATE" == "connected" ]] && [[ "$CURRENT_CON" != "$HS_CON" ]]; then
exit 0
fi
# 5. SCANNING
# We use a forced rescan. If the radio is busy, this might fail, so we catch it.
VISIBLE_SSIDS=$(nmcli -t -f SSID dev wifi list --rescan yes 2>/dev/null | sed 's/\\//g')
SAVED_PROFILES=$(nmcli -t -f NAME,TYPE connection show | awk -F: '$2=="802-11-wireless" && $1!="device-hotspot" {print $1}')
MATCH_PROFILE=""
MATCH_SSID=""
while read -r PROFILE; do
[ -z "$PROFILE" ] && continue
SSID_OF_PROFILE=$(nmcli -g 802-11-wireless.ssid connection show "$PROFILE" 2>/dev/null)
if echo "$VISIBLE_SSIDS" | grep -Fqx "$SSID_OF_PROFILE"; then
MATCH_PROFILE="$PROFILE"
MATCH_SSID="$SSID_OF_PROFILE"
break
fi
done <<< "$SAVED_PROFILES"
# 6. EXECUTION
if [ -z "$MATCH_PROFILE" ]; then
# CASE: NO WIFI FOUND -> START HOTSPOT
if [ "$CURRENT_CON" != "$HS_CON" ]; then
log_msg "No Wi-Fi visible. Activating Hotspot."
nmcli con down id "$CURRENT_CON" 2>/dev/null
sleep 3
nmcli con up "$HS_CON"
fi
else
# CASE: WIFI FOUND -> SWITCH BACK
if [ "$CURRENT_CON" != "$MATCH_PROFILE" ]; then
log_msg "Known Wi-Fi [$MATCH_SSID] detected. Performing Clean Swap..."
# 1. Kill Hotspot
nmcli con down "$HS_CON" 2>/dev/null
# 2. HARD RESET wlan0 (Clears the "occupied" state)
nmcli device disconnect wlan0 2>/dev/null
# 3. Give the hardware a significant breather (Zero 2 W needs this)
sleep 5
# 4. Try to connect. We use the profile name here.
if nmcli con up "$MATCH_PROFILE"; then
log_msg "Successfully reconnected to [$MATCH_SSID]."
else
log_msg "Connection to [$MATCH_SSID] failed. Reverting to Hotspot."
sleep 2
nmcli con up "$HS_CON"
fi
fi
fi
And make it executable:
sudo chmod +x /usr/local/bin/wifi-logic.sh
Step 5 — Create system services
Network Event Trigger (Dispatcher)
This will run the script when network state changes, i.e. when the WiFi network disappears.
sudo nano /etc/NetworkManager/dispatcher.d/99-wifi-failover
Paste:
#!/bin/bash
INTERFACE=$1
if [ "$INTERFACE" = "wlan0" ]; then
# Trigger the brain on any wlan0 change
/usr/local/bin/wifi-logic.sh &
fi
Make it executable:
sudo chmod +x /etc/NetworkManager/dispatcher.d/99-wifi-failover
The Heartbeat (Polling)
This ensures the Pi scans for home Wi-Fi while the Hotspot is active.
sudo nano /etc/systemd/system/wifi-heartbeat.service
Paste:
[Unit]
Description=WiFi Logic Executor
After=network.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/wifi-logic.sh
Make it run every two minutes:
sudo nano /etc/systemd/system/wifi-heartbeat.timer
Paste:
[Unit]
Description=Run WiFi Logic every 2 minutes
[Timer]
OnBootSec=1min
OnUnitActiveSec=2min
Unit=wifi-heartbeat.service
[Install]
WantedBy=timers.target
Boot Initialization
Run the script on startup:
sudo nano /etc/systemd/system/wifi-init.service
Paste inside this file:
[Unit]
Description=Initialize WiFi Mode on Boot
After=NetworkManager.service
Wants=NetworkManager.service
[Service]
Type=oneshot
ExecStartPre=/bin/sleep 10
ExecStart=/usr/local/bin/wifi-logic.sh
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
Activate systemd files
sudo systemctl daemon-reload
sudo systemctl enable --now wifi-heartbeat.timer
sudo systemctl enable wifi-init.service
How it works
Verification
To ensure your Raspberry Pi Zero 2 W is currently “listening” to all the triggers, you can run this combined command. It will check the Timer, the Boot Service, and the Dispatcher’s presence in one go.
echo "--- Timers ---"; systemctl list-timers wifi-heartbeat.timer; \
echo "--- Boot Service ---"; systemctl is-enabled wifi-init.service; \
echo "--- Dispatcher ---"; [ -x /etc/NetworkManager/dispatcher.d/99-wifi-failover ] && echo "Executable" || echo "NOT EXECUTABLE"
View script logs (Follow script’s “thoughts” in real-time):
journalctl -t WiFi-Logic -f
[Option -f is used to keep the log open, and see the results in real-time]
View network manager logs (See the last 50 lines of the system’s actual network manager. This is where you’ll see “Invalid Password” or “Association Failed” errors):
journalctl -u NetworkManager -n 50
Turning Hotsopt On and Off
To manually turn Hotspot on:
sudo touch /var/lib/pi-network-mode/manual-hotspot
sudo /usr/local/bin/wifi-logic.sh
To manually turn Hotspot off (and connect to WiFi if in range):
sudo rm /var/lib/pi-network-mode/manual-hotspot
sudo /usr/local/bin/wifi-logic.sh
Useful commands
- Get the list of available networks:
sudo nmcli dev wifi rescan
sudo nmcli device wifi list
- Active connections:
sudo nmcli connection show --active
- Device state (See if
wlan0isconnected,disconnected, orconnecting):
sudo nmcli device status
- Scan networks:
sudo nmcli device wifi list
- If you want to see everything about one specific saved network (like your home Wi-Fi), run:
nmcli connection show "<connection name>"
- This command provides a table showing the Profile Name, the SSID it looks for, and its Priority:
printf "%-24s %-24s %-6s %-5s\n" "NAME" "SSID" "AUTO" "PRIO"
printf "%-24s %-24s %-6s %-5s\n" "------------------------" "------------------------" "------" "-----"
nmcli -t -f NAME,TYPE connection show | awk -F: '$2=="802-11-wireless"{print $1}' | while read -r c; do
ssid=$(nmcli -g 802-11-wireless.ssid connection show "$c")
auto=$(nmcli -g connection.autoconnect connection show "$c")
prio=$(nmcli -g connection.autoconnect-priority connection show "$c")
printf "%-24s %-24s %-6s %-5s\n" "$c" "$ssid" "$auto" "$prio"
done
Behavior summary
| Scenario | Result |
|---|---|
| Wi-Fi available | connect normally |
| Wi-Fi unavailable | hotspot starts |
| Wi-Fi returns | hotspot stops |
| hotspot forced by user | hotspot persists |