ruby on rails - Connecting to WSL2 server via local network
Solution:
See this video, it helped me:
https://www.youtube.com/watch?v=yCK3easuYm4
netsh interface portproxy add v4tov4 listenport=<port-to-listen> listenaddress=0.0.0.0 connectport=<port-to-forward> connectaddress=<forward-to-this-IP-address>
for example
netsh interface portproxy add v4tov4 listenport=3000 listenaddress=0.0.0.0 connectport=3000 connectaddress=172.30.16.3
Microsoft has published a little bit of information about this on their WSL1 to WSL2 comparison page
Answer
Solution:
First, you will need to open a port in your machine to be able to access it from your network.
netsh advfirewall firewall add rule name="Allowing LAN connections" dir=in action=allow protocol=TCP localport=5000
After you open the port (5000 in my case) you will need to make port forwarding from this port to the port that your app is listening on in the WSL.
netsh interface portproxy add v4tov4 listenaddress=0.0.0.0 listenport=5000 connectaddress=localhost connectport=<the port that your app is listening on>
Notice: I set the connectaddress to localhost not to the IP address of the WSL because by default the requests that go to localhost are forwarded to the WSL. By doing this you won't need to set the port forwarding every time you restart your machine because the IP address of the WSL is dynamic.
Answer
Solution:
Option 1: Use port forwarding
This one worked for me:
- Run the port forwarding by a script from xmeng1: https://gist.github.com/xmeng1/aae4b223e9ccc089911ee764928f5486
The firewall commands in that script didn't work on my system. So I deactivated the Windows firewall completely and use the following stripped version. (The final users will use a 3d party firewall anyway, so that's ok).
$remoteport = bash.exe -c "ifconfig eth0 | grep 'inet '"
$found = $remoteport -match '\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}';
if ($found)
{
$remoteport = $matches[0];
} else {
echo "The Script Exited, the ip address of WSL 2 cannot be found";
exit;
}
#[Ports]
#All the ports you want to forward separated by coma
$ports=@(123,456);
#[Static ip]
#You can change the addr to your ip config to listen to a specific address
$addr='0.0.0.0';
$ports_a = $ports -join ",";
for ($i = 0; $i -lt $ports.length; $i++)
{
$port = $ports[$i];
iex "netsh interface portproxy delete v4tov4 listenport=$port listenaddress=$addr";
iex "netsh interface portproxy add v4tov4 listenport=$port listenaddress=$addr connectport=$port connectaddress=$remoteport";
}
#Then so something, e.g.
#bash.exe -c "cd /g-inverter; ./start_docker.sh"
You may need to "apt install net-tools" for ifconfig in the script. There's also a solution with "ip addr" in the internet somewhere that does not need ifconfig" in a great thread, I haven't a link for here and now.
Caveat
This works only for TCP traffic. netsh interface portproxy does not support port forwaarding of UDP traffic.
Option 2: Bridge mode
Solution: Switch from NAT to Bridge mode
WSL2 comes by default in NAT mode. There the wsl2 system has another ip in another subnet than the host. The PC is from external peers only visible by the windows IP and the wsl2 ip/net is hidden/internal. So all traffic would need to be accepted by the windows IP and then forwarded to the wsl2 ip (port forwarding).
There is another mode called bridge mode. In bridge mode your network interface card will be shared to the wsl2 system, and it will get its own IP/Net in wsl2. So in effect your network card is shared to both systems (windows / wsl2) and will have two IPs, as if you'd have two systems with its own network card each. Cool thing: You will never have port conflicts when Windows uses the same port as well, as your wsl2 app (like 111).
Enable bridge mode
Open Hyper-V Manager as administrator
Select your pc, open Virtual Switch Manager
Select WSL
Set to external network
Select the network card the traffic runs through
Then login to wsl2 terminal and configure an IP address. E.g.
sudo ip addr add 192.168.0.116/24 dev eth0
You need to use another free IP (not your Windows IP). If your network has a DHCP server your wsl can get one by:
sudo ip addr flush dev eth0
sudo dhclient eth0
Caveat
I haven't elaborated yet, how to get DNS working in this scenario in case you want to still be able to access the internet (apt etc.). There's some documentation from MS written in /etc/resolv.conf and maybe executing what's written there and installing resolvconf (prior to all steps above, as you have no internet once you start to bridge) might do the trick.
Answer
Solution:
WSL2 exposes ports on the local interface (which is why in Windows you can access localhost:8080
when your 8080
service is running in WSL2), but they listen on 127.0.0.1
(which is why you can't access yourhostname:8080
on other computers your LAN).
There's a tool to fix this called WSLHostPatcher, which you can find here (download via the Releases section): https://github.com/CzBiX/WSLHostPatcher
WSLHostPatcher changes the behaviour to listen on all IPs, exposing any WSL2 services to all computers on your network.
- Download the latest release from https://github.com/CzBiX/WSLHostPatcher/releases (I tested v0.1.0)
- Unzip the file
- Run the
WSLHostPatcher.exe
- (Re)start your service in WSL2
The service should now be accessible via other computers on your network.
Answer
Solution:
Taking into consideration the above (correct) solutions this is a simplified one liner version that works for me:
- Use "ifconfig" to find your wsl2 public IP address. Have in mind that The Hyper-V Switch IP change everytime when the Windows reboot, so the Public IP in wsl2 also changes.
Run the following command using the IP found in the previous step in connectaddress parameter:
netsh interface portproxy add v4tov4 listenport= listenaddress=0.0.0.0 connectport= connectaddress=172.24.26.277
Parameters explanation:
listenport: the port that Windows will listen
listenaddress: the address that your Windows will listen. Usually 0.0.0.0 should do.
connectport: the port that your Linux Distro through wsl2 will listen.
connectaddress: the public IP of your Linux wsl2 instance (found in step 1)
Use cmd or PowerShell as Administrator to run the above command.
Answer
Solution:
Getting your WSL2 distro to behave like any other client on your network
Building on Roelofs suggestion no.2, here's what made everything tick in my case. I'm too fresh to just leave a comment unfortunately.
My starting point:
Win 10 Pro
Ubuntu under WSL2
(Docker with Linux containers)
My goal:
Getting an rtmp stream from a client on the network into and back out of an nginx server running on the Ubuntu machine.
Building the bridge
In my case, I could not get Hyper-V to set the bridge up properly. After selecting External network for the WSL switch in the Virtual switch section of Hyper-V Manager and hitting apply, it eventually failed with error 0x80070490. Don't know why and didn't have the time to investigate. WSL was not running and neither was the Docker service.
Instead, I just left the setting on Internal network and bridged the interfaces the manual way, under Network Connections (run->ncpa.cpl). In my case, the WiFi connection and vEthernet (WSL). Immediately after doing this, I lost internet connectivity and it took me an embarrassingly long time to find out that a reboot was needed. (Windows for once did not ask me to!)
Getting the VM in shape
After the reboot, I now had internet access from the host, the bridge was set to DHCP and had inherited the IP of the WiFi interface (192.168.1.246). Great.
The VM however was still getting the IP of the virtual switch (or however you want to view it, the random 172.x.x.x address that windows seems to assign to the switch as well as the VM).
Firing up Ubuntu, I decided to do a:
sudo ip addr flush dev eth0
Continuing with:
sudo dhclient eth0
threw a handful of errors at me since I was using the vanilla Ubuntu distro from Windows store, no systemd, no fun. Despite that, it did manage to add the IP of the bridge to eth0. As this was not very handy, I got rid of that with:
sudo ip addr del 192.168.1.248/24 dev eth0
but not before taking a sneak peek at the routing table:
user@vm:~$ route
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
default 192.168.1.1 0.0.0.0 UG 0 0 0 eth0
192.168.1.0 0.0.0.0 255.255.255.0 U 0 0 0 eth0
After deleting the old IP, I added a unique one from outside of my DHCP range:
sudo ip addr add 192.168.1.50/24 dev eth0
I checked the routing table again and the first entry was gone. At this stage I could ping LAN but not WAN.
Added the entry back with:
sudo ip route add default via 192.168.1.1 dev eth0
Pinging WAN IPs was now possible, but no DNS resolution. LMGTFM: Adding permanent DNS In case the solution goes missing, here it is, credit to non-static:
Create a file: /etc/wsl.conf. Put the following lines in the file [network] generateResolvConf = false In a cmd window, run wsl --shutdown Restart WSL2 Create a file: /etc/resolv.conf. If it exists, replace existing one with this new file. Put the following lines in the file nameserver 8.8.8.8 Or the IP of whatever DNS server you want to use, repeat step 3 and 4.
So, to conclude, check your routing and setup your DNS-conf properly. Unfortunately, the IP settings are reverted every time you restart WSL. If you are not ok with doing this manually every time there are discussions on how to automate it here and here. I haven't had the time to find my favorite.
Best regards Alexander
Answer
Solution:
Here's the full three steps I found worked for LAN development access to something running in WSL2 (Ubuntu 20 on Windows 10 20H2):
1. One time at the start: open Expo ports inbound in Windows Firewall
Windows firewall should be on — don't turn it off! —, and it should block inbound attempts by default.
The following will open the ports 19000–19001, inbound, but only on a network that you have configured as "private" (that's the -Profile Private
part, replace with Domain
if you're on a domain):
- Replace the
19000-19001
with the port(s) you need. - Replace the
DisplayName
with one that works for you. It's handy for finding the rule later.
(powershell as Admin)
New-NetFireWallRule -Profile Private -DisplayName 'Expo ports for LAN development' `
-Direction Inbound -LocalPort 19000-19001 -Action Allow -Protocol TCP
(You can check it after with Get-NetFirewallRule |Where-Object {$_.DisplayName -Match "Expo.*"}
— replace the -Match
arg with the DisplayName you chose)
2. Point portproxy
to WSL; Re-run "Each time WSL has a new IP address"
(I'm not sure yet how often the WSL IP address changes, but I suspect only a reboot would)
I saw stuff on the web, including other answers here, saying portproxy to connectaddress=127.0.0.1
but it did not work for me (WSL2, Windows 10 20H2).
I can't say why others found it worked, I can only say that repeated testing confirmed for me that 127.0.0.1
did not work, but the WSL IP address did work.
So here's a reusable command to auto set the connectaddress to the right WSL address:
(powershell — just for the easy inline Trim()
— as Admin)
netsh interface portproxy add v4tov4 listenport=19000 listenaddress=0.0.0.0 `
connectport=19000 connectaddress=$($(wsl hostname -I).Trim());
netsh interface portproxy add v4tov4 listenport=19001 listenaddress=0.0.0.0 `
connectport=19001 connectaddress=$($(wsl hostname -I).Trim());
3. (Optional) If your WSL needs to know your dev machine LAN IP Address
(e.g. QR-Code mobile apps like Expo)
You'll need to re-run the following inside WSL "Each time dev host has a new IP address"
- This is the one that probably changes most often. Your laptop local network IP certainly changes when you change networks (e.g. home/office) — and can change at other times too.
Fortunately it's also pastable / aliasable:
WSL2 shell
netsh.exe interface ip show address "Wi-Fi" | grep 'IP Address' | sed -r 's/^.*IP Address:\W*//'
# e.g. for Expo, I used:
export REACT_NATIVE_PACKAGER_HOSTNAME=$(netsh.exe interface ip show address "Wi-Fi" | grep 'IP Address' | sed -r 's/^.*IP Address:\W*//')
echo Meteor will use dev machine IP address: $REACT_NATIVE_PACKAGER_HOSTNAME
I "wish I didn't have to re-run things and it could all be automated",
but that same laziness makes me happy to at least have command 2 (and 3) easy to "rerun" and consistently get the LAN access I need to my WSL2-hosted service.
Answer
Solution:
install the ssh server on WSL
Find some ssh client on windows
Execute on windows: (Windows's IP with 192.168.x.x and WSL ip with 172.28.x.x)
ssh -L 192.168.x.x:3000:172.28.x.x:3000 someuser@172.28.x.x
if it doesn't work, try to using another local port such as (192.168.x.x:3001:172.28.x.x:3000)
Answer
Solution:
Another solution is to bridge WSL2 directly to the relevant network adapter. I tried doing it within Hyper-V, but couldn't get it to work. However, what did work was going to Control Panel\Network and Internet\Network Connections
, selecting the NIC (Ethernet 3
in my case) and vEthernet (WSL)
and bridging them by right clicking and selecting "Bridge Connections":
You should end up with something like this:
After the bridge is set up let Windows do its thing for a minute or so (seriously!) before continuing.
Next open WSL and have it run dhcp to get a new address:
sudo ip addr flush dev eth0 && sudo dhclient eth0
At this point, both WSL and Windows should have connectivity. Check by pinging some IP address like 1.1.1.1 or 8.8.8.8. If that doesn't work, dismantle the bridge and try again. Remember to wait for a minute or two while Windows configures everything.
Additionally, make sure that eth0
has an address from your LAN (ip addr sh eth0
).
The problem now is that WSL probably can't resolve domain names since WSL creates /etc/resolv.conf
on startup which points to the virtual gateway which is no longer there. This can be solved by asking wsl not to create /etc/resolv.conf
by appending the following to /etc/wsl.conf
(create the file if it doesn't exist):
[network]
generateResolvConf=false
Then restart wsl by restarting the LxssManager
windows services.
The only thing that remains is setting up alternative DNS servers within WSL. Instead of the generated /etc/resolv.conf
, just create a /etc/resolv.conf
with something like:
nameserver 1.1.1.1
nameserver 1.0.0.1
That's it! WSL should now be bridged to your network and have its own unique address on it.
Answer
Solution:
This worked for me using wsl2. I had an app in wsl2 listening on port 4200
. In an administrator elevated powershell:
Add proxy
netsh interface portproxy add v4tov4 listenport=8080 connectport=4200 connectaddress=127.0.0.1
Note the lack of listenaddress=
argument and the different listening port 8080
instead of 4200
Add firewall rule
netsh advfirewall firewall add rule name= "Open Port 8080" dir=in action=allow protocol=TCP localport=8080
Then on my wifi-connected mobile phone visit http://192.168.1.2:8080
Cleaning up
You can delete your proxy with netsh interface portproxy delete v4tov4 listenport=8080
and your firewall rule with netsh advfirewall firewall delete rule name="Open port 8080"
Notes
Obviously change to your ports to your case and 192.168.1.2
to your PCs IP on your LAN. Use ipconfig
to find it. You may need to make sure the code you're running in wsl2 is bound on 0.0.0.0
.
Good luck.
Answer
Solution:
WSL2-PortForward.cmd
@ECHO OFF
REM ** Set WSL Distro, Distro Port, Host Port
SET LXDISTRO=WinKDE
SET WSL2PORT=3389
SET HOSTPORT=13389
REM ** Reset any existing port proxies and delete any stale firewall rule
NETSH INTERFACE PORTPROXY RESET & NETSH AdvFirewall Firewall delete rule name="%LXDISTRO% Port Forward" > NUL
REM ** Get IP from WSL2 instance and write it out to IP.tmp
WSL -d %LXDISTRO% -- ip addr show eth0 ^| grep -oP '(?^<=inet\s)\d+(\.\d+){3}' > IP.TMP
REM ** Read IP.tmp back into batch variable
SET /p IP=<IP.TMP
REM ** Create the port proxy
NETSH INTERFACE PORTPROXY ADD v4tov4 listenport=%HOSTPORT% listenaddress=0.0.0.0 connectport=%WSL2PORT% connectaddress=%IP%
REM ** Create a new firewall rule for the port listening on the LAN
NETSH AdvFirewall Firewall add rule name="%LXDISTRO% Port Forward" dir=in action=allow protocol=TCP localport=%HOSTPORT% > NUL
REM ** Summary
ECHO WSL2 Virtual Machine %IP%:%WSL2PORT%now accepting traffic on %COMPUTERNAME%:%HOSTPORT%
Output::
WSL2 Virtual Machine 172.17.109.95:3389 now accepting traffic on MYCOMPUTER:13389
Answer
Solution:
Regarding bridge mode - Windows would prevent of modifying WSL virtual switch (access denied error) when any Linux distribution is running. From the other hand WSL switch is created when first Linux distro is started. I did as follows:
- Started wsl
- Left wsl shell and check that all distros are stopped ( wsl -l -v)
- Modified WSL virtual switch
- Started wsl again
Answer
Solution:
Finally I've got a solution that has to be run just once after the Windows host boot, works with multiple WSL2 instances at the same time, and does not have to be re-run when a WSL2 instance restarts!
Drawback: cannot listen on 0.0.0.0; you have to specify IPs or adapters for listening explicitly. (This version listens only on one IP address, but can be easily extended to use a list).
$ports = @(8000,8001); # ports to forward
$iface = "Ethernet"; # alias of the Adapter to listen on
# if your host has a fixed IP, you can specify it here explicitly
# and remove the ping-and-if safeguard;
# but 0.0.0.0 won't work!
$external_ip = (Get-NetIPAddress -AddressFamily IPv4 -InterfaceAlias $iface).IPv4Address;
ping -n 1 "$external_ip" > $null
if( !$? ) {
echo "error: cannot determine the external IP address";
exit 1;
}
$firewall_rule_name = "WSL2 Firewall Hole"
Remove-NetFireWallRule -DisplayName "$firewall_rule_name"
New-NetFireWallRule -DisplayName "$firewall_rule_name" -Direction Outbound -LocalPort $ports -Action Allow -Protocol TCP
New-NetFireWallRule -DisplayName "$firewall_rule_name" -Direction Inbound -LocalPort $ports -Action Allow -Protocol TCP
ForEach( $port in $ports ) {
netsh interface portproxy delete v4tov4 listenport=$port listenaddress=$external_ip
netsh interface portproxy add v4tov4 listenport=$port listenaddress=$external_ip connectport=$port connectaddress=127.0.0.1
}
Credits:
- The starting point was this script by edwindijas on WSL Github issue #4150.
- The idea of using
localhost
instead of obtaining the IP address of a running WSL2 instance was gleaned from this answer by Charl Botha here.
The original edwindijas's script didn't work for me because I run WSL2 under a limited user, the script has to run as admin, and running bash
or wsl -e
as admin gets a new WSL2 instance with a different IP address.
Answer
Solution:
Here's a solution that is perhaps simpler than many of the others that have been offered. You need a WSL1 instance with socat
installed in addition to the WSL2 instance that you're using.
On the WSL1 instance, start socat
like this:
socat -d TCP-LISTEN:3000,reuseaddr,fork TCP:localhost:3000
When you start socat like this, Windows will ask if you want to allow network access to the port. Confirm.
Make sure your puma binds to port 3000 on all interfaces.
You will now be able to access your dev server from the LAN. What's happening here is that socat is forwarding requests to 3000 on your ethernet port to port 3000 on localhost, which will magically get forwarded by Windows to WSL2.
(I'm currently using this exact setup to access my Django dev server on WSL2 from an iOS app on my LAN.)
Answer
Solution:
So, I struggled with this for a while the last two days, nothing seemed to work, in this thread or elsewhere. I finally got it worked out though by piecing together a few things I've seen, and I figured I'd post my solution here in case it helps anyone. A few caveats though. First, I'm definitely no Linux guru, so "it works" is just about my real only consideration :) Second, this is on a home dev server, which means that security isn't my biggest concern (the server isn't exposed outside my LAN in any way), so I did some things that I wouldn't do on a real, important machine. Finally, I didn't need this to work all the time, meaning I'm okay with having to do something myself after a server reboot to get it all working.
With all that in mind, the first step was, as malt above describes, bridging the WSL adapter with the server's physical adapter. In my case, the server has a static IP, so I configured the Network Bridge adapter that results with a static IP, gateway and DNS servers for IPv4.
After that, I wrote the following script named startup_tasks.sh in my home directory:
#!/bin/bash
clear
echo Running startup tasks...
# Clear existing IP config
sudo ip addr flush eth0
echo .
sleep 2
# Add static IP
sudo ip addr add 192.168.1.100/24 dev eth0
echo .
sleep 2
# Remove existing default gateway
sudo ip route del default
echo .
sleep 2
# Add default gateway
sudo ip route add default via 192.168.1.1
echo .
sleep 2
# Remove existing name resolution config file
sudo rm /etc/resolv.conf
echo .
sleep 2
# Create name resolution config file
sudo touch /etc/resolv.conf
echo .
sleep 2
# Update permissions on file so we can edit it
sudo chmod 777 /etc/resolv.conf
echo .
sleep 2
# Add name servers
sudo echo "nameserver 8.8.8.8" >> /etc/resolv.conf
echo .
sleep 2
sudo echo "nameserver 1.1.1.1" >> /etc/resolv.conf
echo .
sleep 2
echo - Static IP set
echo .
echo ...done!
For this to work, I also had to ensure my user account has sudo rights without a password, so this was added to the sudoer's file:
devuser=(ALL) NOPASSWD:ALL
I simply run that script after a server reboot and the result is WSL gets a static IP address and all the servers I run (all in Docker containers, though I removed all that from the script above) are accessible.
For me, it's that easy.
I've also considered writing a simple .vbs file and dropping it in my startup folder to run that script after a reboot automatically. That should work fine and make it all completely automatic, but I just haven't done it yet.
Answer
Solution:
for someone who may have been as desperate as me
I tried so many ways, I was desperate things get messed up, I can't even keep accessing localhost:3000 as default (at first, I could)
I converted wsl2 to wsl and converted it back to wsl2 (look like I reset all config for wsl2, anyway, I'm not sure) then I just added portproxy and did nothing more, it worked
PS C:\Windows\system32> netsh interface portproxy add v4tov4 listenport=3001 listenaddress=0.0.0.0 connectport=3001 connectaddress=localhost
PS C:\Windows\system32> netsh interface portproxy add v4tov4 listenport=3000 listenaddress=0.0.0.0 connectport=3000 connectaddress=localhost
PS C:\Windows\system32> netsh interface portproxy show all
Listen on ipv4: Connect to ipv4:
Address Port Address Port
Answer
Answer
Answer
Answer
Answer
Answer
Solution:
Unfortunately, for me, this is a simplified case because my Home network is defined by my Router. Since my home devices don't communicate using the back-to-back connection and my network is protected by the Router's firewall I have to respect the Router's configuration.
- There is a list of DNS's - 8.8.8.8, 8.8.4.4, 9.9.9.9 which back my ISP DNS. It means that resolv.conf has to be crafted. nslookup reflects the correct configuration.
- It is possible to configure DHCP relay to external DHCP
- I can define and config how devices obtain dynamic IP including the static lease. It assumes that the device uses the correct DHCP IP address.
- I can configure port forwarding for known to the router devices, of course.
Answer
Solution:
Try -b 0.0.0.0
That's what works on other OSes