Background
The reason for this is that WSL2's network runs in virtual switch created by the Hyper-V hypervisor. The WSL2 VM's network interface runs (NAT'd) inside (or "behind", depending on how you look at it), the Windows host itself.
This means that services running inside WSL2 are essentially running in a separate network that your other PCs/devices know nothing about.
For the Windows host itself, WSL2 provides something known as "localhostForwarding", which is enabled by default. As you've noticed, when you access localhost:4040
from your local Windows, it first checks to see if something is bound to that port in Windows, and if not, turns around and forwards it to port 4040 in the WSL2 VM.
There are at least four ways now that you can work around this:
Option 1: WSL1 (best/easiest option for many, but not all, use-cases)
You might think with WSL2 released for almost two years now, it would be better at everything than WSL1, but that's simply not the case. There are still scenarios where WSL1 works better, and network access is one of those.
If the application you are running does not require all of the features of the Linux kernel, then it will likely run under WSL1 just fine. WSL1 provides a "syscall translation layer" that maps Linux APIs to Windows. It's like a "reverse WINE" in that way. One of the advantages of that approach is that the network calls actually use the Windows network interface. So when an app binds to port 4040 in WSL1, it actually does so on the Windows interface and is accessible from other machines on the network.
You can convert a WSL2 instance to WSL1 in PowerShell (or CMD, but really, why? ;-) via:
wsl -l -v
# Confirm the distribution name is Ubuntu, adjust the next commands otherwise
wsl --export Ubuntu .\path\to\ubuntu_backup.tar # Optional, and you put the backup where ever you want
wsl --set-version Ubuntu 1
Alternatively, you can create a second copy of Ubuntu and make it WSL1:
wsl -l -v
# Confirm the distribution name is Ubuntu, adjust the next commands otherwise
wsl --export Ubuntu .\path\to\ubuntu_backuptar
mkdir .\location\for\new\Ubuntu\
wsl --import UbuntuWSL1 .\location\for\new\Ubuntu .\path\to\ubuntu_backup.tar --version 1
Then launch the distribution via wsl ~ -d UbuntuWSL1
and follow the instructions in my Super User answer to set your username in the new copy.
If you find that your app doesn't work under WSL1 for some reason, read on ... There are additional options.
Option 2: SSH reverse tunnel forwarding
This is my personal preference for easily forwarding connections to WSL2 distributions such as Ubuntu.
One-time setup:
Install Windows OpenSSH Server via the instructions found in the official doc.
Edit C:\ProgramData\ssh\sshd_config
and uncomment GatewayPorts yes
Open an Administrative PowerShell.
Restart the Windows SSH server with Restart-Service sshd
Add a firewall rule to allow your port (4040 in your case):
New-NetFirewallRule -DisplayName 4040 -Direction Inbound -LocalPort 4040 -Protocol TCP -Action Allow
# Set DisplayName to whatever you need to remember it
Note: If you need to remove this rule, that would be Remove-NetFirewallRule -DisplayName 4040
(or whatever you named it).
Then, in Ubuntu/WSL2, to establish forwarding, run:
ssh -fN -R 4040:localhost:4040 $(hostname).local
Use your Windows (not Ubuntu/WSL2) username and password.
Explanation:
ssh
to $(hostname).local
, which should resolve to your Windows Computer Name + .local
, which should resolve to the correct IP address of the Hyper-V switch. See my Stack Overflow answer on the topic of mDNS resolution.
-f
will place the ssh session into the background after requesting a password (if needed)
-N
says "don't start a terminal or run any command on the remote host, just set up the connection and forwarding".
-R 4040:localhost:4040
is the key, which setups up a reverse tunnel so that when someone connects to port 4040 on the Windows-end, the connection is forwarded to Ubuntu/WSL2's localhost also on port 4040.
At that point, you have one command that you can use to initiate the forwarding, which you can place into a script that starts your web app as well.
Option 3: Windows port forwarding
While you can set up the forwarding from the Windows side, that's made much more difficult by the fact that WSL2/Ubuntu's IP address is set dynamically and changes on every reboot (or shutdown of WSL).
I'll point you to the Microsoft Doc for the exact command, but realize that you'll have to determine the IP address to use each time you run it.
You can script it (see example in the original Github issue on the topic), but it's just so much more complicated than the SSH method that I don't see the point.
Option 4: WSL2 Preview with Bridge
This may be the best option for some (but not you), if they are comfortable running a Preview release of WSL2 and have Windows 11 Pro/Education (or higher), which you don't. For me personally, a separate SSH bug/limitation in the Preview release keeps me from running it as my daily-driver.
On Windows 11, you can install an updated, Preview-release WSL through the Microsoft Store. This version will replace the existing WSL, but you can always revert to the original by uninstalling the Store app.
This Preview has a new feature that allows you to specify a specific Hyper-V network switch to use with WSL2, and you can create and configure that network switch to bridged.
While I haven't personally tested it (I'll get around to it at some point), you can follow the instructions in this blog post (not mine). The original announcement by one of the Microsoft developers is buried somewhere in the (currently) 650+ comments on the Github issue I linked above.