Want to analyze your ESP32 WiFi performance? Measure available Wi-Fi networks and signal strength, then benchmark real-world TCP throughput. This tutorial provides both network scanning and download speed testing for your ESP32 projects.
Part 1: Wi-Fi signal strength
The first part of this tutorial is a basic Wi-Fi network scan, to measure available Wi-Fi networks and signal strength. Signal strength is measured by the RSSI (Received Signal Strength Indicator), which is the power level of a received Wi-Fi signal in dBm (decibels relative to 1 milliwatt).

The code below scans both 2.4 GHz and 5 GHz networks, displaying channel numbers and RSSI for each found network. The ESP32 then connects to your WiFi and continuously monitors RSSI—ideal for testing signal range or verifying antenna reception quality (gain)
//
// ESP32 WiFi test program
// https://tutoduino.fr/
//
#include <WiFi.h> // WiFi library for ESP32
#include <esp_wifi.h> // ESP-IDF WiFi control functions
#include "secret.h" // Contains WIFI_SSID and WIFI_PASSWORD definitions
void setup() {
Serial.begin(115200); // Initialize serial communication
delay(1000); // Small startup delay
Serial.println("ESP32 WiFi Test - Tutoduino");
scanWifi(); // Scan nearby 2.4GHz and 5GHz networks
connectToWifi(); // Try to connect to your WiFi network
}
void loop() {
// Check if the device is still connected to the WiFi network
if (WiFi.status() == WL_CONNECTED) {
int ch = WiFi.channel(); // Get current WiFi channel
bool is5GHz = (ch >= 36); // Channels 36+ correspond to 5GHz
// Print IP address and signal info
Serial.printf("Connected with IP: %s\n", WiFi.localIP().toString().c_str());
Serial.printf("Channel: %d (%s GHz) | RSSI: %d dBm\n",
ch, is5GHz ? "5" : "2.4", WiFi.RSSI());
} else {
// If disconnected, try to reconnect automatically
Serial.println("Disconnected, trying to reconnect...");
WiFi.reconnect();
}
delay(5000); // Wait before repeating the loop
}
void scanWifi() {
int n;
esp_err_t err;
WiFi.mode(WIFI_STA); // Set WiFi to station (client) mode
// ----------- 5 GHz scan -----------
Serial.println("Scanning 5 GHz WiFi networks...");
err = esp_wifi_set_band_mode(WIFI_BAND_MODE_5G_ONLY); // Restrict to 5 GHz
if (err != ESP_OK) {
Serial.println("Set 5G band failed!");
}
n = WiFi.scanNetworks(); // Perform the 5GHz scan
for (int i = 0; i < n; ++i) {
int ch = WiFi.channel(i);
if (ch >= 36) {
Serial.printf("[%d] %s (5G ch%d, %d dBm)\n",
i + 1, WiFi.SSID(i).c_str(), ch, WiFi.RSSI(i));
} else {
Serial.printf("[%d] %s (2.4G ch%d, %d dBm)\n",
i + 1, WiFi.SSID(i).c_str(), ch, WiFi.RSSI(i));
}
}
WiFi.scanDelete();
// ----------- 2.4 GHz scan -----------
Serial.println("Scanning 2.4 GHz WiFi networks...");
err = esp_wifi_set_band_mode(WIFI_BAND_MODE_2G_ONLY); // Restrict to 2.4 GHz
if (err != ESP_OK) {
Serial.println("Set 2G band failed!");
}
n = WiFi.scanNetworks(); // Perform network scan
for (int i = 0; i < n; ++i) {
int ch = WiFi.channel(i);
// Display basic info for each found network
if (ch >= 36) {
Serial.printf("[%d] %s (5G ch%d, %d dBm)\n",
i + 1, WiFi.SSID(i).c_str(), ch, WiFi.RSSI(i));
} else {
Serial.printf("[%d] %s (2.4G ch%d, %d dBm)\n",
i + 1, WiFi.SSID(i).c_str(), ch, WiFi.RSSI(i));
}
}
WiFi.scanDelete(); // Free memory from scan results
}
// Function to connect the ESP32 to the provided WiFi network
void connectToWifi() {
Serial.printf("Connecting to %s...\n", WIFI_SSID);
WiFi.mode(WIFI_STA); // Set WiFi to station (client) mode
WiFi.begin(WIFI_SSID, WIFI_PASSWORD); // Start connection with credentials
int attempts = 0;
// Wait up to 20 attempts (about 10 seconds total)
while (WiFi.status() != WL_CONNECTED && attempts < 20) {
delay(500);
Serial.print(".");
attempts++;
}
// Report connection result
Serial.println(WiFi.status() == WL_CONNECTED ? "\nConnected!" : "\nFailed to connect");
}Example of output on Arduino IDE Serial Monitor showing 2.4G and 5G Wi-Fi networks. The Seeed Studio XIAO ESP32-C6 then connect to my Wi-Fi and display channel and RSSI:
17:30:34.782 -> ESP32 WiFi Test - Tutoduino
17:30:35.393 -> Scanning 5 GHz WiFi networks...
17:30:42.356 -> [1] MyWifi_5GHz (5G ch112, -68 dBm)
17:30:42.356 -> Scanning 2.4 GHz WiFi networks...
17:30:45.372 -> [1] MyWifi (2.4G ch11, -70 dBm)
17:30:45.372 -> [2] Freebox-0265C0 (2.4G ch6, -84 dBm)
17:30:45.372 -> [3] Maison Cabanon (2.4G ch2, -88 dBm)
17:30:45.372 -> [4] Jiggabox (2.4G ch6, -88 dBm)
17:30:45.372 -> [5] Jiggabox-invité (2.4G ch6, -89 dBm)
17:30:45.372 -> [6] Livebox-20B0 (2.4G ch6, -89 dBm)
17:30:45.372 -> [7] Livebox-30E0 (2.4G ch1, -91 dBm)
17:30:45.372 -> [8] WIFI7-YOU (2.4G ch1, -93 dBm)
17:30:45.372 -> [9] boxjack02 (2.4G ch2, -95 dBm)
17:30:45.372 -> [10] wifiRoom (2.4G ch6, -95 dBm)
17:30:45.372 -> Connecting to MyWifi...
17:30:45.885 -> ...
17:30:46.880 -> Connected!
17:30:46.880 -> Connected with IP: 192.168.1.31
17:30:46.880 -> Channel: 11 (2.4 GHz) | RSSI: -68 dBm
17:30:51.890 -> Connected with IP: 192.168.1.31
17:30:51.890 -> Channel: 11 (2.4 GHz) | RSSI: -69 dBm
17:30:56.875 -> Connected with IP: 192.168.1.31
17:30:56.875 -> Channel: 11 (2.4 GHz) | RSSI: -59 dBm
17:31:01.875 -> Connected with IP: 192.168.1.31
17:31:01.875 -> Channel: 11 (2.4 GHz) | RSSI: -59 dBmIn this example, you can see the effect of changing the antenna (at 17:30:56), which improved the RSSI from -69 dBm to -59 dBm.
Part 2: Wi-Fi throughput
In this test, we measure Wi-Fi throughput on ESP32 by transferring a 20 MB file from a local Linux web server to your ESP32.

