What
A minimal ESP32 web server that responds with “OK” when pinged. Used to detect on-prem power/network status in cloud-failover architectures.
Use case: You have an on-prem server with cloud backup. Ping the ESP32 to know if power is up. If ping fails, start cloud backup.
Components:
- ESP32 board
- WiFi connection (2.4GHz)
- ESP-IDF framework
- HTTP server responding on port 80
Why
- Failover detection: Cloud apps can ping this to know if on-prem is offline.
- Cheap: ESP32 costs $5-10. No need for UPS monitoring APIs or expensive hardware.
- Simple: Single endpoint (
/) returnsOK. No auth, no dependencies. - Low power: ESP32 draws minimal current. Runs indefinitely.
- Reliable: No OS overhead. Boots in seconds after power restoration.
Real scenario: On-prem PostgreSQL + cloud RDS standby. ESP32 sits on same network. Cloud Lambda pings every 30 seconds. If 3 consecutive failures, promote RDS and switch DNS. When ESP32 responds again, you know on-prem is back.
How
Step 1 — Install ESP-IDF
Follow the official setup: ESP32 Development Setup on Ubuntu
Key steps:
mkdir -p ~/esp
cd ~/esp
git clone --recursive https://github.com/espressif/esp-idf.git
cd esp-idf
./install.sh
. ./export.sh
Add to ~/.profile:
. $HOME/esp/esp-idf/export.sh
Step 2 — Create Project Structure
mkdir -p ~/esp/esp32-ping-server/main
cd ~/esp/esp32-ping-server
Step 3 — Root CMakeLists.txt
Create CMakeLists.txt:
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(ping-server)
Step 4 — Main Component CMakeLists.txt
Create main/CMakeLists.txt:
idf_component_register(SRCS "main.c"
INCLUDE_DIRS ".")
Step 5 — Main Application Code
Create main/main.c:
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "lwip/err.h"
#include "lwip/sys.h"
#include "esp_http_server.h"
#define WIFI_SSID "YOUR_WIFI_SSID"
#define WIFI_PASS "YOUR_WIFI_PASSWORD"
#define WIFI_MAXIMUM_RETRY 5
static EventGroupHandle_t s_wifi_event_group;
#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT BIT1
static const char *TAG = "ping-server";
static int s_retry_num = 0;
static void event_handler(void* arg, esp_event_base_t event_base,
int32_t event_id, void* event_data)
{
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_START) {
esp_wifi_connect();
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
if (s_retry_num < WIFI_MAXIMUM_RETRY) {
esp_wifi_connect();
s_retry_num++;
ESP_LOGI(TAG, "Retry to connect to the AP");
} else {
xEventGroupSetBits(s_wifi_event_group, WIFI_FAIL_BIT);
}
ESP_LOGI(TAG,"Connect to the AP fail");
} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
ip_event_got_ip_t* event = (ip_event_got_ip_t*) event_data;
ESP_LOGI(TAG, "Got IP:" IPSTR, IP2STR(&event->ip_info.ip));
s_retry_num = 0;
xEventGroupSetBits(s_wifi_event_group, WIFI_CONNECTED_BIT);
}
}
void wifi_init_sta(void)
{
s_wifi_event_group = xEventGroupCreate();
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_netif_create_default_wifi_sta();
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
esp_event_handler_instance_t instance_any_id;
esp_event_handler_instance_t instance_got_ip;
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
ESP_EVENT_ANY_ID,
&event_handler,
NULL,
&instance_any_id));
ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,
IP_EVENT_STA_GOT_IP,
&event_handler,
NULL,
&instance_got_ip));
wifi_config_t wifi_config = {
.sta = {
.ssid = WIFI_SSID,
.password = WIFI_PASS,
.threshold.authmode = WIFI_AUTH_OPEN,
},
};
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA) );
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &wifi_config) );
ESP_ERROR_CHECK(esp_wifi_start() );
ESP_LOGI(TAG, "wifi_init_sta finished.");
EventBits_t bits = xEventGroupWaitBits(s_wifi_event_group,
WIFI_CONNECTED_BIT | WIFI_FAIL_BIT,
pdFALSE,
pdFALSE,
portMAX_DELAY);
if (bits & WIFI_CONNECTED_BIT) {
ESP_LOGI(TAG, "Connected to AP SSID:%s", WIFI_SSID);
} else if (bits & WIFI_FAIL_BIT) {
ESP_LOGI(TAG, "Failed to connect to SSID:%s", WIFI_SSID);
} else {
ESP_LOGE(TAG, "UNEXPECTED EVENT");
}
}
static esp_err_t root_get_handler(httpd_req_t *req)
{
const char* resp_str = "OK";
httpd_resp_set_status(req, "200 OK");
httpd_resp_set_type(req, "text/plain");
httpd_resp_send(req, resp_str, HTTPD_RESP_USE_STRLEN);
return ESP_OK;
}
static const httpd_uri_t root = {
.uri = "/",
.method = HTTP_GET,
.handler = root_get_handler,
.user_ctx = NULL
};
static httpd_handle_t start_webserver(void)
{
httpd_handle_t server = NULL;
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
config.lru_purge_enable = true;
ESP_LOGI(TAG, "Starting server on port: '%d'", config.server_port);
if (httpd_start(&server, &config) == ESP_OK) {
ESP_LOGI(TAG, "Registering URI handlers");
httpd_register_uri_handler(server, &root);
return server;
}
ESP_LOGI(TAG, "Error starting server!");
return NULL;
}
void app_main(void)
{
ESP_ERROR_CHECK(nvs_flash_init());
ESP_LOGI(TAG, "ESP_WIFI_MODE_STA");
wifi_init_sta();
start_webserver();
ESP_LOGI(TAG, "Web server started. Access via ESP32 IP address on port 80");
}
Update:
- Line 14: Your WiFi SSID
- Line 15: Your WiFi password
Step 6 — Build and Flash
cd ~/esp/esp32-ping-server
idf.py build
idf.py -p /dev/ttyUSB0 flash monitor
Replace /dev/ttyUSB0 with your serial port if different.
Step 7 — Find ESP32 IP Address
In the serial monitor output:
I (xxxx) ping-server: Got IP:192.168.1.9
Step 8 — Reserve IP in Router (Recommended)
Why: Avoid checking logs every time. Hardcode IP in monitoring scripts.
ESP32 MAC Address: Check serial output or router DHCP table.
Router DHCP reservation:
- Access router admin (usually
192.168.1.1) - Navigate to DHCP settings
- Add reservation:
- MAC: Your ESP32 MAC address (check serial output or router DHCP table)
- IP:
192.168.1.9(your choice) - Name: ESP32-PingServer
Benefit: ESP32 always gets same IP. No log checks needed.
Step 9 — Test
curl http://192.168.1.9
Expected: OK with HTTP 200 status.
Or open http://192.168.1.9 in browser.
Integration with Cloud Failover
Example: AWS Lambda Health Check
import urllib.request
import boto3
PING_URL = "http://192.168.1.9"
TIMEOUT = 5
def lambda_handler(event, context):
try:
response = urllib.request.urlopen(PING_URL, timeout=TIMEOUT)
if response.status == 200 and response.read().decode() == "OK":
return {"status": "on-prem-online"}
except:
pass
return {"status": "on-prem-offline"}
Run this Lambda every 30 seconds via EventBridge. Store status in DynamoDB. If 3 consecutive failures, trigger failover workflow.
Example: Monitoring Script (Linux)
#!/bin/bash
PING_URL="http://192.168.1.9"
FAIL_COUNT=0
MAX_FAILS=3
while true; do
if curl -f -s -m 5 "$PING_URL" | grep -q "OK"; then
echo "$(date): On-prem online"
FAIL_COUNT=0
else
FAIL_COUNT=$((FAIL_COUNT + 1))
echo "$(date): Ping failed ($FAIL_COUNT/$MAX_FAILS)"
if [ $FAIL_COUNT -ge $MAX_FAILS ]; then
echo "$(date): On-prem offline. Starting cloud failover..."
# Trigger your failover logic here
# Examples:
# - Update Route53 to point to cloud endpoint
# - Start cloud RDS instance
# - Send SNS alert
fi
fi
sleep 30
done
Troubleshooting
WiFi Connection Fails
Symptoms:
wifi:state: run -> init (0xf00)
Solutions:
- Verify SSID and password (case-sensitive)
- Ensure WiFi is 2.4GHz (ESP32 doesn’t support 5GHz)
- Check router MAC filtering
- Disable AP isolation on router
Brownout Detector Triggered
Error:
E BOD: Brownout detector was triggered
Causes: Insufficient USB power.
Solutions:
- Connect directly to computer USB port (not hub)
- Use USB 3.0 port (blue) - provides more current
- Try different USB cable (data cable, not charge-only)
- Use external 5V power supply
Can’t Access Web Server
Check:
- Verify ESP32 got IP:
idf.py -p /dev/ttyUSB0 monitor - Ping from same network:
ping 192.168.1.9 - Check firewall rules on testing device
- Ensure device is on same WiFi network
Notes
- Power consumption: ESP32 draws ~80mA when WiFi is active. Use 5V/1A supply minimum.
- Boot time: ~2-3 seconds from power-on to server ready.
- Reliability: No filesystem, no complex state. Just WiFi + HTTP server.
- Security: No authentication. Only use on trusted networks or add basic auth if needed.
- Limitations: HTTP only (not HTTPS). Fine for internal monitoring.
- Response time: <50ms typical on local network.
Improvements
Add timestamp endpoint
static esp_err_t time_get_handler(httpd_req_t *req)
{
char resp[64];
snprintf(resp, sizeof(resp), "%lld", esp_timer_get_time() / 1000000);
httpd_resp_send(req, resp, HTTPD_RESP_USE_STRLEN);
return ESP_OK;
}
static const httpd_uri_t time_uri = {
.uri = "/time",
.method = HTTP_GET,
.handler = time_get_handler,
.user_ctx = NULL
};
// In start_webserver():
httpd_register_uri_handler(server, &time_uri);
Add JSON response
#include "cJSON.h"
static esp_err_t status_handler(httpd_req_t *req)
{
cJSON *root = cJSON_CreateObject();
cJSON_AddStringToObject(root, "status", "ok");
cJSON_AddNumberToObject(root, "uptime", esp_timer_get_time() / 1000000);
char *json_str = cJSON_Print(root);
httpd_resp_set_type(req, "application/json");
httpd_resp_send(req, json_str, strlen(json_str));
cJSON_Delete(root);
free(json_str);
return ESP_OK;
}
Use static IP (instead of DHCP)
// After esp_netif_create_default_wifi_sta():
esp_netif_t *netif = esp_netif_get_handle_from_ifkey("WIFI_STA_DEF");
esp_netif_dhcp_status_t status;
ESP_ERROR_CHECK(esp_netif_dhcpc_get_status(netif, &status));
if (status == ESP_NETIF_DHCP_STARTED) {
ESP_ERROR_CHECK(esp_netif_dhcpc_stop(netif));
}
esp_netif_ip_info_t ip_info;
IP4_ADDR(&ip_info.ip, 192, 168, 1, 9);
IP4_ADDR(&ip_info.gw, 192, 168, 1, 1);
IP4_ADDR(&ip_info.netmask, 255, 255, 255, 0);
ESP_ERROR_CHECK(esp_netif_set_ip_info(netif, &ip_info));
Project Files
esp32-ping-server/
├── CMakeLists.txt
└── main/
├── CMakeLists.txt
└── main.c
Useful Commands
# Build
idf.py build
# Flash
idf.py -p /dev/ttyUSB0 flash
# Monitor
idf.py -p /dev/ttyUSB0 monitor
# Flash and monitor
idf.py -p /dev/ttyUSB0 flash monitor
# Clean build
idf.py fullclean
# Erase flash
idf.py -p /dev/ttyUSB0 erase-flash
Summary
You’ve built a minimal ESP32 ping server for on-prem power monitoring:
- ✓ Connects to WiFi
- ✓ Runs HTTP server on port 80
- ✓ Responds with “OK” at
/ - ✓ Uses ~80mA power
- ✓ Boots in 2-3 seconds
- ✓ No dependencies or complex state
Use it in:
- Cloud failover detection
- Network monitoring
- Power restoration alerts
- Simple health checks