Even more detailed instructions
Because of incompatibility between Windows and Linux, the process to do this is a bit convoluted. For Windows 10, a quick and dirty way to forward X11 from remote Linux computer is specified here. Initial configuration as follows:
Local Windows
Remote Linux
/etc/ssh/sshd_config
:X11Forwarding yes
X11DisplayOffset 10
sudo systemctl restart sshd
xeyes
using sudo apt install x11-apps
To open an X11 connection:
$env:DISPLAY = "localhost:0"
ssh -Y [USER]@[IPADDR]
export DISPLAY=localhost:10
xeyes
To summarize as an example:
powershell> $env:DISPLAY = "localhost:0" powershell> ssh -Y justin@192.168.0.10 bash$ export DISPLAY=localhost:10 bash$ xeyes
After diving down the rabbit hole, I'm pretty confident about how this X11 forwarding works now. Written in the middle of the night though - I can't be trusted to write coherently.
This article specifically addresses how X11 is forwarded through an SSH connection, not how the protocol works. If interested in the graphical primitives, see this amazing series of articles by Jasper St. Pierre that even provide illustrations using a browser-based X server. A nice summary can also be found here, while the history of X is broadly described in Wikipedia's article.
Alternatives to X11 today (with network transparency) generally fall under the camp of VNC or RDP, which alternative windowing systems such as Wayland and Mir may provide (though their primary usage is still graphical display on the local host).
tl;dr How to setup X11 forwarding on Windows to remote Linux?
xauth
installed (in order to run X clients). Optionally install lightweight X11 apps for testing (e.g. x11-apps
in Ubuntu for xeyes
)/etc/ssh/sshd_config
on remote to include X11Forwarding yes
DISPLAY
environment variable with the value localhost:0.0
ForwardX11 yes
and ForwardX11Trusted yes
into SSH config for your remote machineXLaunch
with default valuesDo not perform X11 forwarding to untrusted computers!
X is a networked windowing environment that works on a server-client basis. An X server manages access to graphical displays and input devices on the host computer, to which X clients can connect to and send graphical data (primitives) / receive user input. Implementations of an X server tend to follow that of Xorg's reference implementation.
In a remote context, the remote system is typically a headless server providing the graphical data, while the user sits at the local system interacting with the graphical windows. The X server hence sits at the local system, while the X client runs on the remote server.
X11 is the 11th edition of the X Window System network protocol, which provides network transparency, allowing X clients and servers to communication over local networks (with Unix sockets or loopback TCP addresses) as well as remote networks (via TCP addresses).
This network protocol is often used due to its early adoption, and strong guarantees that many OS supports it.
xhost
is a program that provides host-based access control to the X server. By default the xhost
list is empty and only local connections from the same machine can connect to the X server. This is still a terrible default since it does not prevent other users on the same machine from connecting as well1). Worse still is the use of xhost +
which allows connections from anywhere.
xauth
is a program that allows import and export of a short key called a magic cookie that can be shared between clients and servers for communication2). The server generates the key and sends it to the client. The client attaches this key whenever graphical data is sent to the server, which the server validates before display.
A common and sufficient security mechanism is called MIT-MAGIC-COOKIE-1
, which generates a 128-bit cookie and stored in the .XAuthority
file in the user's home directory. The magic cookie can be forwarded via a number of mechanisms including the xrsh
program, or sent through an SSH tunnel which is what this article is about :)
xinit
is a program that manually starts an X server. The startx
script is the front-end for xinit
, and is typically called by a display manager (to activate the window manager). ~/.xinitrc
provides some level of configuration for the X server.
This Teleport article provides a well-rounded explanation, which I will attempt to modestly summarize.
We simplify the network components into the following:
Local machine sends an SSH connection request to the remote SSH server (sshd), together with a request to forward X11. If SSH server is configured to allow X11 forwarding, a $DISPLAY
environment variable is set on the remote.
Suppose:
$DISPLAY == localhost:13.0
on a remote Unix machine$env:DISPLAY == localhost:0
on a local Windows machine
then the (remote) SSH server listens to localhost:6013
for incoming X11 commands. The SSH connection proceeds on port 22 as usual. On the local machine, the SSH client will forward X11 commands to localhost:6000
port (which the X server is listening on).
By default -Y
bypasses all SECURITY
extensions in X11, and ignores missing .XAuthority
files (any X11 commands tunneled through the SSH connection is accepted by the X server). Fake authentication data is sent over the SSH tunnel. If the untrusted flag -X
is used:
xauth
first creates a dummy (not fake!) xauth key and sends it to the remote SSH server. Mapping is kept in .XAuthority
xauth
to store in remote user's .XAuthority
file as well (with appropriate read permissions)Once X11 forwarding is setup, the SSH connection proceeds as usual. The entire SSH connection can be viewed with:
ssh -vvv -X ...
When an X client sends a draw request (say by the user through the SSH connection), the previous xauth
key is attached to the X11 command, and both are sent to localhost:6013
(note that X clients without the .XAuthority
file simply send the request to the same port but without the key - this is the case for non-root users on the same machine). The SSH server receives this and forwards it to the SSH client, which then proceeds in one of two ways:
-X
is supplied during X11 forwarding and the xauth key sent by the remote does not match that in the .XAuthority
file, the request is dropped entirely.localhost:6000
as per specification above to the X server.-Y
, all requests, regardless of the xauth key attached, will be forwarded.-X
, the dummy xauth key is replaced with a real xauth key to the X server.A quick graphical brief of what SSH X11 forwarding is all about can be found here.
The SSH daemon (sshd) needs to support X11 forwarding, which is easily done by adding X11Forwarding true
in /etc/ssh/sshd_config
. Note also the configuration X11DisplayOffset 10
that modifies the TCP address / socket mapping, e.g. remote DISPLAY will start from localhost:10
instead of localhost:0
.
The DISPLAY environment variable first needs to be set to allow the SSH client to know where to forward X commands to. If no DISPLAY variable is set, the following debug message will appear in the SSH X11 forwarding.
debug1: X11 forwarding requested but DISPLAY not set
The variable (DISPLAY
for cmd.exe, $env:DISPLAY
for Powershell) can be set on a session-basis using set
, or made persistent via setx
. An alternative graphical method is using the "Edit the system environmental variables" GUI and add to either the user or system environment variables (necessary if using programs that automatically spin up their own sessions for SSH).
The format of DISPLAY is [HOSTNAME]:[DISPLAY].[SCREEN]
, where one (virtual) display can have multiple screens. Unix implementations of xauth assume the unix
socket as the default host - in Windows, the loopback addresses 127.0.0.1
(or alternatively localhost
) must be explicitly specified. The screen number is optional and 0
by default if not specified. Possible mapping of DISPLAY to corresponding TCP / Unix endpoints for display number of 13 (hence good to be precise).
$DISPLAY | Windows | Unix |
---|---|---|
localhost:13.0 | localhost:6013 | localhost:6013 |
:13 | unix:6013 (sad) | /tmp/.X11-unix/X13 |
unix:13.0 | - | /tmp/.X11-unix/X13 |
VcXsrv uses display number of 0 by default as well (when unspecified, i.e. -1, and no other X servers are running), so DISPLAY typically comes to localhost:0
on a Windows system.
The remote DISPLAY value is negotiated by the SSH server by default, do not modify it unilaterally. This provides a nice test if X11 forwarding is working, by checking with echo $DISPLAY
and see if anything appears.
SSH does not forward X11 by default, for security reasons. This can be enabled on a per-session basis using -X
/ -Y
in the SSH command, or globally by modifying the $HOME/.ssh/config
file to include the ForwardX11 yes
/ ForwardX11Trusted yes
lines. For example:
# ForwardX11 == '-X' / ForwardX11Trusted == '-Y' # Change to 'Host *' to apply to all hosts in config Host 192.168.1.2 ForwardX11 yes ForwardX11Trusted yes
For untrusted X11 forwarding, the magic cookie needs to be generated by xauth
first. The path to xauth
must be manually specified when using VcXsrv, like so:
# 'C:\Progra~1' is synonymous to 'C:\Program Files' Host 192.168.1.2 ForwardX11 yes XAuthLocation C:\Progra~1\VcXsrv\xauth.exe
In Visual Studio Code, note that using trusted X11 forwarding -Y
requires the untrusted X11 forwarding -X
flag as well.
For VcXsrv, the XLaunch.exe
program starts a wizard to allow specification of display number (default of -1 maps to display number 0, i.e. localhost:6000
listener), as well as other options. Do not enable "Disable remote access", since all X11 commands are internal (after tunneling through the SSH connection).
After doing the SSH X11 forwarding, run X clients such as xeyes
to test X11 forwarding works.
Quoting from OpenSSH implemented in Debian:
Debian-specific: X11 forwarding is not subjected to X11 SECURITY extension restrictions by default, because too many programs currently crash in this mode. Set the ForwardX11Trusted option to "no" to restore the upstream behaviour. This may change in future depending on client-side improvements.
Several Unix systems today use trusted X11 forwarding by default when using the -X
(untrusted X11 forwarding) flag. Using -Y
however is bad, since rogue X clients can do things like takeover the X server, or do keylogging, or take screenshots of local display. This article goes further to argue untrusted X11 forwarding is still dangerous since the X server is running as root.
Bottomline, do not use X11 if you cannot trust the remote. Use alternatives like VNC and RDP instead.
As of September 2022, Windows 10 still requires a third-party X server for X11 capabilities. Popular options include:
Some bugs on VcXsrv implementation that require notice:
xauth
uses unix
socket as the default endpoint, which is not available in Windows (should use the loopback address localhost
/127.0.0.1
instead).mktemp_proto
fails because of writes to TMPDIR
which is typically linked to /tmp
directory in Unix, and this directory does not exist in Windows. Can be circumvented by setting the TMPDIR
environment variable to any user-writable location (conventionally C:\Temp
).
The command executed by the X servers in particular when using -X
is as shown in the debug log:
debug2: client_x11_get_proto: C:\cygwin64\bin\xauth.exe -f C:\Temp/ssh-ZLktm6tmnewa/xauthfile generate unix:0.0 MIT-MAGIC-COOKIE-1 untrusted timeout 1260 2>/dev/null
Warning: untrusted X11 forwarding setup failed: xauth key data not generated
This fails for different reasons: sometimes /dev/null
is not defined (can create a C:\dev\null
file perhaps), sometimes it's NUL
instead (and fails on Powershell), the %TMPDIR%/ssh-[RANDOMSTRING]
directory may need to exist beforehand, etc. Issues have been raised in Win32-OpenSSH (and another), in VSCode Remote-SSH plugin, in OpenSSH Portable.