This guide shows how to build a local web server with the ESP32 programmed with ESP-IDF that serves a simple HTML web page that is password-protected with basic HTTP authentication. This web page can then be accessed on your local network, but it requires entering the correct username and password. If you logout, you can only access again if you enter the right credentials.

Using Arduino IDE? Follow this tutorial instead: Building an ESP32 Web Server: The Complete Guide for Beginners (Arduino IDE).
Prerequisites
Before following this guide, you need to install the ESP-IDF extension on VS Code IDE (Microsoft Visual Studio Code). Follow the next guide to install it, if you haven’t already:
You will also need an ESP32 development board model of your choice.
Security Concerns
This project is meant to be used on your local network to protect from anyone just typing the ESP IP address and accessing the web server (like an unauthorized family member or friend).
If your network is properly secured, running an HTTP server with basic authentication is enough for most applications. If someone has managed to hack your network, it doesn’t matter if you use HTTP or HTTPS. The hacker can bypass HTTPS and get your user/pass.
Project Overview
Let’s take a quick look at the features of the project we’ll build.

- In this tutorial, you’ll learn how to password-protect your web server;
- When you try to access the web server page on the ESP32 IP address, a window pops up asking for a username and password;
- To get access to the web server page, you need to enter the right username and password (defined in the ESP32 sketch);
- There’s a logout button on the web server. If you click the logout button, you’ll be redirected to a logout page;
- You can only access the web server again if you login with the right credentials;
- If you try to access the web server from a different device (on the local network), you also need to login with the right credentials (even if you have a successful login on another device);
- The authentication is not encrypted.
Creating an ESP-IDF Template App Project for the ESP32
The ESP-IDF extension provides an easy way to create a project from scratch with all the required files and configurations generated automatically.
To create a new ESP-IDF project on VS Code, follow these steps:
- Open the ESP-IDF Espressif extension
- Expand the “Advanced” menu
- Click the “New Project Wizard” option
- Choose the “Use ESP-IDF v5.4.1” to select the framework version

A new window opens, you need to fill in these fields:
- Project Name: type the desired project name;
- Enter Project Directory: click the folder icon and select the target folder to save all your project files. You can use any directory. Note: do NOT use a Google Drive / One Drive / Dropbox folder, because it will write/create many files during the building process—if it’s on a cloud folder, this process might be extremely slow;
- ESP-IDF Target: select the target device chip, I’m using an ESP32 with the esp32s3 chip;
- ESP-IDF Board: for the esp32s3 chip, I also need to select the configuration: ESP32-S chip (via builtin USB-JTAG);
- Serial Port: while having your ESP32 board connected to your computer, select the correct COM port number that refers to your ESP32;
- Choose Template: click the blue button to create a new project using a template.

In the menu, select the “ESP-IDF Templates” sample project and press the “Create project using template sample project” button.

Opening the ESP-IDF Project on VS Code
After a few seconds, a notification will appear in a new window in VS Code. You can click “Open Project” to open the newly created ESP-IDF sample project template.

IMPORTANT: if you didn’t see the notification that allows you to automatically open the ESP-IDF project on VS Code, you can easily do it by following these instructions:
Go to File > Open Folder…

Browse on your computer for the esp-idf-project folder (your project folder name that you’ve previously defined) and “Select Folder“.

That’s it! Your new ESP-IDF project template has been successfully created and opened.
ESP-IDF generates many files, folders, and subfolders for your project. For this guide, I recommend keeping all the default files unchanged; we will only modify the main.c file.
The example code will be written in the main.c file. To open it, follow these instructions:
- Open the project explorer by clicking the first icon on the left sidebar.
- Select your project folder name, in my case, it’s “ESP-IDF-PROJECT“.
- Expand the “main” folder.
- Click the “main.c” file.
- The default main.c template file loads in the code window.

