Table of Contents

X11 Forwarding

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

  • Download an X11 server, e.g. VcXsrv, run it, and set the display number to 0.

Remote Linux

  • SSH into the remote server and verify in /etc/ssh/sshd_config:
    • X11 forwarding is enabled: X11Forwarding yes
    • Check X11DisplayOffset value, e.g. mine says X11DisplayOffset 10
  • Restart sshd if configuration changed, e.g. sudo systemctl restart sshd
  • Download any X-based software to test X11 connection later
    • e.g. xeyes using sudo apt install x11-apps

To open an X11 connection:

  • In Windows, open Powershell and load the display variable with the initial display number, e.g. $env:DISPLAY = "localhost:0"
  • SSH into the remote server with insecure X11 forwarding, e.g. ssh -Y [USER]@[IPADDR]
  • Set the display variable to offset (10, in my case) plus the display number in Windows, e.g. export DISPLAY=localhost:10
  • Run an X-based software and see if the software loads in an individual window in the local Windows, e.g. xeyes
  • Profit

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?

  1. Ensure remote machine has xauth installed (in order to run X clients). Optionally install lightweight X11 apps for testing (e.g. x11-apps in Ubuntu for xeyes)
  2. Modify /etc/ssh/sshd_config on remote to include X11Forwarding yes
  3. Add the DISPLAY environment variable with the value localhost:0.0
  4. Add ForwardX11 yes and ForwardX11Trusted yes into SSH config for your remote machine
  5. Download VcXsrv and run XLaunch with default values
  6. Login to the remote and run your X11 app

Do not perform X11 forwarding to untrusted computers!

Terminology

X Window System

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

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

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

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

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.

SSH X11 forwarding flow

This Teleport article provides a well-rounded explanation, which I will attempt to modestly summarize.

We simplify the network components into the following:

Initial forwarding

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:

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:

Once X11 forwarding is setup, the SSH connection proceeds as usual. The entire SSH connection can be viewed with:

ssh -vvv -X ...

X client request

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:

Achieving X11 forwarding via SSH

A quick graphical brief of what SSH X11 forwarding is all about can be found here.

Enable X11 forwarding on SSH server

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.

Set DISPLAY environment variable

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.

Enable X11 forwarding

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:

~/.ssh/config
# 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:

~/.ssh/config
# '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.

Run X server on local machine

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.

Why we still use -Y

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.

Windows

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:

  1. VcXsrv's 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).
  2. In untrusted scenarios, 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.