{"id":108,"date":"2026-07-25T07:55:23","date_gmt":"2026-07-25T07:55:23","guid":{"rendered":"https:\/\/demensdeum.com\/blog\/2026\/07\/25\/port-forwarding-chisel\/"},"modified":"2026-07-25T07:55:23","modified_gmt":"2026-07-25T07:55:23","slug":"port-forwarding-chisel","status":"publish","type":"post","link":"https:\/\/demensdeum.com\/blog\/2026\/07\/25\/port-forwarding-chisel\/","title":{"rendered":"Port forwarding between clients via Chisel: a stripped-down tunnel without L3"},"content":{"rendered":"<p>When two devices are behind NAT or strict firewalls and cannot \u201csee\u201d each other directly, a VPN seems to be the standard solution. But a full-fledged L3 tunnel (like WireGuard or OpenVPN) is often redundant: it requires root rights, setting up virtual interfaces, and can conflict with existing routes.<\/p>\n<p>In such cases, it is convenient to use <b>Chisel<\/b> &#8211; a TCP\/UDP tunnel that runs on top of HTTP and uses WebSockets for data transfer. In this note I will show how to \u201cforward\u201d a port from one client to another through an intermediate server.<\/p>\n<h2>How does it work?<\/h2>\n<p>Imagine the situation: you have <b>Client A<\/b> (for example, your home server), <b>Client B<\/b> (your work laptop) and a <b>VPS<\/b> with a public IP address. Clients A and B can access the VPS, but not each other.<\/p>\n<p>The forwarding scheme will look like this:<br \/>\n1. <b>Client A<\/b> connects to the VPS and opens a \u201creverse\u201d port on the server. Now everything that comes to port X of the server goes to port Y of Client A.<br \/>\n2. <b>Client B<\/b> connects to the VPS and forwards port Z from its local machine to port X of the server.<br \/>\n3. As a result, Client B accesses <b>localhost:Z<\/b> and ends up on <b>Client A:Y<\/b>.<\/p>\n<p>This approach is one of the solutions in cases where communication with a VPS does not implement an L3 layer or there is no ability to configure routing between clients. We work exclusively at the application and port level.<\/p>\n<h2>Step 1: Start the server<\/h2>\n<p>On your VPS, just run Chisel in server mode. The <code>--reverse<\/code> flag is required to allow clients to open ports on the server side.<\/p>\n<div class=\"hcb_wrap\">\n<pre class=\"prism undefined-numbers lang-unknown\" data-lang=\"unknown\"><code>chisel server --port 8080 --reverse\n<\/code><\/pre>\n<\/div>\n<h2>Step 2: Connecting Client A (Source)<\/h2>\n<p>Let&#8217;s say Client A wants to open access to his local web server on port 3000. He connects to the VPS and says: \u201creserve port 2000 on the server and forward it to me to 3000.\u201d<\/p>\n<div class=\"hcb_wrap\">\n<pre class=\"prism undefined-numbers lang-unknown\" data-lang=\"unknown\"><code>chisel client vps-ip:8080 R:2000:127.0.0.1:3000\n<\/code><\/pre>\n<\/div>\n<p>Now port 2000 on the VPS (on the loopback interface) leads to Client A.<\/p>\n<h2>Step 3: Connecting Client B (Consumer)<\/h2>\n<p>Now Client B wants to access this resource. It connects to the same VPS and forwards its local port 8080 to port 2000 of the server.<\/p>\n<div class=\"hcb_wrap\">\n<pre class=\"prism undefined-numbers lang-unknown\" data-lang=\"unknown\"><code>chisel client vps-ip:8080 8080:127.0.0.1:2000\n<\/code><\/pre>\n<\/div>\n<p>Ready! Now, when you open <code>http:\/\/localhost:8080<\/code> on Client B, you will see the service running on Client A.<\/p>\n<h2>Safety and nuances<\/h2>\n<p>Chisel supports authentication via the <code>--auth<\/code> flag, which is highly recommended when working through public servers. You can also use TLS certificates to encrypt traffic.<\/p>\n<p>The main advantage of this approach is that there is no need for TUN\/TAP devices and complex routing tables. This is a stripped-down tunnel that does exactly one thing: binds ports via a WebSocket connection. This even works through corporate proxies if you configure Chisel to work through port 443.<\/p>\n<h2>Output<\/h2>\n<p>Chisel is a utility for specific network tasks. When you need to forward ports between isolated nodes without setting up a full-fledged VPN, a combination of forward and reverse tunnels through a relay server turns out to be a completely viable solution.<\/p>\n<h2>Links<\/h2>\n<p><a href=\"https:\/\/github.com\/jpillora\/chisel\" rel=\"noopener\" target=\"_blank\">https:\/\/github.com\/jpillora\/chisel<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>When two devices are behind NAT or strict firewalls and cannot \u201csee\u201d each other directly, a VPN seems to be the standard solution. But a full-fledged L3 tunnel (like WireGuard or OpenVPN) is often redundant: it requires root rights, setting up virtual interfaces, and can conflict with existing routes. In such cases, it is convenient [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[8],"tags":[],"class_list":["post-108","post","type-post","status-publish","format-standard","hentry","category-notes"],"translation":{"provider":"WPGlobus","version":"3.0.3","language":"en","enabled_languages":["en","ru","zh","de","ja","fr","es","pt","hi"],"languages":{"en":{"title":true,"content":true,"excerpt":false},"ru":{"title":true,"content":true,"excerpt":false},"zh":{"title":true,"content":true,"excerpt":false},"de":{"title":true,"content":true,"excerpt":false},"ja":{"title":true,"content":true,"excerpt":false},"fr":{"title":true,"content":true,"excerpt":false},"es":{"title":false,"content":false,"excerpt":false},"pt":{"title":true,"content":true,"excerpt":false},"hi":{"title":true,"content":true,"excerpt":false}}},"_links":{"self":[{"href":"https:\/\/demensdeum.com\/blog\/wp-json\/wp\/v2\/posts\/108","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/demensdeum.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/demensdeum.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/demensdeum.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/demensdeum.com\/blog\/wp-json\/wp\/v2\/comments?post=108"}],"version-history":[{"count":0,"href":"https:\/\/demensdeum.com\/blog\/wp-json\/wp\/v2\/posts\/108\/revisions"}],"wp:attachment":[{"href":"https:\/\/demensdeum.com\/blog\/wp-json\/wp\/v2\/media?parent=108"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/demensdeum.com\/blog\/wp-json\/wp\/v2\/categories?post=108"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/demensdeum.com\/blog\/wp-json\/wp\/v2\/tags?post=108"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}