Short-answer for WSL1:
Configure your firewall rules once, and everything should "just work". This is the easiest method.
Short-answer for WSL2:
Once you have things configured properly, you can initiate port-forwarding very easily with a single command from Ubuntu/WSL2:
ssh -f -N -R 8080:localhost:8080 "$(hostname).local"
Details on how to configure are below. It's not necessarily easy, but it's all just one-time setup.
(Much) more detail:
As noted by Steeldriver in the comments, WSL1 and WSL2 behave differently here.
Side-note just to clarify terminology (since it differed slightly in the comments): "WSL" refers to the subsystem itself which controls both versions -- WSL1 and WSL2.
Side-note 2. Please don't let the length of this post scare you away. I'm just very detailed. Considering the related Github issue is up to (currently) 536 comments, I think I'm fairly concise by comparison ;-).
WSL1
WSL1 is certainly the easier use-case. Since it's a "translation layer" between Linux syscalls and the Windows kernel, it actually utilizes the "real" Windows network interface(s). For this reason, you are able to connect directly from another device on the network to a port in WSL1.
You likely do still need a firewall rule, however. I'll cover that in the WSL2 answer, since it's common to both. See the New-NetFirewallRule
command in the WSL2 section. Just run that one command (one time) for the firewall rule.
However, if your web app doesn't require WSL2 (and most do not), it's usually far easier to run it from a WSL1 instance (or at least it used to be). That's what most people end up doing. On the other hand, with the new (to me at least) WSL2/SSH method I propose below, it's not nearly as painful as it used to be to do this in WSL2. I'll leave it to you which route to choose. At this point, I consider either to be equally valid.
If you choose to go with WSL1, I would recommend keeping two Ubuntu instances around -- One with WSL1 and another with WSL2. That's what I do.
To copy your existing WSL2 instance to a new WSL1, exit the existing one and, from PowerShell:
# Adjust base path as desired
$WSL_ROOT = "$env:USERPROFILE\WSL"
$WSL_IMAGE_NAME = "$(get-date -UFormat `"%Y-%m-%d`") Ubuntu Backup.tar"
mkdir -p "$WSL_ROOT\images"
mkdir -p "$WSL_ROOT\instances\Ubuntu_WSL1"
cd $WSL_ROOT
wsl -l -v
# Confirm name of distribution - If it isn't "Ubuntu", adjust the following line as needed
wsl --export Ubuntu "$WSL_ROOT\images\$WSL_IMAGE_NAME"
wsl --import Ubuntu_WSL1 .\instances\Ubuntu_WSL1\ .\images\$WSL_IMAGE_NAME --version 1
wsl ~ -d Ubuntu_WSL1
At this point, you'll be in an Ubuntu WSL1 instance, but you'll be root, since WSL doesn't "remember" the default username for --import
'd instances. Follow my "Method 1" from this answer to set your default username.
At this point, you have two WSL Ubuntu instances, one for WSL1 (Ubuntu_WSL1
) and another for WSL2 (likely Ubuntu
or perhaps Ubuntu-20.04
). If you are using Windows Terminal, it will detect both of them and create profiles for launching. Or you can always launch manually using wsl ~ -d <distroname>
.
Another option would be to simple convert to WSL1 and use it exclusively. The steps for this are similar to copying it, since you likely still want to back up. Again, exit the instance, and from PowerShell:
# Adjust base path as desired
$WSL_ROOT = "$env:USERPROFILE\WSL"
$WSL_IMAGE_NAME = "$(get-date -UFormat `"%Y-%m-%d`") Ubuntu Backup.tar"
mkdir -p "$WSL_ROOT\images"
cd $WSL_ROOT
wsl -l -v
# Confirm name of distribution - If it isn't "Ubuntu", adjust the following line as needed
wsl --export Ubuntu "$WSL_ROOT\images\$WSL_IMAGE_NAME"
wsl --set-version Ubuntu 1
There is no need to reset the default username in this case.
WSL2
WSL2 starts to get a lot more complicated. While there's a link in the comments to a doc on how to do it, I'll point you to the original Github issue and comment that really started people in this direction.
There are two real issues that have to be solved to get this working in WSL2:
First, the WSL2 network is a virtual network (actually a Hyper-V vNIC). It doesn't reside on your "office network", as you note in your question. You first need to have some way of telling the Windows host to route the packets to your WSL2 virtual network for that port.
That forwarding is complicated by the fact that the virtual WSL2 network address changes on every reboot (or wsl --shutdown
). That means (at least with the method documented in the comments and that Github issue) that you have to repeat the process of:
- Finding the WSL2 IP address
- Deleting old firewall rules
- Deleting old forwarding rules
- Creating new firewall rules
- Creating new forwarding rules
... each time you reboot. What a pain, right?!
So I'm going to propose what I believe is an easier method. You can still fall back to the other method if you desire. This has some slightly complicated set-up, but almost all of it only has to be done once.
This method utilizes SSH to provide the port forwarding. Since this is initialized from the WSL2-end, it has several advantages:
First, it can be done as part of the same step when you run your application (web server). You don't mention the architecture/language of the app, but I'll assume one of the most common -- Node. If that's the case, you can likely even include it in your npm run
script. There's almost certainly a technique that will work for any architecture.
Most importantly, it does not need the IP address of the WSL2. This avoids the need to do 4 of those above steps each time you reboot.
So, here we go. First, there's the "one time" setup:
Enable Windows OpenSSH server. You can follow the Microsoft instructions, but I'll summarize here. Start by opening a PowerShell prompt as Administrator, then:
Add-WindowsCapability -Online -Name OpenSSH.Client~~~~0.0.1.0
Add-WindowsCapability -Online -Name OpenSSH.Server~~~~0.0.1.0
Set-Service -Name sshd -StartupType 'Automatic'
This should automatically create the SSH forwarding rules. Again, see the doc if you run into any trouble.
Edit C:\ProgramData\ssh\sshd_config
and make sure that GatewayPorts yes
is not commented out. I believe it is disabled by default.
Back in your admin PowerShell, run:
Start-Service sshd
You don't mention the port number that your web app runs as, so I'll choose 8080
for the sake of these examples. Adjust as needed. Still in admin PowerShell, run:
New-NetFirewallRule -DisplayName 8080 -Direction Inbound -LocalPort 8080 -Protocol TCP -Action Allow -Profile Private
This:
- Allows inbound TCP traffic
- On port 8080
- From devices on the private network
If your network is set Public
, drop the -Profile Private
Exit your admin PowerShell
With that out of the way, everything's in place. To start the forwarding at this point, execute the following from Ubuntu/WSL2:
ssh -f -N -R 8080:localhost:8080 "$(hostname).local"
Use your Windows username and password.
At this point, you should have access to your web app from another computer (or phone, or whatever) on the same office network.
Explanation:
- Connects from Ubuntu/WSL2
- To the OpenSSH Server that we set up
- Using the
"$(hostname).local"
which (should) always find the correct DNS name via mDNS (explanation in this answer.
- It does not allocate a terminal (
-N
) and runs in the background (-f
) after requesting the login credentials
- It tells the remote SSH host (Windows) to forward traffic received on its port 8080 to the local (WSL2) port 8080.
- Because we specified
GatewayPorts yes
in the server config, this means that it will extend that forwarding to other hosts on the network as well.