Before going further, I remind you some concepts for the units of information:
- Bit (b) → most basic unit of information (0 or 1)
- Kilobit (kb ou kbit) → 1 kbit = 1 000 b
- Megabit (Mb ou Mbit) → 1 Mbit = 1 000 000 b
- Byte (B) → 1 B = 8 b
- Kilobyte (kB) → 1 kB = 1 000 B
- Kibibyte (KiB) → 1 KiB = 1 024 B
- Megabyte (MB) → 1 MB = 1 000 x 1 000 B = 1 000 000 B
- Mebibyte (MiB) → 1 MiB = 1 024 x 1 024 B = 1 048 576 B
1. Server Setup (Linux)
Create 20 MiB (Mebibyte) random data file with this command on the local Linux server:
dd count=20480 bs=1024 if=/dev/random of=/tmp/data20 480 blocks × 1 024 bytes/block = 20,971,520 bytes = 20 × 1 048 576 bytes = 20 MiB = ~21 MB.
2. HTTP server
Start a simple Python HTTP server with this command executed on the local Linux server:
python3 -m http.server 8080 -d /tmpYou can test that local web server is operational with this command executed on the local Linux server:
tutoduino@F15:~$ curl -o /dev/null http://127.0.0.1:8080/data
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 20.0M 100 20.0M 0 0 2428M 0 --:--:-- --:--:-- --:--:-- 2500M
3. ESP32 Client
Compile and upload this code to your ESP32. It performs HTTP GET requests from ESP32 to download the 21 MB file from the local web server and calculates Wi-Fi throughput in kb/s.
//
// ESP32 Wi-Fi throughput test program
//
// https://tutoduino.fr/
//
#include <WiFi.h>
#include <esp_wifi.h> // ESP-IDF WiFi control functions
#include "secret.h" // Contains WIFI_SSID and WIFI_PASSWORD definitions
// Global TCP client object
WiFiClient client;
void setup() {
esp_err_t err;
// Start serial console for debug output
Serial.begin(115200);
delay(1000);
Serial.println("ESP32 WiFi Throughput Test - Tutoduino");
// Connect to WiFi access point
Serial.println("Connecting to WiFi...");
WiFi.mode(WIFI_STA);
// Restrict test to 2.4 GHz WiFi band
err = esp_wifi_set_band_mode(WIFI_BAND_MODE_2G_ONLY);
if (err != ESP_OK) {
Serial.println("Set 2G band failed!");
}
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
// Wait until connected (blocking loop)
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println();
Serial.println("WiFi connected!");
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
}
void loop() {
// Only run the test if WiFi is still connected
if (WiFi.status() == WL_CONNECTED) {
const char* host = "192.168.1.22"; // Test server IP, change with your server IP!
const int port = 8080; // Test server port
const char* path = "/data"; // Resource to download
Serial.println("Starting throughput test...");
unsigned long startTime = millis();
int totalBytes = 0;
// Open TCP connection to the server
if (client.connect(host, port)) {
Serial.println("Connected to server");
// Send a minimal HTTP GET request
client.print(String("GET ") + path + " HTTP/1.1\r\n" + "Host: " + host + "\r\n" + "Connection: close\r\n\r\n");
// Wait for HTTP response headers to arrive
while (client.connected() && !client.available()) {
delay(10);
}
// Skip HTTP headers (read until end of header line '\n')
while (client.available() && client.read() != '\n') {
// just discard header data
}
// Read HTTP body data and count bytes
unsigned long lastActivity = millis();
while (client.connected() || client.available()) {
if (client.available()) {
int bytesAvailable = client.available();
totalBytes += bytesAvailable;
// Read and discard data (we only count bytes)
for (int i = 0; i < bytesAvailable; i++) {
client.read();
}
// Update last activity time
lastActivity = millis();
}
// Timeout after 2 seconds without any new data
if (millis() - lastActivity > 2000) {
break;
}
}
// Close TCP connection
client.stop();
// Compute duration in seconds
unsigned long endTime = millis();
unsigned long duration = (endTime - startTime) / 1000; // seconds
// Avoid division by zero
if (duration == 0) duration = 1;
// Compute throughput in megabit/s : 1 byte = 8 bits ; 1 Mbit = 1000 bits * 1000 bits
float speedMbitps = (totalBytes * 8.0 / (1000.0 * 1000.0)) / duration;
Serial.print("Downloaded size: ");
Serial.print(totalBytes);
Serial.println(" bytes");
Serial.print("Duration: ");
Serial.print(duration);
Serial.println(" s");
Serial.print("Estimated throughput: ");
Serial.print(speedMbitps, 2);
Serial.println(" Mbit/s");
} else {
Serial.println("Failed to connect to server");
}
} else {
Serial.println("WiFi disconnected");
}
// Wait 10 seconds before the next test
delay(10000);
}Replace host IP address (192.168.1.22) in the code with your Linux machine’s IP. Store your WIFI_SSID and WIFI_PASSWORD in a secret.h file.
Example of output on Arduino IDE Serial Monitor showing 7.29 Mbit/s throughput on ESP32:
17:03:56.822 -> ESP32 WiFi Throughput Test - Tutoduino
17:03:56.822 -> Connecting to WiFi...
17:03:58.441 -> ...
17:03:58.959 -> WiFi connected!
17:03:58.959 -> IP address: 192.168.1.31
17:03:58.959 -> Starting throughput test...
17:03:59.701 -> Connected to server
17:04:22.388 -> Downloaded size: 20971709 bytes
17:04:22.388 -> Duration: 23 s
17:04:22.388 -> Estimated throughput: 7.29 Mbit/sMeasurement consistency check and bottleneck analysis
To confirm the validity of the measurements, I carried out two additional tests with a larger file size of 105 MB.
Check performance of Python HTTP server
To check that Python HTTP server is not a bootleneck, I performed a local test on the Linux server.
steph@F15:~$ curl -o /dev/null http://127.0.0.1:8080/data
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 100M 100 100M 0 0 2164M 0 --:--:-- --:--:-- --:--:-- 2173M
Curl uses the binary prefix and bytes units, so M means MiB/s and not MB/s.
This output shows that throughput is very high with 2173 MiB/s = ~18 228 Mbit/s.
So the Python HTTP server is clearly not limiting .
Check network performance
I realized the same test on another PC in my local network on a larger file to have accurate value.
tutoduino@my-desktop:~$ curl -o /dev/null http://192.168.1.22:8080/data
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 100M 100 100M 0 0 12.4M 0 0:00:08 0:00:08 --:--:-- 12.8M
Result shows a throughput of 12.8 MiB/s = ~107.4 Mbit/s
It is consistent with the Wi-Fi card bitrate of my Linux server, as you can see below:

So the Wi-Fi network and the Wi-Fi connectivity on the Linux server is also not the limiting factor.
Conclusion
The measurements method presented above is accurate, confirming the ESP32’s throughput at approximately 7.63 Mbit/s.
This result aligns with typical ESP32 Wi-Fi performance in embedded IoT setups like ESPHome, where 7-8 Mbit/s is realistic for localhost transfers under optimal conditions.
I hope this tutorial was helpful! Please share your feedback by rating with the stars below or leaving a comment.
