Print to your home printer from anywhere — no port forwarding, no cloud print service, no PC left running at home. A $10 ESP32-S3 board joins your Tailscale network as a real node and forwards print jobs to your printer over the LAN:
laptop, anywhere Tailscale tunnel your home LAN
┌──────────────┐ WireGuard (direct or DERP relay) ┌─────────┐ ┌─────────┐
│ Windows PC │ ═══════════════════════════════════▶│ ESP32-S3│──────▶ │ printer │
│ LPR driver │ 100.100.20.30:515 │ proxy │ :515 │ (LPD) │
└──────────────┘ └─────────┘ └─────────┘
The ESP32 makes only outbound connections (WiFi → Tailscale control plane + WireGuard), so nothing on your home network is exposed to the internet. Verified end-to-end on real hardware: printing from the home LAN (fast, direct WireGuard path) and from a cellular hotspot on the other side of a carrier NAT (~25 s per page via Tailscale's DERP relay).
This project stands on two great open-source components, vendored in components/:
- CamM2325/microlink (MIT) — a Tailscale-compatible client for ESP32. Thank you — this project would not exist without it.
- smartalock/wireguard-lwip (BSD-3) — the WireGuard implementation for lwIP that microlink builds on.
| Bug | Effect | Report | Fix |
|---|---|---|---|
| lwIP entered from the wrong task on decrypted RX | random crash + reboot mid-transfer under TCP load | #17 | PR #20 |
| unvalidated peer endpoints installed at peer-add | one-way tunnel — peers behind carrier NAT (phone hotspots) can never reach you | #18 | PR #21 |
| DERP home region hardcoded to Dallas | very slow relayed traffic outside North America | #19 | config note below |
The copy vendored in this repository already includes all three fixes — you don't need to do anything.
- ESP32-S3 with PSRAM. Built and tested on an ESP32-S3 N16R8 devkit
(16 MB flash, 8 MB octal PSRAM). PSRAM is mandatory — microlink holds
3×64 KB buffers at once while fetching the network map; without PSRAM it
exhausts internal RAM and never gets a tailnet address (endless
Empty MapResponse). - Any printer that speaks LPR/LPD (TCP port 515) on your LAN. Tested with a
Brother MFC-J2340DW (LPR queue
BINARY_P1). Most network printers from Brother/HP/Epson/Canon support LPD — check yours before starting (see the printer section below).
Config warning: never enable
CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP. Forcing WiFi buffers into octal PSRAM breaks WiFi on the S3 (esp_wifi_scan_startfails, disconnectreason=201). The providedsdkconfig.defaultsis already set up correctly: PSRAM on, WiFi buffers internal.
- A (free) Tailscale account with Tailscale installed on the computer you'll print from.
- ESP-IDF v5.3
installed and exported (
. ~/esp/esp-idf/export.sh).
git clone <this-repo>
cd esp32-tailscale-print-bridge
# your WiFi + Tailscale auth key (file is gitignored, secrets stay local)
cp sdkconfig.credentials.example sdkconfig.credentials
# edit sdkconfig.credentials with your valuesThen two code-level settings:
-
Your printer's LAN IP — edit
main/main.c:#define PRINTER_IP "192.168.1.222" /* <-- set to YOUR printer's LAN IP */
Find it on the printer's own network status page/printout. Give the printer a DHCP reservation in your router so it doesn't move.
-
Your nearest DERP region — edit
components/microlink/include/microlink_internal.h:#define ML_DERP_REGION 3 /* Singapore in this copy — pick YOURS */
This is the relay region used whenever a direct connection can't be established (typical for phones/hotspots behind carrier NAT). A wrong region costs real speed: with the relay on the wrong continent, a one-page job took ~4 minutes; with the right one, ~25 seconds. Pick the region closest to the printer's location from Tailscale's DERP region list (common ids: 1 NYC, 2 SFO, 3 Singapore, 4 Frankfurt, 9 Dallas, 10 Seattle).
idf.py set-target esp32s3
idf.py build
idf.py flash monitor(flash.sh in this repo is an alternative flasher for WSL2+usbip setups, where
esptool's RTS-based reset silently fails; ignore it on a normal Linux/macOS/Windows
host.)
On the serial console you should see the board join WiFi, connect to Tailscale, and get a tailnet address:
m5-proxy: WiFi got LAN IP: 192.168.x.x
...
m5-proxy: *** TAILNET CONNECTED — VPN IP: 100.x.x.x ***
m5-proxy: print-proxy listening on 0.0.0.0:515 -> 192.168.1.222:515
The board also appears in your Tailscale admin console
as print-bridge. Note its 100.x.x.x address — that's your printer's address
from anywhere in the world.
Know your printer first. Before configuring the port, check your printer's documentation or its web admin page for LPD/LPR support and its queue name (Brother uses
BINARY_P1; HP is oftenraworTEXT; Epson/Canon have their own). Having the queue name ready makes the port configuration one-shot.
-
Settings → Bluetooth & devices → Printers & scanners → Add device — wait, then choose "Add manually" ("The printer that I want isn't listed").
-
Select "Add a printer using an IP address or hostname" → Device type: TCP/IP Device → Hostname/IP: the board's tailnet IP (the
100.x.x.xfrom step 4 above — not the printer's LAN IP). Uncheck "Query the printer and automatically select the driver". -
When asked for the port type, choose Custom → Settings and set:
- Protocol: LPR
- Queue Name: your printer's LPR queue (e.g.
BINARY_P1for Brother) - LPR Byte Counting: unchecked works (the verified setup below); if your driver's jobs come out truncated, try enabling it
- SNMP Status Enabled: UNCHECKED ← critical, see below
-
Pick your printer's driver (install the manufacturer's driver first if Windows doesn't list it), finish the wizard, and print a test page.
Why SNMP must be off: the bridge forwards TCP port 515 only. SNMP is UDP and never reaches the printer, so with "SNMP Status Enabled" checked, Windows decides the printer is offline and holds your job in the queue forever. This is the #1 setup mistake.
| Symptom | Cause / fix |
|---|---|
| Job sits in the Windows queue, printer shows "offline" | SNMP still enabled on the port — uncheck it (step 3 above). |
| Prints work on home WiFi but are slow from cellular (~minutes) | Wrong ML_DERP_REGION — pick the region nearest the printer. Relayed prints of ~25 s/page are normal; carrier NAT usually prevents a direct path. |
Board never gets a 100.x address, log shows Empty MapResponse repeatedly |
PSRAM not working — board must actually have PSRAM, and CONFIG_SPIRAM=y (+ octal mode for N16R8) must be set. |
WiFi won't connect, disconnect reason=201 |
CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP got enabled — remove it, WiFi buffers must stay in internal RAM. |
| Diagnosing "is it the printer or the tunnel?" | python3 tools/lpr_probe.py <host> — run once against the printer's LAN IP and once against the board's tailnet IP, compare ACK latencies (it aborts the job; nothing prints). |
- One print job at a time (listen backlog of 1). A second job waits; that's the right behavior for a printer anyway.
- TCP/LPR only. No UDP forwarding — so no SNMP status, no printer discovery, no scanning. Print works; the driver's status readback doesn't.
- Peers behind carrier NAT ride the DERP relay (~25 s/page). This is a Tailscale-architecture reality, not a bug; home/office networks usually get a fast direct path.
- Control plane is Tailscale SaaS — the coordination server hostname is compiled into microlink. Self-hosted Headscale is untested.
- The Tailscale node key expires (default ~180 days). When it does, the board
needs a fresh auth key in
sdkconfig.credentialsand a reflash. - IPv4 only.
MIT — see LICENSE. Vendored components keep their own licenses: microlink (MIT, Cameron Malone), wireguard-lwip (BSD-3, Daniel Hope).



