Building a Remote Desktop Environment on DGX Spark (RustDesk + Tailscale)

Building a Remote Desktop Environment on DGX Spark (RustDesk + Tailscale)

By combining RustDesk and Tailscale, I will introduce the steps to build a secure remote desktop environment without a relay server. I have also summarized the key configuration points for headless environments.
2026.02.09

This page has been translated by machine translation. View original

Introduction

Hello, I'm Morishige from Classmethod's Manufacturing Business Technology Department.

I've written several articles about DGX Spark, but this time I'm changing things up a bit to cover setting up a remote desktop environment.

I've mostly been working via SSH through the CLI and had barely touched the GUI desktop. However, I thought it was a shame to have GNOME Shell running without using it, so I decided to set up a remote desktop environment.

In this article, I'll walk through the steps to build a secure remote desktop connection without a relay server, using a combination of RustDesk and Tailscale. (It took me a bit of effort in my environment, but depending on your setup, you might be able to get started with no hassle at all...)

Why RustDesk + Tailscale?

While researching remote desktop options, I came across an open-source remote desktop software called RustDesk. As the name suggests, it's written in the Rust language and runs lightweight and cross-platform. This time, I chose a "Direct IP Access" configuration combining RustDesk with Tailscale.

https://rustdesk.com/ja/

Comparing the main options, here's what I found:

Option Merits Demerits
VNC (TigerVNC, etc.) Mature technology, simple configuration Rendering can be heavy, encryption requires separate setup
Chrome Remote Desktop Easy setup Dependent on Google account, ARM support is?
RustDesk (via public server) Open source, cross-platform Latency due to public relay server
RustDesk + Tailscale Direct connection without relay, double encryption Requires setting up both

My personal deciding factors were: first, using Tailscale's Direct IP Access eliminates the need for relay servers like hbbs/hbbr, reducing infrastructure management to zero. Additionally, the double encryption via Tailscale (WireGuard) and RustDesk (NaCl E2E) protects both the network path and screen transfer.

Here's an overview of the connection we'll be building:

This is a configuration where RustDesk communicates directly within Tailscale's WireGuard tunnel, without going through a relay server.

Prerequisites

The environment for this setup is as follows:

Item DGX Spark (server side) MacBook Pro (client side)
OS Ubuntu 24.04 (DGX OS) macOS
Architecture aarch64 (Grace CPU) Apple Silicon
Tailscale IP 100.x.x.x 100.x.x.x
Display X11 + GDM + GNOME Shell -

Tailscale is already configured on both machines, and a direct connection can be confirmed with tailscale status.

DGX Spark Setup

Installing RustDesk

Since DGX Spark uses the aarch64 architecture, we'll install the ARM version of the deb package.

# Check architecture
uname -m
# aarch64

# Download the latest version (1.4.5 as of February 2026)
cd /tmp
wget https://github.com/rustdesk/rustdesk/releases/download/1.4.5/rustdesk-1.4.5-aarch64.deb

# Install
sudo dpkg -i rustdesk-1.4.5-aarch64.deb
sudo apt-get install -f -y

Configuring Direct IP Access and a Fixed Password

Enable direct connection via Tailscale and set a password for unattended access.

# Enable Direct IP Access
sudo rustdesk --option direct-server Y

# Set a fixed password (16 characters or more recommended)
sudo rustdesk --password '<your-strong-password>'

# Check the RustDesk ID (make a note of it)
rustdesk --get-id

Configuring the systemd Service

We'll start RustDesk as a service, but there's one tricky point here. Since the systemd service runs outside of the X11 session, we need to explicitly pass the display information.

First, let's check the current session information.

# Check the X11 display number
# Get environment variables from the GNOME Shell process
cat /proc/$(pgrep -u $(whoami) gnome-shell | head -1)/environ | tr '\0' '\n' | grep ^DISPLAY=
# DISPLAY=:1

# Check the XAUTHORITY path
cat /proc/$(pgrep -u $(whoami) gnome-shell | head -1)/environ | tr '\0' '\n' | grep ^XAUTHORITY=
# XAUTHORITY=/run/user/1000/gdm/Xauthority

Using this information, we'll create a systemd override file.

sudo mkdir -p /etc/systemd/system/rustdesk.service.d

sudo tee /etc/systemd/system/rustdesk.service.d/override.conf <<'EOF'
[Service]
Environment="DISPLAY=:1"
Environment="XAUTHORITY=/run/user/1000/gdm/Xauthority"
Environment="DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus"
Environment="XDG_SESSION_TYPE=x11"
EOF

sudo systemctl daemon-reload
sudo systemctl enable rustdesk
sudo systemctl start rustdesk

xorg.conf Configuration for Headless Environments

And this is the part I got most stuck on this time.

When operating DGX Spark without a monitor connected, all outputs in xrandr show as disconnected. RustDesk's screen capture library (libscrap) uses xrandr's monitor information to enumerate displays, so if there are 0 connected monitors, it results in a "no display" error.

# xrandr output when no monitor is connected
xrandr
# Screen 0: minimum 8 x 8, current 3440 x 1440, maximum 32767 x 32767
# HDMI-0 disconnected primary
# USB-C-0 disconnected
# USB-C-1 disconnected
# ...

Even though the X11 screen itself exists and GNOME is rendering, from RustDesk's perspective there is "no screen."

The solution is to configure xorg.conf to make the NVIDIA driver treat HDMI-0 as "connected."

sudo vim /etc/X11/xorg.conf

Add the following 2 lines to the Section "Device":

