Skip to content

Configuration

Polaris is designed to be configured from the web UI first. The config file is still useful when you want to script setup, review current values, or recover from a broken UI state.

File Default path Purpose
Main config ~/.config/polaris/polaris.conf Host settings
App library ~/.config/polaris/apps.json Published apps and launch behavior
Runtime state ~/.config/polaris/polaris_state.json Saved UI and session state

If you change port in polaris.conf, the web UI moves to https://localhost:<port + 1>.

Add installed Steam titles from the web UI’s Applications library scan. The Nix modules do not generate or replace apps.json; Polaris stores each imported title with its Steam app id and applies the selected Linux stream runtime when that title launches. Steam Big Picture remains available as the browse-first entry and does not require an app id.

headless_mode = enabled
linux_use_cage_compositor = enabled
linux_prefer_gpu_native_capture = enabled
trusted_subnets = ["10.0.0.0/24"]
encoder = nvenc
nvenc_split_encode_mode = disabled
adaptive_bitrate_enabled = enabled
max_sessions = 2

These are the settings behind the recommended Headless Stream mode on a Linux host. Use encoder = nvenc on NVIDIA, encoder = vaapi on AMD/Intel Mesa VAAPI hosts, and encoder = software only as a fallback or diagnostic path. Vulkan Video is experimental: an explicit choice supports DRM/KMS, wlroots, and Portal capture, while Auto promotes it only for a compatible AMD private-stream route that passes an exact live-frame safety probe.

The encoder key remains the host-wide default. Clients that advertise the typed session-encoder contract can ask Polaris to keep that default, use Auto, or require one backend for a single game. Polaris accepts only a backend compiled into the running build, validates it again against the live capture route at launch, and restores the host value at teardown. Auto is the only request allowed to fall back; a named backend fails closed instead of silently selecting another encoder.

This is the config-file summary. For choosing a mode, what each one feels like in practice, and AMD/NVIDIA guidance, see Launch modes and capture paths.

I want Set linux_stream_mode to
My real desktop, at host resolution desktop_display (Mirror Desktop)
An extra display, sized to the client host_virtual_display
My live Hyprland desktop on a private client-sized output, with host displays dark desktop_takeover
An isolated game-only session, desktop untouched headless_stream / windowed_stream

Two client-facing notes: Moonlight-protocol clients can request the mirror for a single launch with mirrorDesktop=1 on /launch (no host reconfiguration), and headless_mode = enabled without linux_use_cage_compositor derives host_virtual_display, not a headless session.

Key Typical value What it controls
headless_mode enabled Request a stream-only session instead of the visible desktop
headless_max_refresh_rate 240 Refresh ceiling in Hz for launches where Polaris creates the display itself (Private Stream, Host Virtual Display, Desktop Takeover, Gamescope). Advertised to clients and enforced at launch from the same value; 0 uses the built-in default of 240
linux_use_cage_compositor enabled Enable Polaris’ private stream runtime
linux_prefer_gpu_native_capture enabled Prefer DMA-BUF/GPU-resident capture on NVIDIA and AMD-capable stacks; Polaris reports SHM/system-memory fallback truthfully when the compositor or driver cannot provide it
linux_stream_mode headless_stream (new installs) Stream path id for Linux sessions: headless_stream, windowed_stream, gamescope_stream, host_virtual_display, desktop_takeover, desktop_display, or headless_dongle. Empty derives the path from the legacy booleans above. See Launch modes and capture paths for choosing, stream paths for the contract
linux_private_runtime labwc Private compositor used by paths that host the session themselves: labwc or gamescope. Ignored on host paths
headless_swap_mode privacy Headless Dongle path only: privacy makes the dongle primary and blanks the panel after one-time portal approval is saved (the approval session keeps it on); off extends onto the dongle and leaves the panel primary
linux_virtual_display_backend auto What creates the screen for Host Virtual Display: auto (a new KWin screen on KDE Plasma, then EVDI, then Hyprland, then kscreen-doctor borrowing linux_streaming_output), or evdi, kwin, wlr or kscreen to use only that one. A chosen backend that cannot run refuses the launch with its reason instead of falling back
fallback_mode 1920x1080x60 Display mode used when the client-requested mode is unsupported, as WxHxFPS. The web UI’s Display Planner presets write this same key, so Moonlight compatibility stays standard; Nova and per-game overrides can layer on top where client-settings support exists
display_plan balanced Display Planner preset id persisted alongside fallback_mode: native, balanced, sharp, or performance. Empty means the mode was set manually; hand-editing fallback_mode clears the id so a stale pairing never lights up in the UI
trusted_subnets CIDR list Enable Trusted Pair on known local networks
headless_gamepad_isolation enabled Hide host-connected gamepads from private headless streams; disable only when you intentionally want a wired host controller visible inside the stream
client_gamepad_seat_isolation disabled Assign Polaris-created client gamepads to a dedicated Linux seat so other active-seat users do not receive automatic device ACLs
client_keyboard_mouse_seat_isolation disabled Assign the virtual keyboard, mouse, touch and pen Polaris creates for clients to a dedicated Linux seat, so a client streaming a private session does not also type and click into the desktop session logged in at the machine
mouse_cursor_visible enabled Composite a separately captured host cursor into the stream. Required for DRM/KMS; disable it if the client draws its own cursor and you see two pointers. Portal may embed its cursor independently
back_button_timeout -1 Milliseconds, not seconds, that Back/Select must be held to emulate Home/Guide. -1 disables it. A small value such as 2 means two milliseconds, which turns nearly every Back/Select press into Home and makes the button look broken — use 2000 for two seconds
capture empty (automatic) Capture backend override; explicit Vulkan supports kms, wlr, and portal
encoder nvenc / vaapi / vulkan / software Primary encoder backend; vulkan is experimental and strict when selected explicitly
nvenc_split_encode_mode disabled Experimental Linux/FFmpeg NVENC split-frame encoding for HEVC/AV1
adaptive_bitrate_enabled enabled Allow mid-stream bitrate adjustment
disconnect_resume_timeout_seconds 300 Seconds to keep an app paused after client disconnect for resume
max_sessions 2 Number of simultaneous sessions or viewers
enable_pairing enabled Accept new clients
enable_discovery enabled Advertise Polaris over mDNS
stream_audio enabled Capture and stream audio
steamgriddb_api_key key Cover art lookups for non-Steam apps
beat_times_lookup enabled Ask How Long To Beat about titles missing from the local completion-estimate dataset; disable to keep the host from making those requests
host_sleep_enabled disabled Let a paired client put this host to sleep