Create the Wi-Fi Config Options File – Kconfig.projbuild
The Kconfig.projbuild file in ESP-IDF is a configuration file placed in a project’s main folder to define custom options in the SDK Configuration Editor (menuconfig tool). This allows users to set specific settings, like strings or values that will be used during build configuration.
To create the Kconfig.projbuild file, go to the File Explorer left sidebar and right-click on top of the main folder. Then, select the “New File…” option:

Name the new file exactly as follows: Kconfig.projbuild and copy the content (shown after the following screenshot) to your newly created file.

menu "Wi-Fi Configuration"
config ESP_WIFI_SSID
string "WiFi SSID"
help
SSID (network name) you want to connect to.
config ESP_WIFI_PASSWORD
string "WiFi Password"
help
Password of the Wi-Fi network you want to connect to.
endmenu
Save the file content. This will create a new menu called “Wi-Fi Configuration” in the SDK Configuration Editor where you can type you SSID and Password, so the ESP can connect to your network.
Having the Kconfig.projbuild file prepared, click the gear icon to open the SDK Configuration Editor (menuconfig).

Now, instead of typing your network credentials inside the main.c file, you can configure them in the menuconfig tool. Follow these next steps:
- Search for “wifi“;
- Type your SSID and Password in the fields;
- Press the “Save” button to apply the changes.

