Remote Desktop with Sway Headless and Wayvnc [network/remote-desktop]

Mesh VPN solutions give me a static IP map of all my devices, so that I can have encrypted access to each of my devices with address like 100.64.0.1 or athyrium.spore.tail.

Since I’m a Neovim user (btw), plain old SSH is mostly enough for coding and ops. But sometimes I want a graphic session to play games remotely or temporarily peek through device cameras.

Most of my use cases involve my phones (they are incapable enough to make remote desktops always useful), so RDP doesn’t help. There are several RDP client implementations on Android, but unfortunately none of them handles H.264 encoded streams (yes, especially the Microsoft one which just crashes), and all of the RDP servers on Linux use H.264 as the only option.

So I’ve got to use VNC on Linux. There’s a nice, simple VNC client on Android called AVNC, which covers all the features I need.

Server side, I use a headless Sway session combined with wayvnc. They were a bit unstable last year (the fact that I have an NVIDIA optimus card didn’t help), but have been working quite well since 2025. Using a separate Wayland session has a few advantages, like I can spin it up entirely remotely in tmux, without physical access. It’s also a common situation that the remote connection has limited bandwidth, and this dedicated Sway session is configured to be at 720p@30FPS by default.

I use a simple script to launch the headless session with VNC1:

#!/usr/bin/env bash

# run in headless mode, and show a software (remote) cursor
export WLR_BACKENDS=headless WLR_LIBINPUT_NO_DEVICES=1
sway --unsupported-gpu &

sleep 2

# `wayland-0` if you have no other graphical sessions running
# probably not needed if you run wayvnc in the Sway config
export WAYLAND_DISPLAY=wayland-1
wayvnc -g <listen-ip> &

wait

I also have a few special Sway bindings for this scenario:

# This switches the resolution on the fly. Refresh rate can be changed too.
bindsym <key> output HEADLESS-1 mode 1920x1080@30Hz
# This rotates the screen to be in portrait mode.
bindsym <key> output HEADLESS-1 transform 90
# This resets the rotation.
bindsym <key> output HEADLESS-1 transform normal

An apparent caveat is that VNC doesn’t handle audio by itself. On my old machine I have remote audio with Simple Protocol Player and pulseaudio, but I’m not yet motivated enough to set it up. Also I’m rather sure that there’s a better solution with pipewire, that I’m just too lazy to find out.

1

Connection is not secure, nor password-protected with this config. In my case encryption is handled by Tailscale and I only accept connections from Tailscale clients by setting <listen-ip> appropriately.