Expose an iSCSI target as a USB mass storage device — powered by ESP32-S3
This firmware turns an ESP32-S3 into a wireless-to-USB storage bridge. The device connects to your Wi-Fi network, logs into an iSCSI target (a NAS, SAN, or any iSCSI server), and presents that storage to a connected host as an ordinary USB flash drive — no drivers, no software, no network stack required on the host side.
Give any USB-capable device — smart TV, car stereo, BIOS flasher, thin client — transparent access to network storage.
Plug a NAS LUN directly into a laptop or workstation without installing iSCSI initiator software or configuring the OS.
Raspberry Pi and similar boards that lack a network-boot iSCSI stack can boot or access a LUN through this USB bridge.
Securely move data from a networked storage server to a machine that is kept off the network — the bridge is the only link.
| Item | Notes |
|---|---|
| ESP32-S3 devkit with an external USB-UART chip | Required for simultaneous flashing and bridge operation. The ESP32-S3's USB OTG and USB Serial/JTAG controllers share the same pins (GPIO 19/20) and cannot be used at the same time — devkits that add an external UART chip (e.g. CP2102N) work around this by providing a fully independent serial port. Recommended: ESP32-S3-DevKitC-1 (has CP2102N on the UART connector). Boards with a single USB connector (e.g. ESP32-S3-Zero, FeatherS3) require re-flashing to switch between serial monitor and bridge operation. |
| Two USB cables | One for the UART/COM port (flashing & serial monitor), one for the OTG port (the bridge connection to your host). Only needed on boards with an external UART chip. |
| Wi-Fi 2.4 GHz AP | Standard WPA2 network. The ESP32-S3 does not support 5 GHz. |
| iSCSI target | Any RFC 3720-compliant server: TrueNAS, Linux targetcli, Windows iSCSI Target, Synology DSM, etc. |
| Chrome or Edge 89+ | Required for the one-click browser flasher. Firefox and Safari users can flash manually with esptool.py — see the alternative path below the flash button. |
Requires Chrome or Edge 89+. If no port appears, hold BOOT while plugging in to force download mode.
Use this path on Firefox, Safari, or any system where Web Serial is unavailable.
1 — Download the firmware bundle
Download firmware-flash-488b792c.zip
— contains all three .bin files and the address map (manifest.json).
2 — Install esptool
pip install esptool
3 — Connect the UART/COM port and flash
Replace PORT with your serial port
(COM3 on Windows, /dev/ttyUSB0 or /dev/cu.usbserial-*
on Linux/macOS):
esptool.py --chip esp32s3 --port PORT write_flash \ --flash_mode dio --flash_size 2MB --flash_freq 80m \ 0x0 bootloader.bin \ 0x8000 partition-table.bin \ 0x10000 iscsi-usb-bridge.bin
Windows users without Python can use the
Espressif Flash Download Tool
— load each .bin at the addresses above.
On first boot (and any time credentials are cleared) the bridge enters provisioning mode instead of trying to connect. All configuration is done through a built-in web form — no serial terminal or app required.
iSCSI-Bridge-XXXX
(where XXXX is unique to your board). It is an open network — no password.
http://192.168.4.1/.
| Field | What to enter |
|---|---|
| Wi-Fi SSID | Name of your 2.4 GHz network |
| Wi-Fi Password | WPA2 passphrase (leave blank for open networks) |
| iSCSI Target Host | IPv4 address or DNS name of your iSCSI server, e.g. 192.168.1.50 or target.example.com. A hostname is required when using TLS + CA verification (see below). |
| iSCSI Port | TCP port — 3260 for plain iSCSI, 3261 for iSCSI-over-TLS (scsipub convention). The port field auto-switches when you toggle the TLS checkbox. |
| iSCSI Target IQN | Full IQN of the LUN, e.g. iqn.2003-01.org.linux-iscsi.nas:disk0 |
| CHAP Username (optional) | Leave blank if the target does not require authentication. Must match the target's configured userid / incoming user. |
| CHAP Secret (optional) | Shared secret paired with the CHAP username. Stored in NVS and only sent as a hashed MD5 challenge response — never transmitted in plain text. |
| Encrypt with TLS (checkbox) | Wrap the iSCSI connection in TLS 1.2/1.3 before any PDU is exchanged. Recommended for connections over the public internet; usually unnecessary on a trusted LAN. |
| TLS pinned SHA-256 (optional) | SHA-256 fingerprint of the target's leaf certificate (64 hex chars, colons and 0x prefix allowed). When set, the fingerprint is checked exactly and the certificate does not need to chain to a public CA. When blank, the bundled Mozilla CA store is used and the target host must be a DNS name. |
targetcli: run targetcli ls /iscsi and copy the IQN shown.
On Windows: iSCSI Initiator → Targets tab lists discovered IQNs after a portal discovery.
iqn.2024-01.local:esp32:<mac> (printed to the serial
log on each boot) and connects from the IP assigned by your DHCP server.
Make sure your target's ACL allows this initiator before saving the config.
MD5(id ‖ secret ‖ challenge). Mutual CHAP
(target authenticates back to the initiator) is not currently supported.
cd /iscsi/<iqn>/tpg1 ; set attribute authentication=1, then
cd acls/<initiator-iqn> ; set auth userid=<user> password=<secret>3261. For other targets you will typically need
an stunnel or haproxy wrapper in front of port
3260.
openssl s_client -connect host:3261 -servername host < /dev/null 2>/dev/null | openssl x509 -fingerprint -sha256 -noout
Copy the colon-separated hex; the provisioning form accepts it with or
without colons and with an optional 0x prefix.
To change Wi-Fi or iSCSI settings, trigger a factory reset by holding the BOOT button for 3 seconds at power-on:
If saved credentials become invalid (wrong Wi-Fi password, target moved to a new IP, etc.) the bridge will automatically erase its config and reboot into provisioning mode after failing to connect — no manual reset needed.
| Symptom | Likely cause & fix |
|---|---|
iSCSI-Bridge-XXXX network never appears |
Board did not boot into provisioning mode. Hold BOOT for 3 s at power-on to force a reset, or reflash. |
| Captive portal form does not open automatically | Navigate manually to http://192.168.4.1/ — some OSes suppress the prompt for open networks. |
| USB drive does not appear on host after setup | iSCSI login likely failed. Check the target IP, port, and IQN. Verify the target's ACL permits the bridge's initiator IQN (visible in the serial log via idf.py monitor). |
Serial log shows CHAP auth rejected or status class=0x02 |
The target rejected the CHAP credentials. Re-enter the username and secret via the provisioning form, making sure they match the target's configuration exactly (CHAP secrets are case-sensitive). If the target does not use CHAP at all, leave both fields blank. |
Serial log shows tls: handshake failed or cert verification failed |
TLS could not authenticate the target. If you're using CA mode (no pin): make sure the iSCSI Target Host field is the exact DNS name that appears in the target's certificate (not an IP), and that the target's certificate chains to a public CA. If you're using pin mode: re-copy the leaf SHA-256 from the target (see the iSCSI over TLS box above) — the certificate may have rotated. |
Serial log shows resolve '…' failed: gai=… |
DNS resolution failed — either the hostname is typo'd or the bridge's Wi-Fi network cannot reach a DNS server. Try switching to an IP literal (plain iSCSI) or to TLS pin mode (which works with IP literals too). |
| USB drive appears but reads/writes fail | iSCSI session dropped. The bridge reconnects automatically; if the problem persists check network stability between the bridge and the target. |
| Host says disk needs formatting | Normal if the LUN is newly created and has no filesystem. Format from the host as you would any new drive. |
| Only one host can mount the drive | Expected — the iSCSI LUN is accessed exclusively by the bridge, which re-exports it as a single USB MSC device. Simultaneous multi-host access is not supported. |
| Flashing fails / port not found | Make sure you are using the UART/COM port (the one with the external UART chip), not the OTG port. Hold BOOT while plugging in to enter download mode manually. |
| Board has only one USB connector and bridge stops working after connecting for serial monitor | Single-connector boards (ESP32-S3-Zero, FeatherS3, etc.) cannot flash and bridge simultaneously — the OTG and Serial/JTAG controllers share the same pins. Disconnect the serial monitor cable and reconnect to the host you want to use as the USB drive target. |
The ESP32-S3 contains two USB controllers — a USB OTG controller and a USB Serial/JTAG controller — but they share the same two physical pins (GPIO 19/20) through an internal mux and cannot operate simultaneously. This firmware uses the OTG controller to enumerate as a USB Mass Storage (MSC) device using the TinyUSB stack. Devkits that add an external USB-UART bridge chip (such as the CP2102N on the DevKitC-1) provide a genuinely independent serial port on a separate connector, which is why those boards can be flashed and used as a bridge at the same time.
When the host issues a SCSI READ or WRITE command over USB, the firmware translates it into an iSCSI READ (6/10/16) or WRITE (6/10/16) command sent over TCP to the configured target. The target performs the actual storage I/O and returns the data, which the firmware forwards to the USB host — making the network disk appear local.
Configuration is stored in the ESP32-S3's internal Non-Volatile Storage (NVS) partition and survives power cycles.
Firmware built with ESP-IDF v5.4. Web flasher powered by ESP Web Tools.
A Defensible Logic product