Moonshine: a headless Linux game streaming host for Moonlight clients
Headless streaming server for Moonlight clients, written in Rust.
At a glance
- What is it?
- Moonshine is a Rust server that streams games from a Linux PC to any device running Moonlight, running each session in its own compositor so a headless host can serve several streams at once. It suits a home server or a spare machine that nobody is sitting at, and it depends on Vulkan video encoding and a GPU that can do it.
- Who is it for?
- Moonshine fits a Linux machine that has a spare GPU and no monitor, whether that is a home server, a spare rig in a cupboard or a cloud GPU instance, because it needs no HDMI dummy plug and each stream runs in an isolated compositor so the host stays usable. Skip it on Windows or macOS, since the requirement is Linux only, and skip it if your GPU lacks Vulkan video encoding, because NVIDIA RTX, AMD RDNA2 or newer and Intel Arc are the stated targets.
- Can I use it commercially?
- Yes. BSD-2-Clause is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 2 days ago.
- What is it written in?
- Mainly Rust, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A host that does not need a monitor
Moonshine lets you stream games from your PC to any device running Moonlight, and your keyboard, mouse and controller inputs are sent back to the host so you can play games remotely as if you were sitting in front of it.
That last clause is where the real work is, and it is where a game streaming server lives or dies. Video encoding is the easy half. Latency for the input path is the hard half, because every millisecond of round trip is a millisecond between your thumb and the game. A server that streams beautiful video but eats 80ms on the controller makes a fast action game unplayable.
The feature list explains how the project approaches that. Isolated streaming sessions means each stream runs in its own compositor, completely separate from your desktop environment, and the host PC can still be used for other things while you stream. Full input support covers mouse, keyboard, touchscreen, pen and stylus, and gamepad including motion, touchpad and haptics. Audio streaming is stereo and surround sound with 5.1 and 7.1 support and low-latency Opus encoding.
The compositor isolation is worth dwelling on. Running the game inside your own desktop compositor would mean the stream competes with your actual session for the GPU and the display, and any window you opened would show up in the stream. A separate compositor per stream means the stream is a self contained graphical world, which is what makes headless operation possible at all.
The encoding support is H.264, H.265 and AV1 using the GPU, with true 10-bit HDR streaming for supported games. AV1 matters more than it used to, since it is the codec that gives the best quality per bit on a weak uplink.
The language is Rust, the licence is BSD-2-Clause, and the last push was on 2026-10-02.
Four requirements, one of them absolute
The requirements section is short, and the first entry settles everything.
Linux only. It is available as `.deb`, `.rpm`, Nix, AUR, and an install script for SteamOS, and it is tested on Arch Linux but reported to work on other distributions too. There is no Windows or macOS path, and the project does not pretend otherwise.
The second is systemd, required for launching and managing application processes, and the documentation notes that almost all modern Linux distributions include it by default. This is a hint about the architecture, because Moonshine runs each application as a separate process it can start and stop, and a service manager is the natural way to supervise that.
The third is a GPU with Vulkan video encoding: NVIDIA RTX, AMD RDNA2 or newer, or Intel Arc. This is the requirement most likely to end your particular plan, because it is a hardware capability rather than a software setting. An older NVIDIA card without NVENC, an AMD card before RDNA2, or an Intel integrated GPU of a certain age will not do hardware encoding, and the project does not offer a software fallback path in the documentation.
The fourth is Moonlight v6.0.0 or higher on the client, with the explicit caveat that compatibility with older versions or unofficial ports is not guaranteed.
The AUR is the recommended install method, and the packaging options cover the range of Linux distributions people actually run. The `.deb` and `.rpm` are plain packages, the Nix route provides both a package and a NixOS module that sets the service up for you, and there is a separate path for Bazzite, Silverblue and other Atomic distros where packages are layered rather than installed.
The SteamOS route is the one worth reading closely, because it is the least conventional. The installer script deploys Moonshine to `/opt/moonshine/` and configuration drop-ins to `/etc/`, and it registers those files with SteamOS so they persist across atomic updates. That registration is the part that matters, since on an immutable system anything you install outside the system directories disappears on the next update.
Enabling the service and the gamepad caveat
The steps that follow installation are the same for every install method, and two of them are easy to get wrong.
The first is enabling user lingering:
sudo loginctl enable-linger $USERThis allows Moonshine to run applications in the user's session even when the user is not logged in, which is the headless case the project is built for. The documentation adds that if your user is always logged in when you want to stream, you can skip this step, and the logic is sound: lingering exists to keep a session alive for a user who is not present, and if someone is present anyway, it is redundant.
The second is the systemd unit itself:
sudo systemctl enable --now moonshine@$USERThe unit is templated on the user, which is the correct shape for a per-user session service.
The third is the gamepad caveat, and it is the one that catches people. For gamepad support when streaming headless with no active desktop session, add your user to the `input` group, then log out and back in, or reboot, for it to take effect:
sudo usermod -aG input $USERThe reasoning is in the documentation. Moonshine creates a virtual gamepad per streamed controller, and the streamed game must be able to read it. When streaming while a desktop session is active, your user is already granted access to input devices as the active seat, so this step is not needed there.
So the failure mode is asymmetric and confusing if you do not know about it. Streaming from a logged in desktop and your gamepad works; streaming headless and it does not, because the permission comes from the seat rather than from the group. The group membership is what a headless server needs, and it only takes effect after a new login because the existing session's credentials are cached.
There is a diagnostic for exactly this. Run `moonshine healthcheck` to verify, and it reports an `input group` warning if the group is missing. A healthcheck subcommand that names the specific misconfiguration is worth more than a generic failure.
Pairing and the applications file
Pairing is the first thing that happens when a client connects, and it is a PIN-based flow.
When you connect with Moonlight for the first time, it shows a PIN. A notification appears on the host that you can click to open the pairing page, or you can visit it manually at `http://localhost:47989/pin`. There is also a command line route:
curl -X POST "http://localhost:47989/submit-pin" -d "uniqueid=0123456789ABCDEF&pin=<PIN>"The HTTP endpoint on localhost is worth noting for a headless setup, because a notification on a machine with no display is not much of a notification. The curl form is the way to pair a server with no one physically present, and the port, 47989, is the same one the pairing page uses.
Applications are then declared in `config.toml`, one block per application, and the format is TOML arrays of tables:
[[application]]
title = "Steam"
boxart = "/path/to/steam.png" # optional
command = ["/usr/bin/steam", "steam://open/bigpicture"]`title` is the name shown in Moonlight. `boxart` is an optional path to a cover image. `command` is the command to run, where the first entry is the executable and the rest are arguments, and the Steam example shows a URI argument, which is how you launch a game rather than the client itself.
There are two optional hooks around the launch. `pre_command` runs commands before launching the application, with each entry a separate command executed in order, and it runs synchronously so the session waits for all of them to finish. `post_command` runs after the streaming session ends.
The synchronous behaviour of `pre_command` is a deliberate design point rather than an implementation detail, and it is the thing to understand if you want to use it. It means the session does not start until your setup commands have completed, so a script that launches a game through a wrapper, or that prepares a save directory, has a guaranteed ordering relative to the game. It also means a slow pre_command is added directly to the time before you see the first frame, and the session will not start at all if one of them fails.
The configuration file is created automatically if the path you provide does not exist, and with the AUR package it defaults to `$XDG_CONFIG_HOME/moonshine/config.toml`.
Building from source means installing a Vulkan layer
The source build is more involved than a typical Rust project, and the reason is the Vulkan integration layer.
The build and runtime dependencies are listed for an Arch style system:
# Build dependencies
sudo pacman -S --asdeps \
clang \
cmake \
libc++ \
rust
# Runtime dependencies
sudo pacman -S \
gcc-libs \
glibc \
libevdev \
libxkbcommon \
mesa \
opus \
waylandThe runtime list tells you what the project does. `libevdev` and `libxkbcommon` are input handling, so raw evdev events and keyboard mapping, which is the input path. `wayland` and `mesa` are the display stack, so the compositor and the software Vulkan driver as a fallback. `opus` is the audio codec.
The build itself is a standard workspace build:
cargo build --release --workspaceAnd the Cargo manifest shows the workspace layout, with members of `.`, `moonshine-core`, `moonshine-tools` and `moonshine-wsi`, and the root package named `moonshine` at version 0.16.1 with edition 2024. The `moonshine-wsi` member is the Vulkan WSI, the window system integration layer.
That layer is the part most people will not have set up before. The environment setup installs the uinput and uhid kernel modules, which is what lets the process create virtual input devices for the gamepad support, and installs the udev rules for input device access:
sudo install -Dm644 dist/moonshine-modules.conf /usr/lib/modules-load.d/moonshine.conf
sudo modprobe uinput uhid
sudo install -Dm644 dist/60-moonshine.rules /usr/lib/udev/rules.d/60-moonshine.rules
sudo udevadm control --reload-rulesThen the Vulkan layer itself, both the shared object and the JSON manifest that tells the loader about it:
sudo install -Dm755 target/release/libmoonshine_wsi.so /usr/lib/moonshine/vulkan-layers/libmoonshine_wsi.so
sudo install -Dm644 dist/VkLayer_moonshine_wsi.json /usr/share/vulkan/implicit_layer.d/VkLayer_moonshine_wsi.jsonNote that the layer is installed as an implicit layer, so it is picked up by the loader without an application having to opt in. That is the mechanism by which the game, which knows nothing about Moonshine, ends up rendering into a stream instead of onto a display.
Finally the run command, which takes the path to the configuration file:
cargo run --release -- /path/to/config.tomlThe repository also carries an `nfpm.yaml`, which is the configuration for building the `.deb` and `.rpm` packages, and a `flake.nix` and `flake.lock` for the Nix flake.
What the Linux-only, GPU-bound design costs you
Moonshine is a good fit for a specific setup and a poor fit for most others, and the reasons are structural rather than accidental.
The Linux-only requirement is the first filter. Game streaming servers for Windows and macOS exist and are widely used, so a Linux-only project is a real constraint on who can use it. The justification is implied by the rest of the requirements: systemd, the Wayland compositor, the Vulkan WSI layer and the uinput device creation are all Linux shaped, and a project that wanted to be cross platform would be spread thin.
The GPU encoding requirement is the second. It is a hard dependency on hardware that a given machine may not have, and there is no software fallback documented, which is the right engineering choice for a latency sensitive application but it does mean the tool has a floor on the hardware it supports.
The third limit is the client. Moonlight v6.0.0 or higher is required, and compatibility with older versions and unofficial ports is explicitly not guaranteed. Since the wire protocol is the shared contract between server and client, a server that accepts a wider range of clients would need to implement protocol versions the project has moved past.
The cost of that isolation is the Wayland and Vulkan WSI machinery described above, which is why the source install is longer than a cargo build.
There is also the other alternative, which is not a streaming server at all. A remote desktop protocol gives you the whole desktop rather than one game, and it works over a WAN where a game stream may not, at the cost of latency that is much worse for games specifically.
The maintenance picture is active. Versions 0.15.0, 0.16.0 and 0.16.1 came out on 2026-08-05, 2026-09-11 and 2026-09-14 respectively, and the last push was on 2026-10-02. The changelog is at the top level along with a TIPS.md and a `moonshine-tools/` member, which is the place for the healthcheck and related utilities.
Editorial conclusion
Moonshine fits a Linux machine that has a spare GPU and no monitor, whether that is a home server, a spare rig in a cupboard or a cloud GPU instance, because it needs no HDMI dummy plug and each stream runs in an isolated compositor so the host stays usable. Skip it on Windows or macOS, since the requirement is Linux only, and skip it if your GPU lacks Vulkan video encoding, because NVIDIA RTX, AMD RDNA2 or newer and Intel Arc are the stated targets. Before you install, check that Moonlight v6.0.0 or higher is on the client side and that your client is an official port, since compatibility with older versions and unofficial ports is not guaranteed. The last push was on 2026-10-02 with v0.16.1 released on 2026-09-14.
Frequently asked questions
What is Moonshine and who can use it?
It is a Rust server that streams games from a Linux PC to any device running Moonlight, with your keyboard, mouse and controller inputs sent back to the host. It is Linux only, needs systemd, a GPU with Vulkan video encoding and Moonlight v6.0.0 or higher.
Does Moonshine need a monitor or an HDMI dummy plug?
No. The feature list states it works on headless servers with no HDMI dummy plug needed, and each stream runs in its own compositor, completely separate from your desktop environment.
How do I enable the Moonshine service on Linux?
Run `sudo loginctl enable-linger $USER` so the session survives without a logged in user, then `sudo systemctl enable --now moonshine@$USER` to start it on boot. For headless gamepad support also run `sudo usermod -aG input $USER` and log back in.
How do I pair a Moonlight client with Moonshine?
The client shows a PIN on first connection. Open the pairing page at http://localhost:47989/pin on the host, or post it directly with `curl -X POST "http://localhost:47989/submit-pin" -d "uniqueid=0123456789ABCDEF&pin=<PIN>"`.
Which video encodings does Moonshine support?
H.264, H.265 and AV1 using the GPU, with true 10-bit HDR streaming for supported games. Audio is stereo and surround sound with 5.1 and 7.1 support using low-latency Opus encoding.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/hgaiser-moonshine)Was this useful?