When uploads fail
The port vanished, esptool prints dots and gives up, or the board uploads once and never again. All three have the same small set of causes, and none of them are your code.
The one rule
The chip reads GPIO 0 at the instant reset is released. Low means wait for esptool; high means run the sketch. Everything on this page is that rule being satisfied, or not.
Reading the error
No serial data received— the chip never entered download mode. Button sequence, or a bad cable.Wrong boot mode detected— it entered, then left. Something is pulling a strapping pin.- The port is not in the list at all — the operating system never saw a USB device. Cable, driver, or a native-USB board whose sketch has crashed.
- It uploads, then nothing on the Serial Monitor — that is not an upload failure. Check the baud rate.
When nothing works
Erase the flash. A half-written partition table or a failed OTA leaves the board
boot-looping in a way that also blocks uploads, and
esptool.py erase_flash clears it in ten seconds. You lose stored settings and
uploaded files, which is exactly what you want at this point.
The code
There is no sketch for this page. The recovery is a button sequence, and it is worth learning by hand because it works on every board whether the auto-reset circuit does or not.
1. Hold down BOOT (sometimes labelled IO0 or FLASH).
2. Tap EN (sometimes RST) and let it go.
3. Let go of BOOT.
4. Press Upload.
// Still failing? Erase the flash and start clean:
// esptool.py --port /dev/ttyUSB0 erase_flash
// Then upload again. This clears a corrupt partition table,
// which is what a board that boot-loops after a failed OTA has.If manual mode always works and automatic never does, the two transistors that drive EN and IO0 from the USB chip are missing or wrong on your board. Nothing in software fixes that; the sequence below is the fix.
Same buttons. The difference is that a MicroPython board can also be bricked by a bad main.py, which looks like a dead board and is not.
# A main.py that crashes on boot loops forever and eats the REPL.
# Interrupt it over serial with Ctrl-C before it gets going,
# then rename the file:
import os
os.rename('main.py', 'main.bak')
# If Ctrl-C is too slow, hold BOOT, tap EN, release BOOT,
# and reflash the firmware with esptool.A board stuck in a crash loop from main.py still enters download mode with the button sequence, so you can always reflash the firmware and start again.
When it does not work
This is a native-USB board. The port is created by your firmware, so a sketch that crashes or sleeps takes the port with it. The button sequence brings back the ROM's own USB device, which is always there.
Something else is holding a strapping pin. Unplug everything from GPIO 0, 2, 12 and 15 and try again. A sensor that idles low on GPIO 0 stops the board booting normally and stops uploads too.
Almost always power. A Wi-Fi transmit burst asks for 350 mA, and a thin cable or a laptop port that cannot give it browns the chip out. The serial log says Brownout detector was triggered, which is a power message, not a code one.
Your user is not in the dialout group. sudo usermod -aG dialout $USER, then log out and back in. It is not a driver problem and no amount of reinstalling fixes it.
Half the failures on this page are really about which port the board became, and who decided the name. That is the next page.
Serial ports and drivers →Edit this page — content/esp32/when-uploads-fail.mdx
Discuss this article
Ask about this page. The answer stays here, on the page it belongs to, for whoever hits the same wall next.