Code: ESP32 Web Server with HTTP Authentication using ESP-IDF
Copy the following code to the main.c file. This code serves a basic HTML web page hosted on an ESP32 that is password-protected with basic HTTP authentication.
/*
Rui Santos & Sara Santos - Random Nerd Tutorials
https://RandomNerdTutorials.com/esp-idf-esp32-web-server-http-authentication/
*/
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include <esp_log.h>
#include <esp_system.h>
#include <esp_wifi.h>
#include <esp_event.h>
#include <esp_netif.h>
#include <esp_http_server.h>
#include <esp_tls.h>
#include <esp_check.h>
#include <nvs_flash.h>
#include "esp_tls_crypto.h"
#include "sdkconfig.h"
#define MY_ESP_WIFI_SSID CONFIG_ESP_WIFI_SSID
#define MY_ESP_WIFI_PASS CONFIG_ESP_WIFI_PASSWORD
#define BASIC_AUTH_USER "admin"
#define BASIC_AUTH_PASS "password"
typedef struct {
char *username;
char *password;
} basic_auth_info_t;
#define HTTPD_401 "401 UNAUTHORIZED" // HTTP Response 401
static const char *TAG = "web_server";
// HTML web page to serve the root /
static const char *root_page =
"<!DOCTYPE html>"
"<html>"
"<head>"
"<title>ESP-IDF: ESP32 Web Server</title>"
"<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">"
"</head>"
"<body>"
"<h1>ESP-IDF: ESP32 Web Server</h1>"
"<p>Hello from ESP32!</p>"
"<button onclick=\"window.location.href = '/logout';\">Logout</button>"
"</body>"
"</html>";
// HTML web page to serve the /logout (redirects the user to the root page after logout)
static const char *logout_page =
"<!DOCTYPE html>"
"<html>"
"<head>"
"<title>Logout</title>"
"<script>"
"function forceLogout(){"
" var xhr = new XMLHttpRequest();"
" xhr.open('GET', '/', true, 'logout', 'logout');"
" xhr.send();"
" setTimeout(function(){ window.location.href='/'; }, 500);"
"}"
"</script>"
"</head>"
"<body onload='forceLogout()'>"
"<h2>Logging out...</h2>"
"</body>"
"</html>";
// Send 401 + WWW-Authenticate header
static esp_err_t send_401_response(httpd_req_t *req)
{
httpd_resp_set_status(req, HTTPD_401);
httpd_resp_set_type(req, "text/html");
httpd_resp_set_hdr(req, "Connection", "keep-alive");
httpd_resp_set_hdr(req, "WWW-Authenticate", "Basic realm=\"ESP32 Web Server\"");
httpd_resp_send(req, NULL, 0);
return ESP_OK;
}
// HTTP basic authentication base64 encoding function
static char *http_auth_basic(const char *username, const char *password)
{
size_t out;
char *user_info = NULL;
char *digest = NULL;
size_t n = 0;
int rc = asprintf(&user_info, "%s:%s", username, password);
if (rc < 0) {
ESP_LOGE(TAG, "asprintf() returned: %d", rc);
return NULL;
}
if (!user_info) {
ESP_LOGE(TAG, "No enough memory for user information");
return NULL;
}
esp_crypto_base64_encode(NULL, 0, &n, (const unsigned char *)user_info, strlen(user_info));
// 6: The length of the "Basic " string
// n: Number of bytes for a base64 encode format
// 1: Number of bytes for a reserved which be used to fill zero
digest = calloc(1, 6 + n + 1);
if (digest) {
strcpy(digest, "Basic ");
esp_crypto_base64_encode((unsigned char *)digest + 6, n, &out, (const unsigned char *)user_info, strlen(user_info));
}
free(user_info);
return digest;
}
// Check basic authentication
static bool check_basic_auth(httpd_req_t *req, const char *username, const char *password)
{
char *buf = NULL;
size_t buf_len = httpd_req_get_hdr_value_len(req, "Authorization") + 1;
if (buf_len <= 1) {
ESP_LOGE(TAG, "No Authorization header");
return false;
}
buf = calloc(1, buf_len);
if (!buf) {
ESP_LOGE(TAG, "No memory for auth buffer");
return false;
}
if (httpd_req_get_hdr_value_str(req, "Authorization", buf, buf_len) != ESP_OK) {
free(buf);
return false;
}
char *expected = http_auth_basic(username, password);
if (!expected) {
free(buf);
return false;
}
bool authenticated = (strcmp(expected, buf) == 0);
free(expected);
free(buf);
if (authenticated) {
ESP_LOGI(TAG, "Authenticated user: %s", username);
} else {
ESP_LOGE(TAG, "Authentication failed");
}
return authenticated;
}
// GET handler for the /logout
static esp_err_t logout_get_handler(httpd_req_t *req)
{
httpd_resp_set_type(req, "text/html");
httpd_resp_send(req, logout_page, HTTPD_RESP_USE_STRLEN);
ESP_LOGI(TAG, "Logout page served");
return ESP_OK;
}
// URI registration structure for the /logout
static const httpd_uri_t logout_uri = {
.uri = "/logout",
.method = HTTP_GET,
.handler = logout_get_handler,
.user_ctx = NULL
};
// Root web page GET handler with basic authentication
static esp_err_t basic_auth_get_handler(httpd_req_t *req)
{
basic_auth_info_t *basic_auth_info = req->user_ctx;
if (!check_basic_auth(req, basic_auth_info->username, basic_auth_info->password)) {
return send_401_response(req);
}
httpd_resp_set_type(req, "text/html");
httpd_resp_send(req, root_page, HTTPD_RESP_USE_STRLEN);
return ESP_OK;
}
// URI registration structure for the root / with basic authentication
static httpd_uri_t basic_auth = {
.uri = "/",
.method = HTTP_GET,
.handler = basic_auth_get_handler,
};
// Register the basic authentication URI handler
static void httpd_register_basic_auth(httpd_handle_t server)
{
basic_auth_info_t *basic_auth_info = calloc(1, sizeof(basic_auth_info_t));
if (basic_auth_info) {
basic_auth_info->username = BASIC_AUTH_USER;
basic_auth_info->password = BASIC_AUTH_PASS;
basic_auth.user_ctx = basic_auth_info;
httpd_register_uri_handler(server, &basic_auth);
}
}
// Start the HTTP server
static httpd_handle_t start_web_server(void)
{
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
httpd_handle_t server = NULL;
if (httpd_start(&server, &config) == ESP_OK) {
ESP_LOGI(TAG, "HTTP server started on port %d", config.server_port);
httpd_register_uri_handler(server, &logout_uri);
httpd_register_basic_auth(server);
return server;
}
ESP_LOGE(TAG, "Failed to start HTTP server");
return NULL;
}
// Wi-Fi and IP event handler
static void wifi_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_LOGI(TAG, "Wi-Fi STA started. Connecting to %s...", MY_ESP_WIFI_SSID);
esp_wifi_connect();
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
ESP_LOGW(TAG, "Wi-Fi disconnected. Retrying connection...");
esp_wifi_connect();
} 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 Address: " IPSTR, IP2STR(&event->ip_info.ip));
ESP_LOGI(TAG, "Web Server ready! Access at http://" IPSTR "/", IP2STR(&event->ip_info.ip));
}
}
void app_main(void)
{
// Initialize NVS
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
// Initialize TCP/IP stack and event loop
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
// Create default Wi-Fi STA interface
esp_netif_create_default_wifi_sta();
// Initialize Wi-Fi
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
// Register event handlers
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
ESP_EVENT_ANY_ID,
&wifi_event_handler,
NULL, NULL));
ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,
IP_EVENT_STA_GOT_IP,
&wifi_event_handler,
NULL, NULL));
// Configure Wi-Fi STA
wifi_config_t wifi_config = {
.sta = {
.ssid = MY_ESP_WIFI_SSID,
.password = MY_ESP_WIFI_PASS,
},
};
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());
// Start the web server (it will be ready once IP is assigned)
httpd_handle_t server = start_web_server();
if (server) {
ESP_LOGI(TAG, "Web Server initialized. Waiting for Wi-Fi connection...");
}
}
How the Code Works
In this section, we’ll take a look at the main code sections to see how it works.
Libraries
We start by including the required libraries:
- stdio.h – the standard C library will be used for the printf function that prints the debugging information in the serial monitor;
- string.h – the standard C library used for string manipulation;
- FreeRTOS.h – provides the core FreeRTOS types and functions;
- task.h – allows to use task management;
- esp_log.h – offers a framework to format log messages in the serial monitor for debugging;
- esp_system.h – system level functions like restart and hardware info;
- esp_wifi.h – library for Wi-Fi configuration (station and access point modes and connection options);
- esp_event.h – handles events like Wi-Fi status changes;
- esp_netif.h – provides the network interface;
- esp_http_server.h – library to create an HTTP server and handle HTTP requests/responses;
- esp_tls.h – ESP-IDF TLS/SSL library for secure socket communication;
- esp_check.h – ESP-IDF helper functions for error checking;
- nvs_flash.h – stores key-value data in non-volatile storage (NVS) memory persistently;
- esp_tls_crypto.h – ESP-IDF TLS crypto utilities for ciphers, hashes, and certificate handling;
- sdkconfig.h – includes the project’s configuration file.
#include <stdio.h>
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include <esp_log.h>
#include <esp_system.h>
#include <esp_wifi.h>
#include <esp_event.h>
#include <esp_netif.h>
#include <esp_http_server.h>
#include <esp_tls.h>
#include <esp_check.h>
#include <nvs_flash.h>
#include "esp_tls_crypto.h"
#include "sdkconfig.h"
Wi-Fi Configuration
These variables will be retrieved from the menuconfig options as described in the previous section “Create the Wi-Fi Config Options File – Kconfig.projbuild“.
#define MY_ESP_WIFI_SSID CONFIG_ESP_WIFI_SSID
#define MY_ESP_WIFI_PASS CONFIG_ESP_WIFI_PASSWORD
HTTP Authentication Configuration
You can define a username and a password to access the ESP32 web server. In this example, we’re setting the user to admin, but you can modify the name to whatever you want. The password is password, but you can also modify it.
#define BASIC_AUTH_USER "admin"
#define BASIC_AUTH_PASS "password"
Logging Tag
The “web_server” prefix will be used to log all the debugging messages.
static const char *TAG = "web_server";
HTML – Root Web Page
This is the HTML web page that will be sent to the connected client after a successful HTTP authentication.
static const char *root_page =
"<!DOCTYPE html>"
"<html>"
"<head>"
"<title>ESP-IDF: ESP32 Web Server</title>"
"<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">"
"</head>"
"<body>"
"<h1>ESP-IDF: ESP32 Web Server</h1>"
"<p>Hello from ESP32!</p>"
"<button onclick=\"window.location.href = '/logout';\">Logout</button>"
"</body>"
"</html>";
HTML – Logout Web Page
This is the HTML web page that will be temporarily redirected after clicking the logout button on the root web page.
static const char *logout_page =
"<!DOCTYPE html>"
"<html>"
"<head>"
"<title>Logout</title>"
"<script>"
"function forceLogout(){"
" var xhr = new XMLHttpRequest();"
" xhr.open('GET', '/', true, 'logout', 'logout');"
" xhr.send();"
" setTimeout(function(){ window.location.href='/'; }, 500);"
"}"
"</script>"
"</head>"
"<body onload='forceLogout()'>"
"<h2>Logging out...</h2>"
"</body>"
"</html>";
http_auth_basic
The http_auth_basic function generates a complete HTTP Basic Authentication header value by concatenating the provided username and password into a “username:password” string.
static char *http_auth_basic(const char *username, const char *password)
{
size_t out;
char *user_info = NULL;
char *digest = NULL;
size_t n = 0;
int rc = asprintf(&user_info, "%s:%s", username, password);
if (rc < 0) {
ESP_LOGE(TAG, "asprintf() returned: %d", rc);
return NULL;
}
if (!user_info) {
ESP_LOGE(TAG, "No enough memory for user information");
return NULL;
}
esp_crypto_base64_encode(NULL, 0, &n, (const unsigned char *)user_info, strlen(user_info));
// 6: The length of the "Basic " string
// n: Number of bytes for a base64 encode format
// 1: Number of bytes for a reserved which be used to fill zero
digest = calloc(1, 6 + n + 1);
if (digest) {
strcpy(digest, "Basic ");
esp_crypto_base64_encode((unsigned char *)digest + 6, n, &out, (const unsigned char *)user_info, strlen(user_info));
}
free(user_info);
return digest;
}
check_basic_auth
The check_basic_auth function verifies the HTTP client credentials by extracting the Authorization header from the HTTP request. Then, it generates the correct Base64 encoded credentials using http_auth_basic.
Finally, it compares the credentials sent by the client with the expected username/password defined in the code. If the authentication credentials match, it returns true. Otherwise, it logs the reason for the authentication error.
static bool check_basic_auth(httpd_req_t *req, const char *username, const char *password)
{
char *buf = NULL;
size_t buf_len = httpd_req_get_hdr_value_len(req, "Authorization") + 1;
if (buf_len <= 1) {
ESP_LOGE(TAG, "No Authorization header");
return false;
}
buf = calloc(1, buf_len);
if (!buf) {
ESP_LOGE(TAG, "No memory for auth buffer");
return false;
}
if (httpd_req_get_hdr_value_str(req, "Authorization", buf, buf_len) != ESP_OK) {
free(buf);
return false;
}
char *expected = http_auth_basic(username, password);
if (!expected) {
free(buf);
return false;
}
bool authenticated = (strcmp(expected, buf) == 0);
free(expected);
free(buf);
if (authenticated) {
ESP_LOGI(TAG, "Authenticated user: %s", username);
} else {
ESP_LOGE(TAG, "Authentication failed");
}
return authenticated;
}
HTTP GET – Logout Handler
The logout_get_handler is a simple GET request handler that sets the response content type to “text/html”, sends the logout_page HTML web page content to the client, logs a message, and returns ESP_OK.
static esp_err_t logout_get_handler(httpd_req_t *req)
{
httpd_resp_set_type(req, "text/html");
httpd_resp_send(req, logout_page, HTTPD_RESP_USE_STRLEN);
ESP_LOGI(TAG, "Logout page served");
return ESP_OK;
}
The logout_uri structure registers this handler with the ESP32 HTTP server so that any GET request to the /logout URL will be processed by logout_get_handler.
static const httpd_uri_t logout_uri = {
.uri = "/logout",
.method = HTTP_GET,
.handler = logout_get_handler,
.user_ctx = NULL
};
HTTP GET – Root Handler
These next functions create a simple protected web server using the httpd library. The basic_auth_get_handler function serves as the GET request handler for the root URI /. It first extracts the authentication details, then calls check_basic_auth to verify the client’s Base64 encoded credentials against the defined BASIC_AUTH_USER and BASIC_AUTH_PASS. Finally, if authentication fails, it returns a 401 Unauthorized response, otherwise it returns the root_page web page defined earlier.
static esp_err_t basic_auth_get_handler(httpd_req_t *req)
{
basic_auth_info_t *basic_auth_info = req->user_ctx;
if (!check_basic_auth(req, basic_auth_info->username, basic_auth_info->password)) {
return send_401_response(req);
}
httpd_resp_set_type(req, "text/html");
httpd_resp_send(req, root_page, HTTPD_RESP_USE_STRLEN);
return ESP_OK;
}
static httpd_uri_t basic_auth = {
.uri = "/",
.method = HTTP_GET,
.handler = basic_auth_get_handler,
};
static void httpd_register_basic_auth(httpd_handle_t server)
{
basic_auth_info_t *basic_auth_info = calloc(1, sizeof(basic_auth_info_t));
if (basic_auth_info) {
basic_auth_info->username = BASIC_AUTH_USER;
basic_auth_info->password = BASIC_AUTH_PASS;
basic_auth.user_ctx = basic_auth_info;
httpd_register_uri_handler(server, &basic_auth);
}
}
Start the HTTP Web Server
The start_web_server() function starts by configuring the server (uses the default port 80):
httpd_config_t config = HTTPD_DEFAULT_CONFIG();
httpd_handle_t server = NULL;
The httpd_start(&server, &config) starts the actual HTTP web server and starts listening to connections on port 80, so a client can connect. Then, we register the logout routes.
if (httpd_start(&server, &config) == ESP_OK) {
ESP_LOGI(TAG, "HTTP server started on port %d", config.server_port);
httpd_register_uri_handler(server, &logout_uri);
httpd_register_basic_auth(server);
return server;
}
If it fails to start the HTTP web server, it prints a failed message.
ESP_LOGE(TAG, "Failed to start HTTP server");
Wi-Fi and IP event handler
This callback function runs when an event related to Wi-Fi happens.
static void wifi_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_LOGI(TAG, "Wi-Fi STA started. Connecting to %s...", MY_ESP_WIFI_SSID);
esp_wifi_connect();
} else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
ESP_LOGW(TAG, "Wi-Fi disconnected. Retrying connection...");
esp_wifi_connect();
} 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 Address: " IPSTR, IP2STR(&event->ip_info.ip));
ESP_LOGI(TAG, "Web Server ready! Access at http://" IPSTR "/", IP2STR(&event->ip_info.ip));
}
}
For example, when the ESP32 gets assigned an IP address, it triggers the IP_EVENT that prints this message in the Serial Monitor:
ESP_LOGI(TAG, "Got IP Address: " IPSTR, IP2STR(&event->ip_info.ip));
ESP_LOGI(TAG, "Web Server ready! Access at http://" IPSTR "/", IP2STR(&event->ip_info.ip));
app_main(void)
When creating an ESP-IDF project, the app_main function will always be called to run. This function is where you need to write your code for any ESP-IDF applications; it is the equivalent of the setup() in Arduino programming. When the ESP32 boots, the ESP-IDF framework calls app_main.
void app_main(void)
{
// your code goes here
}
In the app_main(void) function, you start by initializing the NVS (storage)—the ESP32 stores the Wi-Fi settings in flash.
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
Then, initialize the TCP/IP stack required for network functions usage.
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
Start the Wi-Fi interface in station mode, so the ESP32 can connect to the router. It also initializes the Wi-Fi functionalities.
esp_netif_create_default_wifi_sta();
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
Assign the Wi-Fi events so that when the ESP32 establishes a Wi-Fi connection or gets an IP address, it runs the corresponding Wi-Fi event.
ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT,
ESP_EVENT_ANY_ID,
&wifi_event_handler,
NULL, NULL));
ESP_ERROR_CHECK(esp_event_handler_instance_register(IP_EVENT,
IP_EVENT_STA_GOT_IP,
&wifi_event_handler,
NULL, NULL));
These next few lines set the SSID and password so the ESP32 can connect to your network, set the ESP in station mode, and start Wi-Fi.
wifi_config_t wifi_config = {
.sta = {
.ssid = MY_ESP_WIFI_SSID,
.password = MY_ESP_WIFI_PASS,
},
};
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());
Finally, start the web server:
httpd_handle_t server = start_web_server();
if (server) {
ESP_LOGI(TAG, "Web Server initialized. Waiting for Wi-Fi connection...");
}
Build and Flash Code to the ESP32 Board
To build and flash ESP-IDF code to the ESP32, you always need to follow this procedure. You need to select the flash method (UART), the COM port number, the target device (ESP32), build the code, and finally, flash it to the board. All these commands are available in the bottom menu bar of VS Code.
Make sure all your options are correct (they may already be properly configured if you used the project wizard).