host_sleep_enabled lets a paired client suspend the host. It is off by default: a client putting the host to sleep takes the machine away from everyone on it, and there is no undo from the couch.

With it on, POST /polaris/v1/host/sleep suspends the host. The request needs a paired client certificate carrying launch permission, which every paired client has unless you made it watch only, and Polaris refuses while a stream is running. GET /polaris/v1/host/power reports what a client needs in order to decide whether to offer the control at all:

  • sleep_supported: logind answered that this host can suspend.
  • sleep_enabled: host_sleep_enabled is on.
  • sleep_permitted: the calling client may control this host rather than only watch it.
  • sleep_blocked_reason: polkit_denied, not_available, logind_unavailable or unsupported_platform.
  • sleep_blocked_message: the same thing in a sentence you can show someone.

The same block is served on /polaris/v1/capabilities, so a client that already polls capabilities does not need a second request.

The General tab shows the same answer under the setting: whether this host can sleep, why not when it cannot, with a link to the polkit rule below when polkit is the reason, and whether the last sleep request was accepted but the host never went down. The console reads it from GET /api/host/power, which carries the fields above except sleep_permitted, and last_sleep_outcome (none, pending, suspended or failed) with last_sleep_message for the last request.

Waking the host again is Wake-on-LAN, which Polaris does not do for you: enable it in the firmware and on the interface, and send the magic packet from the client.

A host that sleeps and cannot wake is worse than one that never sleeps, and four separate things can cause that. Linux reports none of them as a failure, so check them rather than assuming. Every one of these was found on a working desktop that looked correctly configured.

1. The firmware wake setting, and a cold power-down after it. Enable the board’s wake option: on ASUS boards it is Advanced > APM Configuration > Power On By PCI-E/PCI, and other vendors call it Wake on LAN or Resume by PCI-E. If the board offers ErP Ready or EuP, it has to be disabled, because it cuts standby power to the slots.

Then shut the machine down fully, cut mains power for a few seconds, and start it again. Standby power to the PCIe slots is established at power-on, so a newly enabled wake setting does not take effect after an ordinary reboot. A host that was configured correctly and rebooted will still fail to wake.

2. The interface flag. ethtool <interface> | grep Wake-on should report g. Persist it with nmcli connection modify <connection> 802-3-ethernet.wake-on-lan magic.

Do not read Wake-on: g as evidence that waking works. It says the driver armed the card. It says nothing about whether the firmware will power that card while the host is asleep, and it reads exactly the same on a host that cannot wake at all.

3. Devices that wake the host straight back. A trackball picks up desk vibration and a wireless receiver picks up stray RF, and either will wake the machine within seconds of every suspend, which looks like the host refusing to stay asleep. List what is armed:

