The 'Wireless LAN Country' Block: Why Your Pi Won't Connect
If your Raspberry Pi's Wi-Fi interface (wlan0) refuses to scan or connect, the direct cause is almost always a missing or invalid Wireless LAN Country code. By default, the Broadcom Wi-Fi chip (BCM43455 on the Pi 4, BCM4345 on the Pi 5) ships with its radio frequencies locked. To comply with international RF spectrum laws, the Linux kernel requires a valid ISO 3166-1 alpha-2 country code before it will unblock the 2.4GHz and 5GHz channels and apply the correct transmit (TX) power limits.
Without this code, the brcmfmac driver keeps the interface in a soft-blocked state. You will typically see the exact error string wlan0: CTRL-EVENT-SCAN-FAILED ret=-22 retry=1 in your wpa_supplicant logs, or rfkill will report the WLAN as soft-blocked. This guide provides the exact fixes for both modern Raspberry Pi OS Bookworm (NetworkManager) and legacy Bullseye (wpa_supplicant), along with a Python hardware debug script.
Regulatory Domain Limits: Country Codes & TX Power
Setting the wrong country code doesn't just break your connection; it can cause you to transmit on restricted frequencies (like radar bands) or exceed legal power limits, which causes severe interference and packet loss. Here is the data-dense breakdown of common regulatory domains the Pi supports.
| Country Code | Regulatory Body | 2.4GHz Channels Allowed | 5GHz DFS Required? | Max 2.4GHz TX Power |
|---|---|---|---|---|
| US | FCC | 1 - 11 | Yes (UNII-2/2e) | 30 dBm (1000mW) |
| GB | Ofcom | 1 - 13 | Yes | 20 dBm (100mW) |
| DE | BNetzA | 1 - 13 | Yes | 20 dBm (100mW) |
| JP | MIC | 1 - 13 (Ch 14 restricted) | Yes (W52/W53/W56) | 20 dBm (100mW) |
| AU | ACMA | 1 - 13 | Yes | 20 dBm (100mW) |
GB to 'unlock' channels 12 and 13, your router will likely reject the connection if it's configured for US channels, and your Pi's TX power will be artificially capped at 100mW, causing dropped packets at the edge of your workspace.
Parts List & Hardware Debug Mapping
This guide targets the Raspberry Pi 5 (8GB) and Raspberry Pi 4 Model B (4GB/8GB) running Raspberry Pi OS Bookworm (64-bit) or Bullseye. The internal Wi-Fi module does not use external GPIO pins; it communicates via the internal SDIO bus. However, for debugging headless setups, we map an external status LED to a physical GPIO pin.
Required Components
- Compute Board: Raspberry Pi 5 (8GB) or Raspberry Pi 4 Model B (4GB+)
- Power Supply: Official 27W USB-C PD PSU (Pi 5) or 15W USB-C PSU (Pi 4). Do not use phone chargers; brownouts disable the Wi-Fi chip first.
- Storage: SanDisk Extreme 64GB microSD (A2 rating for faster OS boot)
- Debug Hardware: 1x 5mm Red LED, 1x 330Ω resistor, jumper wires
Hardware Interface & GPIO Debug Mapping
| Function | Interface / Pin | Physical Pin # | Notes |
|---|---|---|---|
| Internal Wi-Fi | SDIO0 (wlan0) | N/A (Internal) | Broadcom BCM4345/BCM43455 |
| Debug Status LED (+) | GPIO 16 (BCM) | Pin 36 | Connect via 330Ω resistor to LED Anode |
| Debug Status LED (-) | GND | Pin 34 | Connect to LED Cathode |
| Serial Debug Console | UART0 TX / RX | Pins 8 / 10 | Enable via enable_uart=1 in config.txt |
Step-by-Step Fix: Bookworm vs Bullseye
Raspberry Pi OS transitioned from wpa_supplicant to NetworkManager in the Bookworm release. The method to set the wireless LAN country code depends entirely on your OS version.
Method A: Raspberry Pi OS Bookworm (NetworkManager)
- Open the terminal and check your current regulatory domain:
iw reg get - If it returns
country 00: DFS-UNSET, the Wi-Fi is blocked. Set your country code (e.g., US) usingnmcli:sudo nmcli general permissions(ensure you have rights)sudo nmcli radio wifi on - NetworkManager relies on the system-wide
crdaorwireless-regdb. Set the country code in the NetworkManager configuration:sudo nano /etc/NetworkManager/NetworkManager.conf - Add the following lines under the
[main]section:[main] plugins=keyfile [keyfile] unmanaged-devices=interface-name:eth0
- The most reliable Bookworm method is setting it via the kernel command line or
raspi-config. Run:sudo raspi-config
Navigate to 5 Localisation Options -> L4 WLAN Country, select your country, and reboot.
Method B: Raspberry Pi OS Bullseye (wpa_supplicant)
- Open the
wpa_supplicantconfiguration file:sudo nano /etc/wpa_supplicant/wpa_supplicant.conf - Add the country code at the very top of the file, above the network blocks:
country=US ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev update_config=1
- Save and exit (Ctrl+O, Enter, Ctrl+X).
- Reboot the Pi or restart the service:
sudo systemctl restart wpa_supplicant
Python Wi-Fi Status Monitor (With Error Handling)
When building headless embedded nodes, you need physical feedback if the Wi-Fi drops or the country code resets after an OS update. This Python script targets the Pi 4B / Pi 5, reads the iw regulatory domain, checks rfkill status, and drives the debug LED on GPIO 16.
import subprocess
import time
import RPi.GPIO as GPIO
import sys
# --- Hardware Definitions ---
STATUS_LED_PIN = 16 # BCM 16, Physical Pin 36
CHECK_INTERVAL = 5 # Seconds between polling
# --- GPIO Setup ---
GPIO.setmode(GPIO.BCM)
GPIO.setup(STATUS_LED_PIN, GPIO.OUT)
GPIO.output(STATUS_LED_PIN, GPIO.LOW)
def get_wifi_status():
"""Checks rfkill state and regulatory domain. Returns tuple (is_unblocked, country_code)."""
try:
# Check rfkill for soft/hard blocks
rfkill_out = subprocess.run(['rfkill', 'list', 'wifi'], capture_output=True, text=True, check=True)
if 'Soft blocked: yes' in rfkill_out.stdout or 'Hard blocked: yes' in rfkill_out.stdout:
return False, 'BLOCKED'
# Check regulatory domain via iw
iw_out = subprocess.run(['iw', 'reg', 'get'], capture_output=True, text=True, check=True)
for line in iw_out.stdout.split('\n'):
if 'country' in line and 'DFS' in line:
# Example line: country US: DFS-FCC
code = line.split('country ')[1].split(':')[0].strip()
if code == '00':
return False, 'UNSET'
return True, code
return False, 'UNKNOWN'
except subprocess.CalledProcessError as e:
print(f'Command failed: {e.cmd}', file=sys.stderr)
return False, 'ERROR'
except FileNotFoundError:
print('Error: iw or rfkill not installed. Run: sudo apt install iw rfkill', file=sys.stderr)
return False, 'MISSING_TOOLS'
def main():
print('Starting Wi-Fi Country & Status Monitor...')
try:
while True:
is_unblocked, country = get_wifi_status()
if is_unblocked:
print(f'[OK] WLAN Unblocked. Country Code: {country}')
GPIO.output(STATUS_LED_PIN, GPIO.HIGH) # Solid ON
else:
print(f'[FAIL] WLAN Blocked or Unset. Status: {country}')
# Blink LED to indicate fault
GPIO.output(STATUS_LED_PIN, GPIO.HIGH)
time.sleep(0.2)
GPIO.output(STATUS_LED_PIN, GPIO.LOW)
time.sleep(0.2)
continue # Skip the main sleep to keep blinking fast
time.sleep(CHECK_INTERVAL)
except KeyboardInterrupt:
print('\nMonitor stopped by user.')
finally:
GPIO.cleanup()
if __name__ == '__main__':
main()
Debugging: The First Three Things to Check When It Fails
If you have set the country code and the interface is still down, follow this ranked decision path. These are the most common failure modes on the bench.
1. Check for Power Supply Brownouts (Most Likely on Pi 5)
The Symptom: dmesg shows brcmfmac mmc1:0001:1 wlan0: Failed to set TX power, error -22 or the Wi-Fi chip completely disappears from ip link.
The Fix: The Broadcom Wi-Fi chip draws peak current during TX bursts. If your USB-C power supply cannot maintain 5V under load, the Pi's firmware will throttle or disable the Wi-Fi module to save the CPU. Verify you are using the official 27W PD supply for the Pi 5. Check for the lightning bolt icon on the display or run vcgencmd get_throttled (if 0x0, power is clean).
2. Verify the Exact Error String in wpa_supplicant
The Symptom: You see wlan0: CTRL-EVENT-SCAN-FAILED ret=-22 retry=1 repeating in journalctl -u wpa_supplicant.
The Fix: Error -22 is EINVAL (Invalid Argument). This almost always means the country code in your config file is either missing, lowercase (it must be uppercase, e.g., US not us), or not recognized by the wireless-regdb package. Run sudo apt update && sudo apt install wireless-regdb to ensure your local regulatory database is up to date, then reboot.
3. Check for MAC Address Randomization Conflicts
The Symptom: The Pi connects to the router, but gets assigned a new IP address on every reboot, or the router's MAC filtering blocks it.
The Fix: NetworkManager randomizes MAC addresses by default during scanning. If your router uses MAC whitelisting, this will fail. Disable it by editing /etc/NetworkManager/NetworkManager.conf and adding:
[device] wifi.scan-rand-mac-address=no [connection] wifi.cloned-mac-address=preserveThen run
sudo systemctl restart NetworkManager.
Extending and Simplifying the Build
How to Simplify: If you are deploying multiple Pis and want to avoid SSH-ing into each one to set the wireless LAN country, use the Raspberry Pi Imager on your desktop. Under the 'OS Customisation' menu (the gear icon), you can pre-configure the SSID, password, and the exact Wireless LAN Country code. The Imager injects a firmware.cfg or NetworkManager profile directly onto the boot partition before the first boot, bypassing the block entirely.
How to Extend: For remote industrial or agricultural IoT deployments where Wi-Fi is unreliable, extend this build by adding a Waveshare SIM7600G-H 4G HAT. You can modify the Python script above to trigger a fallback: if wlan0 remains blocked or disconnected for more than 5 minutes, the script uses nmcli to bring up the ppp0 cellular interface instead. Always ensure your cellular HAT has its own dedicated power regulator, as 4G transmit bursts can pull 2A peak and will brownout the Pi's main 5V rail if sharing the same bus.
For deeper reading on NetworkManager configurations and regulatory domains, refer to the Raspberry Pi OS Configuration Documentation and the official NetworkManager nmcli reference.






