iSCSI USB Bridge

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.

Use cases

🖥

Legacy device access

Give any USB-capable device — smart TV, car stereo, BIOS flasher, thin client — transparent access to network storage.

💾

NAS as USB drive

Plug a NAS LUN directly into a laptop or workstation without installing iSCSI initiator software or configuring the OS.

🔌

Embedded / single-board PCs

Raspberry Pi and similar boards that lack a network-boot iSCSI stack can boot or access a LUN through this USB bridge.

🔒

Air-gapped transfer

Securely move data from a networked storage server to a machine that is kept off the network — the bridge is the only link.

What you need

ItemNotes
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 cablesOne 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 APStandard WPA2 network. The ESP32-S3 does not support 5 GHz.
iSCSI targetAny 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.

Step 1 — Flash firmware

  1. Connect the ESP32-S3 to this computer via the UART/COM port. On the DevKitC-1 this is the connector labelled COM, closest to the board edge. The other connector (USB) is the OTG port — it is how the bridge talks to your host machine once running, and cannot be used for programming.
  2. Click Install firmware and select the CP210x (or similar UART) port in the browser dialog. If you see "USB JTAG/serial debug unit" instead, you have the OTG port plugged in — swap cables.
  3. Confirm the installation and wait for the progress bar to reach 100%.
  4. The device reboots automatically when flashing is complete.

Requires Chrome or Edge 89+. If no port appears, hold BOOT while plugging in to force download mode.

Can't use the browser flasher? Flash manually with esptool

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.


Step 2 — Configure Wi-Fi & iSCSI

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.

  1. On your phone or laptop, open Wi-Fi settings and connect to the network named iSCSI-Bridge-XXXX (where XXXX is unique to your board). It is an open network — no password.
  2. A captive-portal prompt should appear automatically. If it does not, open a browser and navigate to http://192.168.4.1/.
  3. Fill in the form:
    FieldWhat to enter
    Wi-Fi SSIDName of your 2.4 GHz network
    Wi-Fi PasswordWPA2 passphrase (leave blank for open networks)
    iSCSI Target HostIPv4 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 PortTCP 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 IQNFull 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.
  4. Click Save & Reboot. The bridge restarts, connects to your network, logs into the iSCSI target, and begins advertising the LUN as a USB drive.
  5. Disconnect the flashing cable. Connect the ESP32-S3's OTG port to the host that should see the drive. The host enumerates it as a standard USB mass storage device within a few seconds.
Finding your iSCSI IQN. On TrueNAS: Sharing → iSCSI → Targets, copy the value in the Target Name column. On Linux targetcli: run targetcli ls /iscsi and copy the IQN shown. On Windows: iSCSI Initiator → Targets tab lists discovered IQNs after a portal discovery.
iSCSI access control. Most iSCSI targets restrict connections by initiator IQN or IP address. The bridge uses the IQN 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.
CHAP authentication (optional). If your target requires authentication, fill in the two CHAP fields on the provisioning form; otherwise leave them blank. The bridge implements one-way CHAP with the MD5 algorithm (RFC 7143 §12.1) — the target challenges the initiator, which replies with MD5(id ‖ secret ‖ challenge). Mutual CHAP (target authenticates back to the initiator) is not currently supported.

Configuring the target: Most targets require the secret to be at least 12 characters.
iSCSI over TLS (optional). Tick Encrypt connection with TLS on the provisioning form to wrap the entire iSCSI session in TLS 1.2/1.3. This is the recommended transport whenever the bridge and the target are not on the same trusted network — e.g., connecting from a home LAN to an internet-hosted target. On a trusted LAN, plain iSCSI is usually fine and avoids the ~130 KB TLS code path.

The bridge uses immediate TLS (TLS handshake first, then iSCSI login on the encrypted stream) on a separate port — it does not implement RFC 7146 inline STARTTLS upgrade. The target must expose a TLS-only port; for scsipub that is port 3261. For other targets you will typically need an stunnel or haproxy wrapper in front of port 3260.

Trust model — pick one: If both a pin and a DNS name are provided, the pin takes priority and no CA chain validation is performed.

Getting the SHA-256 fingerprint:
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.

Reconfiguring / factory reset

To change Wi-Fi or iSCSI settings, trigger a factory reset by holding the BOOT button for 3 seconds at power-on:

  1. While the board is powered off, press and hold the BOOT button.
  2. Apply power (plug in the cable or press EN/RST), keeping BOOT held.
  3. The LED blinks rapidly — continue holding.
  4. After 3 seconds the LED goes solid, the saved config is erased, and the device reboots into provisioning mode.
  5. Release the button and follow the Step 2 setup procedure again.
Early release = safe abort. Releasing the BOOT button before the 3-second threshold is reached cancels the reset and the device boots normally with existing credentials.

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.


Troubleshooting

SymptomLikely 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.

How it works

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.


Source & licence

Firmware built with ESP-IDF v5.4. Web flasher powered by ESP Web Tools.

A Defensible Logic product