moonlight-web-stream: a browser front end for Sunshine game streaming
This is a web server which allows you to stream games with sunshine into a web browser.
At a glance
- What is it?
- moonlight-web-stream is an unofficial Moonlight client that runs a web server and forwards Sunshine traffic to a browser over WebRTC, with a Rust core and a TypeScript front end. It suits a self-hosted streamer with a public IP, and it will not work at all without HTTPS for the gamepad and keyboard features.
- Who is it for?
- Adopt moonlight-web-stream if you already run Sunshine, can expose a port or a TURN server, and want to play from a browser rather than installing the native Moonlight client. Do not adopt it if the gamepad API and keyboard capture are the point, since both require a secure context and the WebSocket transport falls back to the Web Codecs API, which is the same constraint.
- Can I use it commercially?
- Yes, with conditions. GPL-3.0 is a copyleft licence: if you distribute software that includes it, you must release that software's source code under the same licence. Running it internally without distributing it does not trigger that obligation.
- Is it still maintained?
- Yes. The repository last received commits 3 days ago.
- What is it written in?
- Mainly TypeScript, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A bridge that puts Sunshine behind a browser WebRTC client
moonlight-web-stream is an unofficial Moonlight client, and the word unofficial is doing real work in that sentence. It is not a fork of the native Moonlight client, and it is not a replacement for Sunshine. It is a web server that forwards Sunshine traffic to a browser using the WebRTC API.
The topology is three pieces. Sunshine runs on the host PC and does the actual game streaming, as it normally would. This project sits between, hosting a web server that speaks the Sunshine protocol on one side. On the other side a browser connects as a WebRTC peer and renders the stream.
That middle layer is the whole contribution. Sunshine already had network streaming clients, but they were native applications. A browser cannot run a native client, so something has to terminate the Moonlight protocol server side and re-emit it as WebRTC media, and that is what this repository does.
The repository structure makes the implementation split visible. The Cargo.toml at the root defines a package named web-server with edition 2024 and version 3.0.0-prerelease.10, so the server is Rust. The web/ directory holds the browser front end, the src/ directory the server code, and there are separate Cargo.wasm.toml and ubrn.config.yaml files for the WebAssembly build of the shared Moonlight code. The package.json is the JavaScript side, with webpack and TypeScript.
The Moonlight protocol logic itself lives in a separate repository, moonlight-common-rust, pulled in as a git dependency pinned to a specific revision with the tokio, tokio-hyper, rustcrypto, stream-proto and serde features enabled.
The secure context requirement that gates controllers and keyboard lock
The limitations section is short and it is the most important thing on the page.
The browser features that only work in a Secure Context are the Gamepad API for controllers and the Experimental Keyboard Lock API for keyboard capture, which allows capturing almost all keys including OS keys. WebSocket transport is on the same list, because it depends on the Web Codecs API and its VideoDecoder.
The reason is the https configuration, and the documentation links the two together. A secure context means the page is served over HTTPS or from localhost. A page served over plain HTTP on a LAN address is not a secure context, so the gamepad and keyboard features are simply unavailable, and the WebSocket fallback path is unavailable as well.
That has a direct consequence for the deployment plan. If you are streaming only to yourself from a browser on the same machine, localhost gives you a secure context and the limitation does not bite. If you are streaming to a phone on your home network over http://192.168.x.x, the gamepad will not appear.
The project can serve https itself, using a private key and a certificate. The documentation suggests generating a self signed certificate with a bundled python script, and the commands are:
pip install pyOpenSSL
python ./generate_certificate.pyThe generated files are then copied into the server directory as server/key.pem and a certificate file. A self signed certificate will still produce browser warnings, so for anything beyond a private LAN the more sensible route is the reverse proxy approach the documentation also covers.
Running the server and the first pairing
The manual install is four steps. Install Sunshine on the host, download the compressed archive for your platform from the releases page and uncompress it, or build it yourself, then run the web-server executable. The web interface is at localhost:8080, and the bind address is configurable.
One trap is called out in a note. A config.json file is not generated automatically on first startup, so if you intend to follow the configuration guides you have to create it yourself by running the config generate subcommand, ./web-server config generate.
A Docker installation is documented separately in the docker directory.
The setup sequence then runs through the web interface. You add a user by typing a name and password, and the first user to log in is created and becomes the admin. You add a new PC with the address as localhost and leave the port empty if you are using the default port. Pairing happens by clicking the host and entering the code shown in Sunshine. Then you launch an app.
The pairing step is Sunshine's own, not a new mechanism, which is the point of the project being a client rather than a reimplementation of the streaming stack.
Getting a WebRTC connection through a restrictive network
The section on streaming over the internet is where the real configuration work sits.
On a local network, WebRTC peers usually negotiate without issues. Over the internet, the built-in STUN servers try to establish a direct peer to peer connection, which works for most home networks and may fail in restrictive environments such as corporate firewalls or strict NAT configurations.
The documentation gives three fallbacks in order. The first and recommended one is a TURN server, hosted on something like coturn or a commercial service, configured through the WebRTC ICE server list in config.json:
{
"webrtc": {
"ice_servers": [
{
"urls": [
"stun:stun.l.google.com:19302",
"stun:stun1.l.google.com:3478",
"stun:stun.l.google.com:5349"
]
},
{
"urls": [
"turn:yourip.com:3478?transport=udp",
"turn:yourip.com:3478?transport=tcp",
"turn:yourip.com:5349?transport=tcp"
],
"username": "your username",
"credential": "your credential"
}
]
}
}The second is direct port forwarding, which requires fixing the WebRTC port range:
{
"webrtc": {
"port_range": {
"min": 40000,
"max": 40010
}
}
}The documentation notes that on Windows Defender you must allow NAT Traversal, and that if the firewall blocks UDP it will not work and you need the TURN server instead.
The third is WebSocket transport, which uses standard HTTP and HTTPS ports and is therefore often allowed even on very restrictive networks. The optional NAT 1 to 1 setting advertises your public IP directly, which WebRTC STUN servers can usually detect on their own.
A Rust server with a TypeScript front end and WebAssembly bindings
The dependency lists describe the architecture more precisely than the readme does.
The server side is built on tokio for async networking, the filesystem and process spawning, actix-web as the HTTP server with the rustls-0_23 feature, actix-files for static file serving and actix-ws for WebSocket handling. The WebRTC layer is the webrtc crate at version 0.21.1, with the rtc crate at the same version. Serialization is serde and serde_json, certificates are handled with pem, and error handling uses anyhow and thiserror.
Logging is layered: the log crate, tracing with tracing-subscriber configured for env-filter, tracing-appender for file output, and tracing-actix-web for the request side. A crate named venator at version 1.1.0 is also listed, which the readme does not describe.
Security is handled in the dependency list too, with sha2 and pbkdf2 with the sha2 feature for passwords, and uuid and hex for identifiers. The lints section allows too_many_arguments and turns unwrap_used and todo into warnings, which is a sensible posture for a server that parses protocol messages.
The front end is TypeScript compiled by webpack with ts-loader, using html-webpack-plugin for the page, css-loader and style-loader for styles, and copy-webpack-plugin for assets. The bindings between the Rust core and the browser are generated rather than hand written, through a cargo test export_bindings step that feeds uniffi-bindgen-react-native, and a ubrn checkout and build pair that compiles the shared Moonlight code to WebAssembly through @ubjs/core. The package.json also pins the toolchain, requiring node >=26.4.0 <27.0.0 and npm >=11.17.0 <12.0.0.
Where moonlight-web-stream is the wrong tool
There are several honest reasons to pick a different approach.
The first is exposure. This server hands a browser control of your PC's display and input, and the readme's own internet setup path involves port forwarding a range of ports on your router. Anyone who reaches the web interface can pair with Sunshine and drive your machine. The documentation describes the mechanisms for reaching it from outside, and the reverse proxy authentication section, but the security model is fundamentally that the web server is the front door to Sunshine.
The second is browser capability. The native Moonlight client uses the Gamepad API without a secure context requirement in the same way, and keyboard capture here depends on an experimental API. Browsers also impose their own constraints on WebRTC that a native client does not have. If your priority is reliable low latency with a controller, the native client remains the better instrument, and this project is not a replacement for it.
The third is the pre-release status. The three most recent tags are all v3.0.0-prerelease.10, .9 and .8, and the readme warns that documentation for the current latest release lives on the v2.10.0 branch while master contains the latest development version and may not match the published documentation. The version in Cargo.toml matches the prerelease tag, so the branch you clone is not the branch the documentation describes.
The fourth is the build weight. A native dependency chain, a WebAssembly build of the Moonlight common code, a code generation step and a webpack build is a lot of machinery for a tool whose alternative is an installer for a native client.
Maintenance state, licence and what the v3 migration implies
The repository is not archived, and the last push was on 2026-10-20, the same day as the v3.0.0-prerelease.10 tag, with the two earlier prereleases a fortnight earlier on 2026-10-04.
The licence is GPL-3.0, and both the Cargo package and the npm package declare it as GPL-3.0-or-later. For self hosting on your own hardware that is not an obstacle. For anyone who wants to modify it and ship the result as part of a product, the copyleft terms apply to the derivative work, and that is a question for your own legal review rather than something to be settled from the readme.
The readme's table of contents lists separate migration sections for v2 and for v3, which tells you two things. The project has already made at least one breaking change, from v2 to v3, and the v3 prereleases suggest another is in progress now. Any adoption decision has to assume your config.json will need to change when the prerelease settles.
There is also a version skew to be aware of. The npm package version field still reads 1.0.0 while the Rust package is at 3.0.0-prerelease.10, so the JavaScript side is not versioned in step with the server, which makes it harder to reason about what a given commit contains.
Finally, the project carries a Cargo.lock, a package-lock.json, a rust-toolchain.toml, a .editorconfig, a tsconfig.json and a webpack.config.js, so the toolchain is pinned and reproducible. That is a good sign for a project where a broken WebAssembly binding step would otherwise be hard to diagnose.
Editorial conclusion
Adopt moonlight-web-stream if you already run Sunshine, can expose a port or a TURN server, and want to play from a browser rather than installing the native Moonlight client. Do not adopt it if the gamepad API and keyboard capture are the point, since both require a secure context and the WebSocket transport falls back to the Web Codecs API, which is the same constraint. Before you start, generate a config.json with ./web-server config generate, because the file is not created on first startup, and decide between direct WebRTC, a TURN relay and WebSocket transport before exposing anything to the internet.
Frequently asked questions
What does moonlight-web-stream do and what does it need?
It is an unofficial Moonlight client that runs a web server and forwards Sunshine traffic to a browser using the WebRTC API. You need Sunshine installed on the host PC, and the web-server executable from a release archive or built from source.
Why do controllers and keyboard lock not work in moonlight-web-stream?
The Gamepad API and the Experimental Keyboard Lock API only work in a Secure Context, and the WebSocket transport path depends on the Web Codecs API for the same reason. You need to serve the page over https or use localhost.
How do I create the config.json for moonlight-web-stream?
It is not generated automatically on first startup. Run the config generate subcommand of the web-server executable, which is shown in the readme as ./web-server config generate, before editing any of the settings.
What happens when a direct WebRTC connection cannot be established?
The documentation offers three options: configure a TURN server, which it recommends, forward the required ports directly, or fall back to WebSocket transport. WebSockets use standard HTTP and HTTPS ports and are often allowed even on very restrictive networks.
Which licence does moonlight-web-stream use?
The Cargo package and the npm package both declare GPL-3.0-or-later, and the LICENSE file is at the repository root. Self hosting on your own hardware is unaffected, but redistribution of a modified version is a matter for your own legal review.
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/mrcreativ3001-moonlight-web-stream)Was this useful?