ESPHome and Tasmota cover a lot of home projects without writing code, but sooner or later you may want firmware that does exactly what you need and nothing else. The Arduino IDE with Espressif's own Arduino core (arduino-esp32) is the most common way to write that firmware in C++. This guide takes you from a fresh Arduino IDE 2 installation to an ESP32 that joins your Wi-Fi, announces itself on the local network and answers HTTP requests.
Everything below comes from the arduino-esp32 documentation, the Arduino IDE 2 tutorials on docs.arduino.cc and the Arduino Help Center. At the time of writing, the latest arduino-esp32 release is 3.3.12 (September 2026).
What you need
- Arduino IDE 2, downloaded from the Arduino Software page. The arduino-esp32 docs list Arduino IDE 1.8 or later as the minimum, but the menu names in this guide are from version 2.
- An ESP32 development board and a USB cable that carries data. The arduino-esp32 troubleshooting page points out that some cables are only for charging.
- Your Wi-Fi name (SSID) and password. The original ESP32 and the ESP32-C3 only support 2.4 GHz Wi-Fi.
Step 1: add Espressif's board manager URL
The ESP32 boards are not built into the IDE. You tell the Boards Manager where to find them by adding a package index URL:
- Open the settings: File > Preferences on Windows and Linux, Arduino IDE > Settings on macOS.
- Near the bottom, find Additional boards manager URLs.
- Paste the stable release URL:
https://espressif.github.io/arduino-esp32/package_esp32_index.json
If the field already holds other URLs, click the button to the right of it. A window opens where you can enter one URL per line. Click OK in that window and OK again in the settings.
Espressif also publishes a development index (package_esp32_dev_index.json) with pre-release versions. For a device that has to keep working, stay with the stable one.
Step 2: install the esp32 platform
In Arduino IDE 2 the Boards Manager sits in the left column: click the Arduino board icon (you can also reach it through Tools > Board > Boards Manager). Type esp32 into the search field and click INSTALL on the esp32 package by Espressif Systems. When it finishes, the entry shows INSTALLED with the version number.
The download includes compilers and tools for every ESP32 variant, so it takes a while. The arduino-esp32 docs suggest restarting the IDE after installation.
Step 3: select the board and the port
Plug in the board. Next to the verify and upload buttons at the top of the window there is a drop-down with detected boards. ESP32 development boards are often not recognised automatically, so pick Select other board and port... in that drop-down, or use Tools > Board and Tools > Port.
For a typical board with the original ESP32 chip, choose ESP32 Dev Module. For a board with an ESP32-C3, choose ESP32C3 Dev Module, or the exact name of your board if it is in the list. The port is the serial device that appears when you plug the board in: a COM port on Windows, a /dev/tty... device on Linux and macOS.
If no port appears, the arduino-esp32 troubleshooting page lists these checks: a missing USB driver for the board's USB-to-serial chip, a charge-only cable, a different USB port (preferably directly on the computer, not through a hub), the power supply and finally a damaged board.
Two USB connectors
Newer ESP32 variants can have two USB connectors. According to the arduino-esp32 troubleshooting page:
- The UART connector goes through a USB-to-UART converter and shows up under that chip maker's name, for example "Silicon Labs CP210x UART Bridge".
- The USB connector uses the chip's own USB and shows up as an Espressif device ("Espressif USB JTAG/serial debug unit").
The original ESP32 has no built-in USB, so its boards only have the UART connection. If you use the native USB connector, set USB CDC On Boot to Enabled in the Tools menu (and, on chips that offer the USB Mode option, choose "Hardware CDC and JTAG"), otherwise Serial.print() output will not reach the Serial Monitor. With the UART connector, set USB CDC On Boot to Disabled.
Linux: permission denied
On Linux the upload can fail with "Permission denied" even when the port is selected. The Arduino IDE 2 upload tutorial explains why: the serial device belongs to the dialout group. Check it with ls -l /dev/ttyACM* (the number at the end can differ), add your user to the group and then log out and log in again:
sudo usermod -a -G dialout <username>
Step 4: write the sketch
The sketch below is based on the HelloServer example of the WebServer library in the arduino-esp32 repository. It connects to Wi-Fi, sets a hostname, starts mDNS and answers on port 80:
#include <WiFi.h>
#include <WebServer.h>
#include <ESPmDNS.h>
const char *ssid = "your-ssid";
const char *password = "your-password";
WebServer server(80);
void handleRoot() {
String body = "hello from esp32!\nuptime: ";
body += String(millis() / 1000);
body += " s\n";
server.send(200, "text/plain", body);
}
void handleNotFound() {
server.send(404, "text/plain", "not found\n");
}
void setup() {
Serial.begin(115200);
WiFi.setHostname("esp32-test");
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println();
Serial.print("IP address: ");
Serial.println(WiFi.localIP());
if (MDNS.begin("esp32-test")) {
Serial.println("MDNS responder started");
}
server.on("/", handleRoot);
server.onNotFound(handleNotFound);
server.begin();
Serial.println("HTTP server started");
}
void loop() {
server.handleClient();
delay(2);
}
A few details that matter:
WiFi.setHostname()must be called beforeWiFi.mode()orWiFi.begin(). The arduino-esp32 Wi-Fi API docs say so explicitly. The hostname is what your router shows in its client list, and it can be at most 32 characters long.server.handleClient()has to run regularly inloop(). If you add slow code there, such as longdelay()calls, the web server stops responding while it waits.- The Wi-Fi password sits in plain text in the source file. Do not commit the sketch to a public repository as it is.
Step 5: verify, upload and open the Serial Monitor
The two buttons at the very left of the toolbar are Verify (checkmark), which only compiles, and Upload (arrow), which compiles and flashes. Run Verify first; compile errors show up in the console at the bottom. Then click Upload. When it is done, a notification appears in the bottom right corner.
Open the Serial Monitor with the button in the top right corner of the IDE. In version 2 it opens as a tab at the bottom of the window instead of a separate window. Set the baud rate in its settings to 115200, the same value as in Serial.begin(). Press the board's reset button (often labelled EN or RST) and you should see the dots, then the IP address.
Now open http://<IP address>/ in a browser on the same network. If your computer resolves mDNS names, http://esp32-test.local/ should work too.
When the upload fails
The most common error is:
A fatal error occurred: Failed to connect to ESP32: Timed out waiting for packet header
To accept new firmware over the serial port, the ESP32 has to start in download mode, which happens when GPIO0 is held low during a reset. Most development boards do this automatically, but not always. The arduino-esp32 troubleshooting page suggests:
- Hold the BOOT button while the upload starts.
- Use another cable and another USB port, avoid hubs and check the power supply.
- Disconnect anything wired to the TX and RX pins.
- On boards that repeatedly fail to enter download mode, solder a 10 µF capacitor between RST and GND, or add the reset delay circuit from the ESP32 Hardware Design Guidelines.
Where to go next
From here you can read sensors with the Arduino libraries for them, publish readings over MQTT with an MQTT client library, or add more server.on() routes. If you want over-the-air updates or a configuration portal, that is extra code you have to write and secure yourself, which is one reason why ESPHome is a reasonable choice for devices that only need standard sensors and switches. The Arduino path is worth it when you need behaviour that no ready-made firmware offers.