However, if your setup is not correct, follow the next instructions to ensure everything is set up correctly. First, click the “Star” icon and select the flash method as UART.

While the ESP32 board is connected to your computer, click the COM Port (plug icon) and select the correct port number that refers to your ESP32.

You also need to select the target device. Click on the chip icon at the bottom bar. In my case, I have an ESP32 with the esp32s3 chip.

For this board, I also need to select the configuration: ESP32-S chip (via builtin USB-JTAG).

Finally, your command bar at the bottom of VS Code should have similar options selected.

Now, you can build the project by clicking the wrench icon (Build Project) as shown in the image below.

The first time you build a project, it usually takes a bit more time. Once completed, it should print a similar message in the Terminal menu and show a “Build Successfully” message.

This is the final step. You can now flash the ESP-IDF project to the ESP32 by clicking the “Flash Device” button (thunder icon).

Depending on your board, you might need to hold down the on-board BOOT button on your ESP32 to put it into flashing mode. Once the process is completed, it will pop-up a info message saying “Flash Done“.

Demonstration
If you followed all the steps, the example should be running successfully on your board. Open your Terminal window — click the “Monitor Device” tool that is illustrated with a screen icon.

The ESP32 connects to Wi-Fi and prints its IP address on the Serial Monitor. Copy that IP address because you need it to access the ESP32 web server.