Section "Device"
    Identifier     "Device0"
    Driver         "nvidia"
    VendorName     "NVIDIA Corporation"
+   Option         "ConnectedMonitor" "HDMI-0"
+   Option         "ModeValidation" "NoDFPNativeResolutionCheck,NoVirtualSizeCheck,NoMaxPClkCheck,NoEdidMaxPClkCheck,NoMaxSizeCheck,NoHorizSyncCheck,NoVertRefreshCheck,NoWidthAlignmentCheck"
EndSection

ConnectedMonitor is an option that makes the driver recognize the specified output as "connected" even when no physical monitor is attached. ModeValidation is an option to allow various resolutions even without EDID information.

After making the changes, restart GDM.

sudo systemctl restart gdm

After restarting, check xrandr and HDMI-0 should now show as connected.

xrandr
# Screen 0: minimum 8 x 8, current 1920 x 1080, maximum 32767 x 32767
# HDMI-0 connected primary 1920x1080+0+0
# ...

Adjusting Resolution

If you want to change the resolution for remote desktop use, you can change it instantly with nvidia-settings. (You can also adjust it from the RustDesk client app.)

DISPLAY=:1 XAUTHORITY=/run/user/1000/gdm/Xauthority \
  nvidia-settings --assign CurrentMetaMode="HDMI-0: 1920x1080 +0+0"

Firewall Configuration

Since traffic via Tailscale is already encrypted with WireGuard, a connection is possible without firewall configuration. However, in environments where ufw is enabled, explicitly opening the ports limited to the Tailscale interface makes things more robust.

sudo ufw allow in on tailscale0 to any port 21118 proto tcp comment 'RustDesk Direct IP'
sudo ufw allow in on tailscale0 to any port 21119 proto tcp comment 'RustDesk Direct IP'

By restricting to tailscale0, access from outside Tailscale can be blocked.

MacBook Pro Setup

The client side is relatively simple.

brew install --cask rustdesk

In macOS "System Settings > Privacy & Security," allow the following:

  • Screen Recording → Check RustDesk
  • Accessibility → Check RustDesk

Once RustDesk is launched, enter the DGX Spark's Tailscale IP (100.x.x.x) in the "ID" input field on the left side to connect. On the first connection you'll be prompted for a password, so enter the fixed password you set earlier with rustdesk --password.

Entering a Tailscale IP in the RustDesk ID input screen

Connecting

Once the MacBook Pro setup is complete, let's try connecting to DGX Spark from RustDesk.

The GNOME desktop displayed after a successful connection

After entering the password, the DGX Spark's GNOME desktop will appear on your MacBook's screen. With Tailscale's direct connection (no DERP relay), you'll barely notice any latency on the same network. Text editing and terminal operations are comfortable, and web browser scrolling runs without any hitches.

After connecting, the following adjustments are available from the RustDesk toolbar:

RustDesk toolbar (changing window size)

  • Window size changes (resizable on the client side)
  • Image quality adjustment (Balanced / Best)
  • FPS changes (default 30fps, 60fps for more motion-heavy operations)

SSH is usually enough for me, but there are surprisingly many times when I want a GUI—like when I want to check the DGX Dashboard or use a WebUI for image generation. Having a remote desktop environment that connects quickly in those moments is really convenient.

Troubleshooting

"No Display" Error

This is the most commonly encountered issue. If you see the following error in the RustDesk server log, check the ConnectedMonitor setting in xorg.conf.

ERROR: Failed to get display 0, displays len: 0

Logs can be found at the following locations:

# systemd journal
journalctl -u rustdesk -f

# RustDesk's own log
cat ~/.local/share/logs/RustDesk/server/rustdesk_rCURRENT.log

Can Connect but Screen is Black

The GNOME lock screen may be displayed. Moving or clicking the mouse via RustDesk will bring up the password entry screen.

If unlocking every time is a hassle, you can also enable automatic login in GDM.

# Add the following to /etc/gdm3/custom.conf
[daemon]
AutomaticLoginEnable=true
AutomaticLogin=<your-username>

Since it's double-protected by Tailscale + RustDesk's fixed password, I think the risk of automatic login is acceptable within a home network.

systemd Service Environment Variables Not Being Applied

Let's check that the content of override.conf is correct.

# Check the override file
cat /etc/systemd/system/rustdesk.service.d/override.conf

# Reload and restart the service
sudo systemctl daemon-reload
sudo systemctl restart rustdesk

Summary

I built a remote desktop environment for DGX Spark using RustDesk + Tailscale's Direct IP Access.

The key points this time are as follows:

  • Direct connection within the Tailscale network eliminates the need for a relay server
  • The ConnectedMonitor setting in xorg.conf is essential for headless operation
  • The systemd service needs to be passed X11 session information (DISPLAY, XAUTHORITY, etc.)

I got quite stuck on the xrandr-related issues in the headless environment this time. The situation where "the X11 screen exists even without a physical monitor, but RustDesk can't see it" was counterintuitive, and it took time to identify the cause. I hope this is helpful for anyone trying the same configuration.

I thought SSH was sufficient, but there are surprisingly many times when you want a GUI, and this has broadened the range of ways I can use DGX Spark a little more.


国内企業 AI活用実態調査2026 配布中

クラスメソッドが独自に行なったAI診断調査をもとに、企業のAI活用の現在地を調査レポートとしてまとめました。企業規模別の活用度傾向に加え、規模を超えてAI活用を進める企業に共通する取り組みまで、自社の現在地を捉えるためのヒントにぜひ。

国内企業 AI活用実態調査2026

無料でダウンロードする

Share this article