I will split the answer into two parts:
SSH Port Forwarding
Firstly, create an SSH tunnel between Windows client and Linux server. Windows 10 or Windows 11 comes with OpenSSH client pre-installed. Open command prompt or Power-shell and execute the following command:
ssh -fnN -L 5901:127.0.0.1:5901 user@linux_server_public_ip
-N Do not execute a remote command. This is useful for just forwarding ports (protocol version 2 only)
-n Redirects stdin from /dev/null (actually, prevents reading from stdin). This must be used when ssh is run in the background. A common trick is to use this to run X11 programs (like VNC) on a remote machine. The ssh program will be put in the background. (This does not work if ssh needs to ask for a password or passphrase; see also the -f option.). To overcome password prompt issue, use password-less SSH (aka SSH public key authentication)
-f Requests ssh to go to background just before command execution. This is useful if ssh is going to ask for passwords or passphrases, but the user wants it in the background. This implies -n. The recommended way to start X11 programs at a remote site is with something like ssh -f host xterm.
-L [bind_address:]port:host:hostport - Specifies that the given port on the local (client) host is to be forwarded to the given host and port on the remote side.
Connect to VNC server
From Windows, you can now connect to VNC server through 127.0.0.1 and port 5901
A simple test
To demonstrate that an SSH tunnel is working and possibly identify any firewall issues, you can test using simple web server hosted on your Linux server. I will use python3 simple web server for this demonstration.
# Go to temporary directory or any directory that has no sensitive data $ cd /tmp # Create a test HTML file $ echo "test web site" > index.html # Start the web server $ python3 -m http.server # By default server will listen on port 8000 and all interfaces.
From your windows laptop, open a web browser and go to Linux server's public IP on port 8000, http://linux_server_public_ip:8000. If you get a test page on your browser, then proceed to next step.
# On Linux server, stop the python simple web server # Then start the python web server but this time bind to loopback address `127.0.0.1` $ python3 -m http.server -b 127.0.0.1
Since the web server is now bound to loopback address, it is no longer directly reachable from outside (meaning that web server is no longer directly exposed). This is where SSH tunneling comes in.
On Windows laptop, start an SSH tunnel (keep port 8000 in mind)
ssh -fnN -L 8000:127.0.0.1:8000 user@linux_server_public_ip
Once the SSH tunnel is up, you can open a web browser once again, but this time visit http://127.0.0.1:8000
References:
Side Note: I suggest that you continue using 127.0.0.1 and shy away from using localhost. By default, when you ping localhost on a Windows a machine, the response comes back with an IP address of ::1 instead of 127.0.0.1. By default, IPv6 takes preference over IPv4, and this might break your SSH tunnel.