Note: if nothing shows up on the Serial Monitor, press the ESP32 “EN” button (ENABLE/RESET button next to the microUSB port).
To access your web server in your local network, open your browser and type the ESP32 IP address. The following page should load, asking for the username and password. Enter the username and password, and you should get access to the web server. If you haven’t modified the code, the username is admin, and the password is password.

After typing the right username and password, you should see a similar web page being served by your ESP32.

In the web server page, there’s a Logout button. If you click the Logout button, you’ll be logged out.

And automatically redirected to the home page, where you need to re-enter the HTTP authentication to access the web server.

Wrapping Up
In this tutorial, you’ve learned how to add authentication to your ESP-IDF ESP32 web server (password-protected web server). You can apply what you learned in this tutorial to any web server built with ESP-IDF.
You might find it helpful to read other ESP-IDF guides:
- ESP-IDF: ESP32 Simple Web Server (Serve HTML Page)
- ESP-IDF: ESP32 Blink LED Example (VS Code)
- ESP-IDF: ESP32 GPIO PWM with LEDC (Control LED Brightness)
- ESP-IDF: ESP32 GPIO – Read Analog Input (ADC – Analog to Digital Converter)
Meanwhile, you can check our ESP32 resources (with Arduino IDE) to learn more about the ESP32 board:
Thanks for reading.
To learn more about creating ESP32 web servers (but using Arduino Core), we recommend exploring our eBook: Build Web Servers with ESP32 and ESP8266 (3rd Edition).


