<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: hack-tramp</title>
    <description>The latest articles on DEV Community by hack-tramp (@hacktramp).</description>
    <link>https://dev.to/hacktramp</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F649800%2F15f54188-9ecd-43e0-b40b-f9a609447d28.png</url>
      <title>DEV Community: hack-tramp</title>
      <link>https://dev.to/hacktramp</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/hacktramp"/>
    <language>en</language>
    <item>
      <title>TCP proxy over MQTT using a cheap ESP32 microcontroller</title>
      <dc:creator>hack-tramp</dc:creator>
      <pubDate>Thu, 01 Oct 2026 13:46:12 +0000</pubDate>
      <link>https://dev.to/hacktramp/tcp-proxy-over-mqtt-using-a-cheap-esp32-microcontroller-21pd</link>
      <guid>https://dev.to/hacktramp/tcp-proxy-over-mqtt-using-a-cheap-esp32-microcontroller-21pd</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0i148b5h6brk2wnszodj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F0i148b5h6brk2wnszodj.png" alt=" " width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Full info and code : &lt;a href="https://github.com/hack-tramp/ESP-MQTTunnel/" rel="noopener noreferrer"&gt;https://github.com/hack-tramp/ESP-MQTTunnel/&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;A working PoC that turns an ESP32 into a hardware proxy that tunnels traffic over MQTT. Because it connects to a MQTT broker, you can bypass firewalls/censorship without open ports/public IP (which would be needed for direct ESP32 - laptop comms). All traffic is relayed &lt;strong&gt;raw&lt;/strong&gt; — no TLS interception, no decryption.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;What it does&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Here's an example to show how this works (for more technical details see below). The purpose is to bypass internet restrictions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Location A:&lt;/strong&gt; A country with highly censored / very restrictive internet. Laptop running Win10 + Firefox + local python proxy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Location B:&lt;/strong&gt; Uncensored, free internet. ESP32 is connected here.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Location C:&lt;/strong&gt; MQTT server - this must be reachable from A and B but does not have to be in a place with uncensored internet. I used HiveMQ cloud (free tier).&lt;/p&gt;

&lt;p&gt;(definition: MQTT (Message Queuing Telemetry Transport) is a lightweight, open-standard messaging protocol designed for resource-constrained devices and unreliable networks)&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;laptop ---&amp;gt; MQTT server :&lt;/strong&gt; The laptop browser tries to access a restricted website, and sends a request to the local python proxy, which sends this to the MQTT server.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;MQTT server ----&amp;gt; ESP32 ---&amp;gt; &lt;a href="http://www.example.com" rel="noopener noreferrer"&gt;www.example.com&lt;/a&gt;&lt;/strong&gt;  The ESP32 reads the bytes from the MQTT server and forwards them to the restricted website which is normally inaccessible from A.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;a href="http://www.example.com" rel="noopener noreferrer"&gt;www.example.com&lt;/a&gt; ---&amp;gt; ESP32 ---&amp;gt; MQTT server :&lt;/strong&gt;  ESP32 receives response from the website, and uploads it to the MQTT server.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;MQTT server ---&amp;gt; laptop:&lt;/strong&gt;  Laptop at A reads the restricted site's response from the MQTT server and (via python) shows it in the browser.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Tested with: Windows 10 Firefox / Python 3 &amp;lt;--&amp;gt; ESP32-S3 Dev Module &amp;lt;--&amp;gt; HiveMQ Cloud (free tier)&lt;/p&gt;

&lt;blockquote&gt;
&lt;h2&gt;
  
  
  ⚠️ The MQTT broker must be reachable from whichever country the laptop is in.