Terminal window
for f in /sys/bus/usb/devices/*/power/wakeup; do
[ "$(cat "$f" 2>/dev/null)" = enabled ] && echo "$f"
done

Turn one off with echo disabled | sudo tee <that path>. That resets on every boot, so make it stick with a udev rule matched on the device rather than on its port:

ACTION=="add|change", SUBSYSTEM=="usb", ATTR{idVendor}=="1234", ATTR{idProduct}=="5678", ATTR{power/wakeup}="disabled"

Leave at least one way to wake the host. Turning off every USB wake source on a machine whose Wake-on-LAN does not work leaves nothing but the power button.

4. Anything blocked on a network or FUSE mount. A single task that will not freeze aborts the whole suspend, and the host simply stays awake. The kernel blames the freezer rather than the process, so look for the name yourself:

Terminal window
journalctl -k | grep -A3 "refusing to freeze"

A command walking a remote filesystem is the usual culprit, and an unscoped find / will find one. Polaris reports this case: the suspend is accepted and then does not happen, and host_power carries last_sleep_outcome of failed with a reason.

Testing it. Sleep the host once while you are standing next to it, and send a magic packet from another machine on the same network. If it comes back, the round trip works. If it does not, you are one power button press from trying again, which is the cheapest moment to find out.

When suspend works in a terminal but not from Polaris

Section titled “When suspend works in a terminal but not from Polaris”

sleep_blocked_reason: polkit_denied means logind will only suspend after an interactive authentication that a remote request cannot answer. That is the usual failure on a host where systemctl suspend works fine in a terminal, because the terminal has an active local session and Polaris may not. Allow the action for the user Polaris runs as:

/etc/polkit-1/rules.d/50-polaris-suspend.rules
polkit.addRule(function (action, subject) {
if ((action.id == "org.freedesktop.login1.suspend" ||
action.id == "org.freedesktop.login1.suspend-multiple-sessions") &&
subject.user == "YOURUSER") {
return polkit.Result.YES;
}
});

headless_gamepad_isolation controls the opposite direction: it hides controllers physically connected to the host from a private stream. It does not make a client-created virtual controller disappear from the host kernel.

When client_gamepad_seat_isolation is enabled, Polaris marks client gamepads for the bundled host rules to assign them to the seat-polaris seat. This prevents logind from granting the active local desktop user an automatic uaccess ACL. The device nodes remain root:input with mode 0660, so the Polaris streaming user must belong to the input group. The option is disabled by default to preserve existing virtual-controller identity and local access; enabling it gives isolated gamepads Polaris-specific device names so current Inputtino uinput backends can enforce the udev policy.

Re-run sudo -H polaris --setup-host after upgrading so the installed udev rules understand the dedicated device names and marker. Existing virtual controller nodes keep their previous access policy until recreated; stop active streams and restart Polaris after host setup. AppImage users should re-run the AppImage install action for the same reason.

The input group requirement is not optional and there is no fallback: the isolated nodes are deliberately denied the logind ACL, so an account outside the group cannot open the devices Polaris just created, and neither can the streamed game. Polaris logs an input_access: warning at startup when seat isolation is enabled and the account it runs as is not a member, and --setup-host reports the same thing. Add the account with sudo usermod -aG input <user> and log out and back in; membership only applies to new sessions. On ostree hosts such as Bazzite the group is defined in /usr/lib/group and usermod cannot see it, so use ujust add-user-to-input-group instead. The startup warning prints whichever command applies.

client_keyboard_mouse_seat_isolation marks the virtual keyboard, mouse, touch and pen the same way, but it has only the phys marker to work with: unlike gamepads, those devices keep their normal names so a compositor configured to ignore them by name keeps working. The marker has to survive into the kernel to matter, and a uinput backend that accepts the field without writing it leaves the devices on seat0 while the setting still reads back as enabled. Polaris inspects the devices it creates and logs a warning naming client_keyboard_mouse_seat_isolation when the marker never arrived, so check for that line, or run the command below, before relying on the boundary.

Check the device, not the seat list:

Terminal window
udevadm info -q property -n /dev/input/eventN | grep ID_SEAT

ID_SEAT=seat-polaris means the rules applied and the device is isolated.

loginctl list-seats will keep showing only seat0, and that is expected. It is not a sign that isolation failed. logind only materializes a seat that owns a device tagged master-of-seat, which in practice means a graphics device. seat-polaris exists purely as a udev property that keeps logind from handing the device to the seat0 session, so it never becomes a seat logind lists.

This is a Unix-user boundary, not a same-account process sandbox. Local users who are deliberately members of input, and local applications running under the same Unix account as Polaris, can still open the virtual controller. For concurrent gaming, run Polaris under a dedicated Unix account and do not add local desktop users to input. Strong same-UID isolation requires a privileged broker, container/security-domain boundary, or equivalent system-level policy; a per-session Web toggle cannot provide it safely.

Linux host and private session input isolation

Section titled “Linux host and private session input isolation”

Polaris can run a private stream session while somebody is using the desktop session logged in at the machine. Two separate leaks have to be closed for that to work, and they are closed by different mechanisms because the two directions are not symmetric.

Client input reaching the host desktop. The virtual keyboard and mouse Polaris creates are kernel input devices, so a desktop session at seat0 receives them along with everything else on that seat: the client types into the stream and into the desktop at the same time. Enabling client_keyboard_mouse_seat_isolation marks those devices for the bundled udev rules to assign to the seat-polaris seat, which stops logind from handing them to the seat0 session. Device names are unchanged, so a host compositor already configured to ignore them by name keeps working. This is the same Unix-user boundary described above: same-account processes and members of input are not isolated.

Host compositors that do not go through logind can ignore the devices by name instead. In sway:

input 48879:57005:Polaris_Keyboard_passthrough events disabled
input 48879:57005:Polaris_Mouse_passthrough events disabled
input 48879:57005:Polaris_Mouse_passthrough_(absolute) events disabled

Host devices reaching the private session. The reverse leak appears when the private compositor opens the seat’s physical devices: the keyboard and mouse on the desk then drive the streamed session. Polaris generates the private session’s ~/.config/labwc-polaris/rc.xml with a <libinput> block that sets sendEventsMode to no for every physical device it finds, leaving its own virtual devices enabled. This needs no configuration and applies to every private session.

Writing your own rc.xml there takes over completely: a file without Polaris’ generated marker comment is never overwritten, and Polaris then stops managing input isolation for that session.

Strict host-controller isolation exposes the Polaris virtual gamepad to the streamed app while hiding physical controllers connected to the host. Local Steam Input settings can still claim that virtual Xbox controller. For Proton games, Steam then tries to hand the game a replacement controller through /dev/uinput, but the strict sandbox deliberately does not expose that device or dynamically created input nodes. The result is a controller that works in host-side event tests but is completely dead in the game.

When Doctor reports steam_input_conflict, open Steam Settings > Controller and disable Steam Input for Xbox controllers. Also review the affected game’s Controller properties: use Default after disabling the host-wide Xbox setting, or Disable Steam Input for that game. A per-game Force On override still triggers the conflict.

This Doctor check is read-only. It reports only aggregate status and counts; it does not expose Steam account ids, installed app ids, profile filenames, or filesystem paths, and it does not edit Steam configuration. Close Steam before changing the setting through Steam or by another supported tool so the running client cannot overwrite the update.

The web UI groups the keys above by area. Each Settings tab links to its section here.

Host identity (the name clients see, the web console language, the log level), automation (commands that run before an app launches and are undone after the session, commands that run when the host changes session state, and server shortcuts a paired client may trigger), desktop behaviour (pre-release update notifications, the tray icon, and whether the tray shows the force stop, restart, and quit controls), and metadata integrations (a SteamGridDB key for artwork on non-Steam entries, and whether Polaris may ask How Long To Beat for titles missing from its local completion dataset). Keys: sunshine_name, notify_pre_releases, system_tray, hide_tray_controls, steamgriddb_api_key, beat_times_lookup, host_sleep_enabled.

Field What it does
Locale (locale) The locale used for Polaris’s user interface.
Server Name (sunshine_name) The name displayed by Moonlight. If not specified, the PC’s hostname is used
Log Level (min_log_level) The minimum log level printed to standard out
Server Commands (server_cmd) Configure a list of commands to be executed when called from client during streaming.
PreRelease Notifications (notify_pre_releases) Whether to be notified of new pre-release versions of Polaris
Enable System Tray (system_tray) Whether to show Polaris icon in the system tray
Hide tray control options (hide_tray_controls) Do not show “Force Stop”, “Restart” and “Quit” in tray menu.
SteamGridDB API Key (steamgriddb_api_key) Optional API key used to fetch artwork metadata from SteamGridDB. The first-run wizard can check and save it. A saved key is used right away, by the cover search and by Nova, with no restart.
Completion Estimate Lookups (beat_times_lookup) Allow Polaris to ask How Long To Beat about titles missing from its local completion-estimate dataset. Disabling it keeps the estimates already stored and stops the host making those requests on your behalf.
Allow Clients To Sleep This Host (host_sleep_enabled) Let a paired client put this machine to sleep. Watch-only clients cannot. Polaris refuses while a stream is running. Waking it again is Wake-on-LAN.

Gamepads (whether clients may control the host with a controller, DualShock and DualSense mapping, and the isolation switches that keep host-wired controllers out of private streams and client pads off other Linux seats), keyboard (passthrough, repeat delay and rate, scancodes for non-US layouts, right Alt as the Windows key), pointer and touch (mouse capture, the separately composited host cursor that DRM/KMS capture needs, high-resolution scrolling, native pen and touch events), and extras (an input-only app entry for TV workflows, rumble forwarding). Keys: controller, headless_gamepad_isolation, client_gamepad_seat_isolation, keyboard, key_repeat_delay, key_repeat_frequency, always_send_scancodes, mouse, mouse_cursor_visible, high_resolution_scrolling, native_pen_touch, client_keyboard_mouse_seat_isolation, enable_input_only_mode, forward_rumble, back_button_timeout, multiseat_moonlight_input.

Field What it does
Enable Gamepad Input (controller) Allows guests to control the host system with a gamepad / controller
Emulated Gamepad Type (gamepad) Choose which type of gamepad to emulate on the host. Automatic follows the client’s controller: an Xbox, PlayStation or Nintendo pad gets its own kind, a Steam Controller gets a DualSense (its gyro and both touchpads fit there), and anything else gets a DualSense when it reports motion or a touchpad and an Xbox One pad otherwise.
Emulate a DS4 gamepad if the client gamepad reports motion sensors are present (motion_as_ds4) If disabled, motion sensors will not be taken into account during gamepad type selection.
Emulate a DS4 gamepad if the client gamepad reports a touchpad is present (touchpad_as_ds4) If disabled, touchpad presence will not be taken into account during gamepad type selection.
Map Back/Select to Touchpad Click (ds4_back_as_touchpad_click) When forcing DS4 emulation, map Back/Select to Touchpad Click
Randomize DualSense MAC Address (ds5_inputtino_randomize_mac) Randomize the emulated DualSense MAC address to improve compatibility with some games and clients.
Isolate Host Gamepads in Private Stream (headless_gamepad_isolation) Prevents controllers physically connected to the host from appearing inside Private Stream sessions. Disable only when you intentionally use a host-wired controller, such as a DualSense for haptics or adaptive triggers.
Isolate Client Gamepads from Other Linux Users (client_gamepad_seat_isolation) Disabled by default to preserve controller identity and local access. When enabled, uses Polaris-specific device names and a dedicated Linux seat so logind does not grant the active local desktop user automatic access. This does not isolate applications running under the same Unix account or users in the input group. Re-run Polaris host setup after upgrading, then stop active streams and restart Polaris so existing controller nodes are recreated.
Experimental Moonlight Multiseat Input (multiseat_moonlight_input) Linux-only, file-configured preview gate for the host side of multiseat input. It is disabled by default and does not select a seat by itself; only a separately authenticated, already-admitted seat selection can use it. Leave disabled outside bounded development testing.
Home/Guide Button Emulation Timeout (back_button_timeout) If the Back/Select button is held down for the specified number of milliseconds, a Home/Guide button press is emulated. If set to a value < 0 (default), holding the Back/Select button will not emulate the Home/Guide button.
Enable Keyboard Input (keyboard) Allows guests to control the host system with the keyboard
Key Repeat Delay (key_repeat_delay) Control how fast keys will repeat themselves. The initial delay in milliseconds before repeating keys.
Key Repeat Frequency (key_repeat_frequency) How often keys repeat every second. This configurable option supports decimals.
Always Send Scancodes (always_send_scancodes) Sending scancodes enhances compatibility with games and apps but may result in incorrect keyboard input from certain clients that aren’t using a US English keyboard layout. Enable if keyboard input is not working at all in certain applications. Disable if keys on the client are generating the wrong input on the host.
Map Right Alt key to Windows key (key_rightalt_to_key_win) It may be possible that you cannot send the Windows Key from Moonlight directly. In those cases it may be useful to make Polaris think the Right Alt key is the Windows key
Enable Mouse Input (mouse) Allows guests to control the host system with the mouse
High Resolution Scrolling Support (high_resolution_scrolling) When enabled, Polaris will pass through high resolution scroll events from Moonlight clients. This can be useful to disable for older applications that scroll too fast with high resolution scroll events.
Native Pen/Touch Support (native_pen_touch) When enabled, Polaris will pass through native pen/touch events from Moonlight clients. This can be useful to disable for older applications without native pen/touch support.
Isolate Client Keyboard and Mouse from the Host Session (client_keyboard_mouse_seat_isolation) Disabled by default. When enabled, Polaris assigns the virtual keyboard and mouse it creates for clients to a dedicated Linux seat, so logind does not hand them to the desktop session logged in at the machine. Use it when you stream a private session while someone is working at the host: without it, a client’s typing and pointer movement also drive the host desktop. Applications running under the same Unix account and members of the input group are not isolated. Stop active streams and restart Polaris so the devices are recreated.
Enable Input Only Mode (enable_input_only_mode) Add an Input Only app entry. When enabled, the app list will only show the current running app and the Input Only entry when streaming. The Input Only entry will not receive any image or audio. Useful for operating the desktop on TV or connecting peripherals which the TV doesn’t support with a phone.
Forward Rumble Messages (forward_rumble) Forward Rumble Messages to clients

Exposure (whether the host announces itself for discovery, whether pairing is enabled, UPnP port forwarding, the address family, the base port and the port map derived from it, which origins may reach the web UI, and an external IP for clients outside the network) and transport security (the encryption mode for LAN and WAN sessions, Trusted Subnet Auto-Pairing, and the trusted subnet list in CIDR form). Keys: enable_discovery, enable_pairing, upnp, address_family, port, origin_web_ui_allowed, external_ip, lan_encryption_mode, wan_encryption_mode, trusted_subnets.

The first-run wizard’s Network step can trust a detected home network with one click, or a network typed in CIDR form. It adds the network to trusted_subnets, keeping the entries already there, and turns on Trusted Subnet Auto-Pairing (trusted_subnet_auto_pairing). A saved trusted network applies to the next pairing request, with no restart. Any device on a trusted network can pair without a PIN, so only trust networks you control.

Field What it does
Enable Auto Discovery (enable_discovery) When disabled, you’ll need to manually enter host IP on the client to pair.
Enable Pairing (enable_pairing) Enable pairing for the Moonlight client. This allows the client to authenticate with the host and establish a secure connection.
UPnP (upnp) Automatically configure port forwarding for streaming over the Internet
Address Family (address_family) Set the address family used by Polaris
Port (port) Set the family of ports used by Polaris
Origin Web UI Allowed (origin_web_ui_allowed) The origin of the remote endpoint address that is not denied access to Web UI
External IP (external_ip) If no external IP address is given, Polaris will automatically detect external IP
LAN Encryption Mode (lan_encryption_mode) This determines when encryption will be used when streaming over your local network. Encryption can reduce streaming performance, particularly on less powerful hosts and clients.
WAN Encryption Mode (wan_encryption_mode) This determines when encryption will be used when streaming over the Internet. Encryption can reduce streaming performance, particularly on less powerful hosts and clients.
Ping Timeout (ping_timeout) How long to wait in milliseconds for data from moonlight before shutting down the stream

Everything the stream is made of: the launch mode and its runtime, host audio capture and the sink Polaris captures, the display outputs and fallback mode the Display Planner writes, Auto Quality, advanced tuning such as the maximum bitrate and the adaptive range, and how long a paused session waits for a client to resume. The mode cards, the live Auto Quality strip, and the planner are explained in Launch modes and capture paths. Keys: linux_stream_mode, headless_mode, linux_use_cage_compositor, linux_prefer_gpu_native_capture, fallback_mode, display_plan, adaptive_bitrate_enabled, disconnect_resume_timeout_seconds.

Load handling (limit the capture frame rate to what the client asked for), compatibility switches for older tools and clients (environment variable compatibility, legacy app ordering, streaming on even when encoder probing fails, the experimental browser stream), and the capture and encoder overrides that force a specific path when automatic selection is wrong. Keys: limit_framerate, envvar_compatibility_mode, legacy_ordering, ignore_encoder_probe_failure, browser_streaming, encoder.

Field What it does
FEC Percentage (fec_percentage) Percentage of error correcting packets per data packet in each video frame. Higher values can correct for more network packet loss, but at the cost of increasing bandwidth usage.
Quantization Parameter (qp) Some devices may not support Constant Bit Rate. For those devices, QP is used instead. Higher value means more compression, but less quality.
Minimum CPU Thread Count (min_threads) Increasing the value slightly reduces encoding efficiency, but the tradeoff is usually worth it to gain the use of more CPU cores for encoding. The ideal value is the lowest value that can reliably encode at your desired streaming settings on your hardware.
Limit capture framerate (limit_framerate) Limit the framerate being captured to client requested framerate. May not run at full framerate if vsync is enabled and display refreshrate does not match requested framerate. Could cause lag on some clients if disabled.
ENVVAR compatibility mode (envvar_compatibility_mode) Enable compatibility mode for environment variables. This will modify the behavior of certain environment variables to be more compatible with older tools.
App ordering for legacy clients (legacy_ordering) Enable ordering support workaround for legacy clients. Can cause issues with clients or scripts that can’t handle UTF8 correctly. Nova clients support this by default.
Ignore Encoder Probe Failure (ignore_encoder_probe_failure) Allow streaming to continue even if probing for encoders fails. This may result in streaming failure if no encoder is available.
Browser Stream (browser_streaming) Experimental zero-install LAN browser access over WebTransport and WebCodecs. Best for touch, mouse, keyboard, launchers, desktop, and slower games; use Nova or Moonlight for controller-first play.
HEVC Support (hevc_mode) Allows the client to request HEVC Main or HEVC Main10 video streams. HEVC is more CPU-intensive to encode, so enabling this may reduce performance when using software encoding.
AV1 Support (av1_mode) Allows the client to request AV1 Main 8-bit or 10-bit video streams. AV1 is more CPU-intensive to encode, so enabling this may reduce performance when using software encoding.
Force a Specific Capture Method (capture) On automatic mode Polaris will use the first one that works. NvFBC requires patched nvidia drivers.
Force a Specific Encoder (encoder) Force a specific encoder, otherwise Polaris will select the best available option. Note: If you specify a hardware encoder on Windows, it must match the GPU where the display is connected. The first-run wizard’s GPU and Encoder step shows what Automatic picks on this host and what hardware encoding still needs, and saves this key when you choose an encoder there. A changed encoder takes effect after a restart.

Where Polaris keeps its app library, its log, its runtime state, and, optionally, the web credentials separately from the state file. The defaults are listed under Files. Keys: file_apps, log_path, file_state, credentials_file.

Field What it does
Apps File (file_apps) The file where current apps of Polaris are stored.
Logfile Path (log_path) The file where the current logs of Polaris are stored.
State File (file_state) The file where current state of Polaris is stored
Credentials File (credentials_file) Store Username/Password separately from Polaris’s state file.
Private Key (pkey) The private key used for the web UI and Moonlight client pairing. For best compatibility, this should be an RSA-2048 private key.
Certificate (cert) The certificate used for the web UI and Moonlight client pairing. For best compatibility, this should have an RSA-2048 public key.

The AI tab is covered under AI provider settings; the encoder tabs under NVIDIA NVENC Encoder and Linux HDR and Main10, with the codec switches (hevc_mode, av1_mode), the quantisation fallback (qp), and the software encoder thread floor (min_threads) on the encoder pages themselves.

On Linux, treat sessions that log stream_hdr_enabled=false as SDR even if the client requests HDR. Forcing hdr_mode = 2 can still select a 10-bit HEVC/Main10 or P010 encode path, but that does not create a true HDR source when the captured display path is SDR and may produce incorrect colors on some VAAPI stacks.

True Linux HDR requires the active capture path to expose HDR display metadata. Today that means a KMS/DRM display path with an HDR-capable output reporting HDR_OUTPUT_METADATA, plus a client HDR request and a 10-bit-capable encoder. A valid true HDR session logs:

HDR metadata: available=true usable=true
Color coding: HDR (Rec. 2020 + SMPTE 2084 PQ)
HDR decision: ... display_hdr=true hdr_metadata_available=true stream_hdr_enabled=true

If the log says HDR metadata: available=true usable=false, Polaris found an HDR metadata blob but the static metadata is incomplete, such as a custom EDID with a zero max luminance value. Polaris treats that stream as SDR instead of tagging it as HDR with unusable metadata.

Headless labwc/wlroots sessions are intentionally treated as SDR until the headless display path can truthfully provide HDR metadata. In that mode, hdr_mode = 2 can still be useful to test Main10/P010 encode support, but Polaris will not advertise true HDR to the client without metadata.

The configuration that carries true HDR today, verified end to end:

capture = kms
linux_stream_mode = desktop_display

host_virtual_display, desktop_takeover and gamescope_stream also show the real output; headless_stream and windowed_stream do not. KMS capture needs CAP_SYS_ADMIN on the binary, granted once with sudo -H polaris --setup-host --enable-kms. The paired client must not have HDR forced off in client_profiles.json (hdr) or device_db.json (hdr_capable), and the client has to request HDR itself. The full checklist with the log line for each step is in runtime.md.

For AMD VAAPI hosts, validate SDR first:

encoder = vaapi
hdr_mode = 0
color_range = 1

Then test HEVC Main 8-bit before enabling Main10 or client HDR requests.

Controls FFmpeg’s split_encode_mode private option for Linux NVENC HEVC and AV1 encoders. Polaris only passes this option when the selected FFmpeg encoder exposes it; H.264 and native Windows NVENC ignore it.

Recommended values:

Value FFmpeg value Recommendation
disabled 15 Default. Preserves legacy behavior after FFmpeg updates.
auto 0 Let NVIDIA’s driver and FFmpeg decide after validating your GPU/driver stack.
2 2 Useful first manual test on multi-NVENC GPUs, especially for 4K120 HEVC/AV1.
forced 1 Experimental. Use only when comparing against auto and explicit engine counts.
3 3 Experimental. Use only on GPUs known to expose three usable NVENC engines.

Vulkan Video is an experimental Linux hardware-encode path for drivers that expose the Vulkan Video encode extensions. Explicit selection supports direct DRM/KMS, wlroots, and Portal capture. GPU-native DRM/KMS and wlroots frames remain matched to the encoder’s render node; Portal and any safely retired wlroots DMA-BUF route use the Vulkan RAM uploader rather than pretending a CPU copy is zero-copy.

With encoder left on Auto, Polaris promotes Vulkan only for a compatible AMD private-compositor route that can validate the exact first live GPU-native frame and retire a failed route to VA-API. NVIDIA stays on NVENC, Intel stays on VA-API, and AMD desktop capture stays on VA-API by default.

Before selecting it, enable KMS host access once, restart Polaris, then set both overrides:

Terminal window
sudo -H polaris --setup-host --enable-kms
capture = kms
encoder = vulkan

If the selected GPU lacks the requested H.264 or HEVC Vulkan Video encoder, or an explicitly selected capture path cannot initialize, session startup fails with an explicit diagnostic. AV1 stays disabled on this experimental path until the bundled FFmpeg implementation passes Vulkan validation. Switch the encoder back to vaapi or nvenc; Polaris does not silently replace an explicit Vulkan choice.

Selects FFmpeg’s Vulkan Video latency/quality target. 2 (low latency) is the streaming default; 0 lets FFmpeg decide, 1 favors quality, and 3 requests ultra-low latency.

Selects Vulkan Video rate control. 2 (constant bitrate) is the streaming default. 0 lets FFmpeg and the driver decide, 1 selects constant-QP mode, and 4 selects variable bitrate.

The AI optimizer is optional. Configure it in the web UI if you want connection testing before saving, or set it directly in polaris.conf. The first-run wizard offers the same provider choice, sign-in, model list, test and enable step; timeouts, the explanation cache and history stay in the AI tab. Saved provider settings take effect right away; a request that is already running finishes with the settings it started with.

ai_enabled = enabled
ai_provider = anthropic
ai_model = claude-haiku-4-5-20251001
ai_auth_mode = subscription
ai_timeout_ms = 30000

For Claude subscription explanations, install a current Claude Code CLI with --safe-mode support on the Polaris host. Sign in as the same OS user running Polaris:

Terminal window
claude auth login
claude auth status

Polaris reuses that CLI subscription login. There is no separate Claude login in the web UI, and a login on another computer or under root does not authorize the Polaris service account. Choose Claude → Claude CLI, run Test provider, then save and enable explanations. The saved-runtime status distinguishes CLI discovery from verified subscription authentication.

Doctor sends redacted evidence through a private, temporary request directory, with CLI tools, MCP servers, ordinary discovered customizations, and session persistence disabled. The installed, unmodified Claude Code and its administrator configuration are trusted host software: managed hooks and policy can still run with the service user’s permissions. This is not an OS sandbox. Polaris does not collect, copy, or intermediate subscription tokens; sign-in stays with Claude Code. Polaris validates the returned explanation and does not apply settings or run recovery actions from AI responses. This response boundary also applies to the other Doctor providers. Requests use the configured timeout, bounded to 1–120 seconds, and a 64 KiB output limit. Older CLIs that do not support the required restriction flags fail rather than retrying without them. This Doctor transport uses Claude subscriptions; Anthropic API-key Doctor explanations remain unsupported.

Subscription availability is provider-specific. OpenAI uses the signed-in Codex CLI below. DeepSeek’s documented integration uses an API key; signing into its chat website does not establish a supported Polaris subscription transport. Local endpoints can run without a provider account. See Claude Code authentication and credential use and DeepSeek’s API setup.

With an API key, the hosted model names apply:

ai_enabled = enabled
ai_provider = openai
ai_model = gpt-5.4-mini
ai_auth_mode = api_key
ai_api_key = sk-proj-...

With the signed-in Codex CLI, the models are the ones Codex offers the signed-in account, which is a different set from the hosted API (a ChatGPT account cannot use gpt-5.4-mini through Codex, for example). The console’s model list reads the catalog the CLI caches on this host, and its default is the model the CLI is configured with. Leave ai_model empty to follow that default, or pick one of the listed models:

ai_enabled = enabled
ai_provider = openai
ai_auth_mode = subscription
ai_model =

If the list is empty, run codex once in a terminal as the user Polaris runs as, then refresh. When Codex refuses a request, the test result shows its reason.

ai_enabled = enabled
ai_provider = gemini
ai_model = gemini-2.5-flash
ai_auth_mode = api_key
ai_api_key = YOUR_GEMINI_KEY

Select the DeepSeek provider and DeepSeek API profile in the web UI, enter a DeepSeek API key, refresh the model list, and run Test Explanation before saving. To configure it directly:

ai_enabled = enabled
ai_provider = deepseek
ai_model = deepseek-v4-flash
ai_auth_mode = api_key
ai_api_key = YOUR_DEEPSEEK_KEY
ai_base_url = https://api.deepseek.com
ai_timeout_ms = 30000

The profile uses DeepSeek’s OpenAI-compatible endpoint for model discovery and chat completions, with JSON-object output and thinking disabled for bounded explanations. Polaris validates the returned explanation against the same six-field contract and keeps deterministic Doctor results as the source of truth.

If you previously entered a DeepSeek URL under OpenAI or Anthropic, select the DeepSeek API profile and enter its key again. This sets the compatible output format and base URL; the Anthropic /anthropic endpoint is not used by Polaris’s Doctor explanation path. Subscription login is not supported for DeepSeek. Model discovery reflects the API’s current list; the configured default is only a fallback when discovery is unavailable.

ai_enabled = enabled
ai_provider = local
ai_model = gpt-oss
ai_auth_mode = none
ai_base_url = http://127.0.0.1:11434/v1
ai_timeout_ms = 60000

Large local models can need substantially longer than cloud models for their first response while weights are loaded. The web UI’s local-provider profiles start with a bounded 60-second timeout and report inference timeout, connection, authentication, missing-model, and response-format failures separately. Lower the timeout after the model is warm if you prefer faster failure.

Security in the web UI changes the sign-in for the web console only; paired clients are not affected. The page has two steps and one commit:

  1. Verify current access: type the current username and password. Polaris confirms the account before it changes anything.
  2. Choose new credentials: type the replacement password twice. Leave the username blank to keep the current one.
  3. Save and Reload: Polaris applies the new pair and reloads this browser session, so you sign in again immediately with what you just set.

The password fields have a Show control for checking what you typed. If you lose the credentials instead of rotating them, use the reset below.

If you lose access to the web UI credentials:

Terminal window
polaris --creds new-username new-password

Run the command as the same user account that runs Polaris, then restart Polaris before signing in with the new credentials:

Terminal window
systemctl --user restart polaris

If Polaris is running in the foreground, stop it and start it again instead.

To re-run the host setup steps explicitly:

Terminal window
sudo -H polaris --setup-host

Optional DRM/KMS setup:

Terminal window
sudo -H polaris --setup-host --enable-kms

These settings apply to VA-API on Linux. A successful web configuration save publishes the settings for the next encoder session; an active codec keeps its current settings. Hand-edited configuration is loaded when Polaris starts.

Setting Default Choices
vaapi_quality auto auto, speed, balanced, quality
vaapi_rc auto auto, cbr, vbr, avbr, cqp, icq, qvbr
vaapi_blbrc auto auto, enabled, disabled
vaapi_strict_rc_buffer disabled enabled, disabled

Automatic quality and block bitrate control leave the codec’s existing defaults untouched. Automatic rate control preserves Polaris’s current policy: Intel, AV1, and explicit strict-buffer requests prefer VBR with a single-frame buffer, then CBR, then CQP. Other paths prefer CBR, then VBR, then CQP with the existing buffer size. No new AMD or Intel default is promoted by these controls.

Quality presets use the selected driver profile and encoding entrypoint’s reported range: quality selects level 1, speed selects the highest level, and balanced selects half the range, rounded down with a minimum of 1. Unknown or unsupported ranges retain the driver default. This adjusts encoding effort; it is separate from the qp setting.

A manual rate-control choice must be supported by both the driver and codec. Unsupported choices use automatic policy and produce a warning. A supported manual mode overrides the Intel/AV1 buffer preference; vaapi_strict_rc_buffer still requests a single-frame buffer. CQP, ICQ, and QVBR use qp for their quality value. CQP and ICQ do not enforce a bitrate target; FFmpeg may ignore buffer settings in modes that do not use a hypothetical reference decoder.

Block bitrate control requires the driver’s VA_RC_MB capability and a mode other than CQP. An unsupported enable request is reported and disabled when the codec exposes the option. Startup logs report the selected rate control, selection policy, buffer choice, compression level, quality range, and block bitrate control. Controls are queried for every encoder initialization, using the selected profile and entrypoint. Existing DMA-BUF containment is preserved.

Example of an opt-in configuration:

vaapi_quality = balanced
vaapi_rc = vbr
vaapi_blbrc = enabled
vaapi_strict_rc_buffer = enabled

These settings follow the libva quality and rate-control API and FFmpeg VA-API mode handling. Hardware acceptance on AMD and Intel is required before changing automatic encoder defaults.

With KMS capture, output_name accepts the connector’s kernel name (for example DP-1 or HDMI-A-1) when that name identifies one available output. The log also lists a qualified form such as kms:pci-0000:01:00.0/DP-1. Qualification uses the GPU’s PCI address, not its changing /dev/dri/cardN number. The pci-0000:01:00.0/DP-1 shorthand is accepted too.

Use the qualified form when different GPUs expose the same connector name. An ambiguous or missing request fails; Polaris does not select another output. Named capture rechecks the opened GPU, connector, and CRTC during initialization and rechecks the connector binding while capturing. A disconnect or reassignment requires capture reinitialization. Replugging the same physical port can resolve its qualified name even when card or display enumeration order changes.

Existing numeric configurations retain the original enumeration positions. The list is not sorted or renumbered by the new names. A GPU without a reliable PCI identity, or a connector with multiple active capture planes, retains numeric selection. When unnamed entries remain, use qualified names for named requests; a plain connector alias cannot rule out ambiguity with those entries.

These identifiers select KMS capture outputs. They do not change the X11, Wayland private-runtime, Windows, or macOS display-selection contracts.