&lt;/h2&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Confirmed working
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;YouTube&lt;/li&gt;
&lt;li&gt;Gmail&lt;/li&gt;
&lt;li&gt;Twitter / X&lt;/li&gt;
&lt;li&gt;News sites&lt;/li&gt;
&lt;li&gt;Reddit&lt;/li&gt;
&lt;li&gt;Google, Duckduckgo etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Known issues
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Instagram gets stuck&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;The &lt;strong&gt;ESP32&lt;/strong&gt; connects to your Wi-Fi and subscribes to the MQTT &lt;code&gt;req&lt;/code&gt; topic.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;Python script&lt;/strong&gt; runs a local HTTP proxy on the laptop (e.g. &lt;code&gt;127.0.0.1:8080&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Firefox is configured to use that local proxy.&lt;/li&gt;
&lt;li&gt;When Firefox requests a site, the Python proxy:

&lt;ul&gt;
&lt;li&gt;Parses the &lt;code&gt;CONNECT host:port&lt;/code&gt; line.&lt;/li&gt;
&lt;li&gt;Publishes an &lt;code&gt;open&lt;/code&gt; message over MQTT.&lt;/li&gt;
&lt;li&gt;Streams the raw TLS bytes as &lt;code&gt;data&lt;/code&gt; messages.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;The ESP32 receives those messages, opens a real TCP socket to the target host, and forwards the bytes.&lt;/li&gt;
&lt;li&gt;Responses from the real server come back through MQTT (&lt;code&gt;res&lt;/code&gt; topic), are received by the Python proxy, and written back to Firefox.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Limitations
&lt;/h2&gt;

&lt;p&gt;The ESP32 has &lt;strong&gt;limited resources&lt;/strong&gt;, so it can't handle too many simultaneous connections. Trying to load pages in multiple tabs will not work.&lt;/p&gt;

&lt;p&gt;Images and video &lt;em&gt;do&lt;/em&gt; work, but to save bandwidth (especially on a free MQTT account) and improve speed, consider using a content blocker such as &lt;strong&gt;Block Image Reloaded&lt;/strong&gt; in Firefox. &lt;/p&gt;




&lt;h2&gt;
  
  
  Usage
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. MQTT broker
&lt;/h3&gt;

&lt;p&gt;Create a free cluster at &lt;a href="https://www.hivemq.com/mqtt-cloud-broker/" rel="noopener noreferrer"&gt;HiveMQ Cloud&lt;/a&gt; (or use any MQTT broker reachable from both devices).&lt;/p&gt;

&lt;p&gt;Note the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hostname&lt;/li&gt;
&lt;li&gt;Port (typically &lt;code&gt;8883&lt;/code&gt; for TLS)&lt;/li&gt;
&lt;li&gt;Username&lt;/li&gt;
&lt;li&gt;Password&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Configure credentials
&lt;/h3&gt;

&lt;p&gt;Edit both files and set your MQTT credentials:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;server.py&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;broker&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your-cluster-id.s1.eu.hivemq.cloud&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;user&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your-username&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;pw&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;your-password&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;esp.ino&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;ssid&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"your-wifi-ssid"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;pass&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"your-wifi-password"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;broker&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"your-cluster-id.s1.eu.hivemq.cloud"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"your-username"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;char&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;mpass&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"your-password"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Flash the ESP32
&lt;/h3&gt;

&lt;p&gt;Open esp.ino in the Arduino IDE, select your ESP32 board, and upload. Open the Serial Monitor at 115200 baud to see activity. I prefer to use PuTTY so I can copy large amounts of output.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Run the python proxy server
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pip install paho-mqtt
python server.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Listening on 127.0.0.1:8080
Set Firefox HTTPS proxy to 127.0.0.1:8080
Press Ctrl+C to stop
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5. Point Firefox at the proxy
&lt;/h3&gt;

&lt;p&gt;In Firefox:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Go to Settings → General → Network Settings → Settings…&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Select Manual proxy configuration&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Set HTTPS Proxy to 127.0.0.1 port 8080&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Make sure localhost and 127.0.0.1 are not in the "No proxy for" list&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Click OK&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Visit any HTTPS site. Traffic will relay through MQTT to the ESP32 and out to the real server.&lt;/p&gt;

&lt;h3&gt;
  
  
  MQTT topics
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Topic&lt;/th&gt;
&lt;th&gt;Direction&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;req&lt;/td&gt;
&lt;td&gt;Python → ESP32&lt;/td&gt;
&lt;td&gt;Client-to-server bytes (open, data, close)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;res&lt;/td&gt;
&lt;td&gt;ESP32 → Python&lt;/td&gt;
&lt;td&gt;Server-to-client bytes (data)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Message format
&lt;/h3&gt;

&lt;h2&gt;
  
  
  All MQTT messages are JSON:
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"conn_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"host"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"www.example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"port"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;443&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"FgMBB2ABAAdc..."&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;type is one of open, data, close&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;data is base64-encoded raw bytes, or null for open/close&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;conn_id identifies the TCP connection (Firefox opens several in parallel)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>proxy</category>
      <category>esp32</category>
      <category>mqtt</category>
      <category>python</category>
    </item>
  </channel>
</rss>
