# OpenThinRDP: the documentation in one file Source: https://github.com/OpenThinRdp/openthinrdp (main), release 0.2.1, built 2026-10-18. Every section below is one file of the repository. ---

OpenThinRDP

The open source thin client OS with RDP over UDP
for Windows Remote Desktop, RDS and VDI: FreeRDP with the UDP transport that upstream does not ship yet.

⬇ Download the ISO (latest release)  ·  Try it live, nothing installed  ·  Website  ·  Italiano

Boot menu: try it live or install Sign-in form Settings of the device

OpenThinRDP turns small PCs and old thin clients into kiosks that boot straight into a sign-in form and connect to Windows: through an RD Connection Broker to the user's desktop or VM, or straight to a session host or to any Windows PC with Remote Desktop turned on, **on premises or in the cloud** (Azure, AWS, any provider). They are managed from a shared folder with a small Windows manager: status of every device, central settings, logo, signed updates, and the screen of any device in your browser. No agent, no management server, no cloud service to depend on, no licence per device. > **Not openthinclient.** OpenThinRDP is a new, independent project, not affiliated with openthinclient: the names > sound alike, nothing else is shared. openthinclient is an older project whose devices boot over the network (PXE) > from its management server; OpenThinRDP is installed on each device or runs live from a USB stick, needs no server > and is built around RDP with FreeRDP's UDP transport. It grew out of a real migration away from a commercial thin client OS, on a fleet of Dell OptiPlex 3000 Thin Clients connected to a Windows 11 VDI on Hyper-V, where the system it comes from runs every day. > **Status: in production every day.** It runs the whole thin client fleet of the company it comes from: 14 Dell > OptiPlex 3000 Thin Clients at desks, tills, a laboratory, weighing stations and a wall screen on Wi-Fi, on a > Windows 11 VDI on Hyper-V behind two RD Connection Brokers, signing in with Kerberos and NTLM as fallback. The > devices were converted in place from the system they ran before and take every release through their management > folder (the manager checks each GitHub release and signs it again with the key of the site), installed during > sessions and applied when the session is over. FreeRDP security releases reach OpenThinRDP [within 7 days](SECURITY.md#freerdp-security-fixes) > (3.32.2: four hours). The ISO is tested in QEMU/KVM with Secure Boot at every release and the system tests run on > every commit ([CI](https://github.com/OpenThinRdp/openthinrdp/actions/workflows/tests.yml)). What is in production, > what is only tested and what does not exist yet: > **[docs/status.md](docs/status.md)**. Try it live, then on one device, before rolling it out. ## RDP over UDP that keeps going on a bad line It started with a faulty switch port losing 43% of the large packets: over TCP the remote desktop froze. We reproduced that line, then raised the loss on every packet from the virtual machine step by step (Windows 11 VDI, full-screen load changing every pixel, frame rate measured by the server): | packets lost | over TCP | **OpenThinRDP over UDP** | |---|---|---| | 20% | 0.6 frames per second: the screen barely moves | not measured (19-21 fps at 50%) | | 43% of the large packets\* (the faulty port) | 0-0.1 fps | **16-21 fps**, 7 minutes in a row | | 50% | not measured (0.2 fps already at 30%) | **19-21 fps**, as on a clean line | | 70% | not measured | **18-19 fps**\*\* | | 80% | not measured | 4 fps, then the tunnel gives up | \* The other rows lose that share of every packet; this one 43% of the large packets and 5% of the small ones. \*\* At 70% the TCP connection RDP keeps beside the tunnel may end the session after 20 s without an acknowledgement. One environment (Dell OptiPlex 3000 Thin Client, Windows 11 VDI on Hyper-V), loss emulated with netem: [the method and the scripts](tests/udp-bench/README.md), to get your own numbers. Getting there took a fix to FreeRDP's UDP transport, which we found and reported in [#13608](https://github.com/FreeRDP/FreeRDP/pull/13608), and which is now part of it. Every step and the TCP numbers: [docs/udp.md](docs/udp.md#on-a-lossy-line). ## Who it is for - You publish desktops with **Windows Remote Desktop Services**: RD Connection Broker with session hosts or a Hyper-V VDI collection. Or there is no broker at all: each device connects to one Windows PC or server (set its name instead of the broker), e.g. a reception desk that works on the PC in the office. Devices outside the office go through the **RD Gateway** over HTTPS ([`RDP_GATEWAY`](docs/configuration.md#rd-gateway)) or their own **VPN**, WireGuard or OpenVPN ([`VPN`](docs/configuration.md#vpn)). - Your desktops are **in the cloud**: an RDS deployment or Windows virtual machines in Azure, AWS, Google Cloud or with any provider, reached through the RD Gateway or a VPN, with UDP through a VPN ([details](docs/configuration.md#desktops-in-the-cloud)). Azure Virtual Desktop and Windows 365 are not supported yet (they need a Microsoft Entra ID sign-in). - You have **x86-64 PCs or thin clients, new or old**: UEFI with or without Secure Boot, or a legacy BIOS, with the complete Debian kernel and firmware ([hardware](docs/hardware.md)), and want to keep them useful without a licence per device. - You want to **try RDP over UDP** on real clients, or help bring it into FreeRDP. - You need **screens that only show a web page**: hall TVs, dashboards, info points, production boards. The same system, no sign-in, the page chosen centrally ([see below](#screens-that-only-show-a-web-page)), so one image covers the whole fleet. Not for you (yet): Citrix, VMware/Omnissa Horizon and other protocols (OpenThinRDP does RDP only), ARM devices (the Raspberry Pi 4 is [in preparation](https://github.com/OpenThinRdp/openthinrdp/issues/61)), 32-bit only PCs. ## Requirements | | Minimum | |---|---| | Device | any 64-bit x86 PC or thin client, **UEFI** (Secure Boot supported, not required) **or legacy BIOS**: old office PCs and used thin clients are fine | | Drivers | the complete Debian 13 kernel and every Debian firmware package: what runs Debian 13 runs OpenThinRDP | | Memory | 2 GB of RAM | | Storage | 8 GB to install it (eMMC, SSD or disk); nothing to try it live from a USB stick | | Network | cable, Wi-Fi or a USB Ethernet adapter; WPA2/WPA3 personal or Enterprise, and 802.1X on the cable | | TPM 2.0 | optional: with one, the device's secrets (management folder password, Wi-Fi key, SSH host keys) are kept encrypted in it ([security](docs/security.md)); without one it works the same | | Windows side | an RD Connection Broker (RDS session hosts or a Hyper-V VDI collection), or any Windows PC or server that accepts Remote Desktop (Pro, Enterprise, Server), on premises or in any cloud | Tested hardware and firmware settings: [docs/hardware.md](docs/hardware.md). ## Try it in five minutes 1. [Download `openthinrdp-.iso`](https://github.com/OpenThinRdp/openthinrdp/releases/latest) and write it to a USB stick with any raw image writer (Rufus in DD mode, balenaEtcher, `dd`). 2. Boot the PC from the stick. The menu starts the **live system** by itself after 10 seconds: it runs from the stick and **never writes to the disk**. 3. With a network, when GitHub has a newer version than the ISO it asks whether to download it or to use the ISO's (no answer in 30 seconds: the newer one), so the ISO does not need downloading again for every release. Then the settings dialog opens: set the broker (or the name of a Windows PC) and the domain, save, sign in. 4. Like it? Restart and choose **Install**: it shows the disk it is going to erase and asks before writing anything (answer *Yes* to go on), then about three minutes. It installs on the **first internal disk**, never a USB disk (not even the installation stick). For rollouts, *Unattended install* skips the question. What the device needs: [Requirements](#requirements). ## What it does - **Remote desktop over UDP.** Windows sends the graphics (and USB, sound and camera) over UDP and adapts frame rate and quality to the line instead of queueing: smoother on busy servers, lossy Wi-Fi and VPNs. The image ships FreeRDP built with the UDP transport that is still under review upstream ([FreeRDP #13608](https://github.com/FreeRDP/FreeRDP/pull/13608)). Per device: `auto` (UDP, TCP when UDP is blocked), `udp` (UDP only) or `tcp`. In our 3-hour test with packet loss, bandwidth limits, network cuts and killed clients the session stayed on UDP the whole time ([results](https://github.com/FreeRDP/FreeRDP/pull/13608#issuecomment-6110587552)). See [docs/udp.md](docs/udp.md). - **Sign-in form, then full screen remote desktop.** Boots in a few seconds with your logo, no desktop, no shell. Retries, fallback brokers, clear messages for "no network", "broker not reachable", "the virtual machine does not start", wrong password (never retried, so accounts are not locked). A virtual machine that is restarting or installing a Windows update gets "Waiting for Windows" (up to 20 minutes) instead of an error. - **Reconnects by itself** after network drops, stops when the session is ended on purpose (sign out, disconnect, VM shut down by an administrator), and warns before taking over a session open on another device. A session taken over by another device shows "Session opened elsewhere" with *Resume here*. - **USB done right for VDI.** Signature keys, tachograph keys and other special devices go to Windows as whole USB devices, also when plugged in during the session. **Webcams and headsets work in meetings**: the camera goes through the RDP camera channel and the microphone through the RDP audio channel, so Teams, Zoom and the browser in the session see both (a microphone passed as a USB device stays outside the remote session). Keyboards, mice, touch screens, hubs and network adapters stay local; smart card readers go through the smart card channel. Touch screens work as touch in Windows. With `USB_MODE=disks` USB sticks stay on the thin client and appear in Windows as drives; with `USB_MODE=auto` the thin client decides device by device: a stick as a drive, a device with storage and something else (an instrument, a key with its own driver) whole. - **Managed from a shared folder (SMB) with a Windows manager**, no server: the whole fleet in one window, settings for all devices or one, and the **remote screen** of any device ([the manager](#the-manager-the-whole-fleet-from-one-window)). - **Signed updates** that never interrupt a session: from GitHub releases (checked every 3 hours, installed in the background, applied when the session is over), or from your folder when you decide. **Try a version on a few devices first**, and **go back** to an earlier one when something breaks ([`UPDATE_TO`](docs/configuration.md#management)): FreeRDP goes back with it. Debian security updates install by themselves. - **Settings dialog on the device** (optionally behind a PIN): broker or PC, network (DHCP, static, Wi-Fi), management folder and its account, USB mode, web button, power schedule, language. A new device names itself from its network card (`thinclient-1a2b3c`). - **Diagnostics for the help desk**, no PIN needed (the ℹ button): network, can each broker or PC be reached, time, management folder, last events. Read only. - **Locked down**: no desktop, no menus, no text consoles (Ctrl+Alt+F1..F12 do nothing), no password-less account. - **Several monitors** become one remote desktop spanning all of them (`MULTIMON`). - **Power schedule and Wake-on-LAN**, screen always on, monitors switched on late handled. - **Web button**: a web app, free browsing, or a full screen kiosk page, with Chromium ([screens that only show a web page](#screens-that-only-show-a-web-page)). - English and Italian interface; more languages are one file each.

Desktop start with the real steps End of session with a countdown Software update in progress

## The manager: the whole fleet from one window `OpenThinRDP-Manager` is one PowerShell script for Windows 10/11 (English and Italian, nothing to install) that works on the shared folder the devices report to. No server, no agent, no database: what you see is the files in the folder.

Manager: every device, its state, user and version, and its settings Manager, Updates page: all devices on one version, a pilot device on the newest

- **Every device at a glance**: state, user, version, last report, a readable summary and the event log of each. - **Settings for all devices or one**, checked before saving, with the value in use on each device and what is still on its way. - **A new version on a few devices first, then on all**: *Release from GitHub* asks *pilot devices only* or *all devices*; the others stay where they are until *Release to all devices*. - **Back to an earlier version when something breaks**, for the whole fleet or one device, FreeRDP included: one click on the Updates page. - **Remote screen** of any device in your browser, over SSH only, with a notice on the device. - **Problems across the fleet** grouped from every event log; a **vault** for the SSH key and the signing key; your fleet on **its own signing key**; logo and background; and the manager **updates itself** from the signed releases. **Everything it does, page by page, with the steps for a pilot group and for going back: [docs/manager.md](docs/manager.md).** ## Screens that only show a web page The same image runs the screens that never need a Windows desktop: a TV in the hall, a dashboard in the office, an info point, a board in production. With `WEB_BUTTON=kiosk` and `KIOSK_URL`: - At power on the page opens **full screen in Chromium**: no sign-in, no desktop, no menus, no address bar. - The page is set **centrally**, for every screen (`config\common.conf`), a group or a single one (`config\.conf`) in the management folder. Change it and each screen picks it up within a few minutes and reopens the page, without a restart. - It **reloads itself** every `KIOSK_RELOAD_MIN` minutes when nobody is touching it, so a page that went to an error after a network glitch comes back by itself. - The **power schedule** switches the screens on and off (`POWER_ON_TIME`, `POWER_OFF_TIME`, `POWER_ON_DAYS`), on cable or Wi-Fi; status and events of every screen are in the management folder like any other device. - Cookies and saved sign-ins stay across restarts. For dashboards behind a login, a link with an access token (offered by most dashboard tools) avoids signing in on the screen at all. - Someone at the screen can still reach the sign-in form: it shows for 5 seconds before the page, and Ctrl+Shift+W closes the page. The settings can be locked with a PIN. Settings: [docs/configuration.md](docs/configuration.md#web-button-and-kiosk-page). ## Why it is built this way **Why FreeRDP is not updated automatically.** Debian security updates install by themselves, but FreeRDP is excluded. First, the image ships its own FreeRDP build with UDP, which Debian's packages would replace with a version without it. Second, the remote desktop client is the part users depend on: a new FreeRDP version can change how sessions, USB devices, smart cards or the graphics behave, and on a fleet a regression stops everyone at the same moment. So FreeRDP comes with OpenThinRDP releases, after testing on real devices, and you can still keep it as it is on one device (`/etc/openthinrdp/freerdp-pinned`). **Why UDP from a pull request that is not merged yet.** Over TCP, a busy server or a lossy line makes the screen fall behind; over UDP, Windows lowers quality instead. The pull request is solid in our tests, and the only way it gets merged is people using it and reporting. `TRANSPORT=tcp` is one setting away, and the image follows the upstream code as it changes. **Why a shared folder instead of a management server.** Every Windows network already has a file share and someone who can manage permissions on it. The devices only read and write files there, so there is nothing to install, patch or license on the server side, and the settings are plain text files you can read, diff and back up. **Why Debian.** Stable, security updates for years, every driver and firmware package, and a kiosk on top of it that you can read: the whole client side is a few shell scripts in `rootfs/opt/openthinrdp`. ## How it compares | | **OpenThinRDP** | Dell ThinOS | IGEL OS | Stratodesk NoTouch | ThinStation | |---|---|---|---|---|---| | Cost | free | comes with Dell thin clients | subscription per device | licence per device | free | | Source | open (Apache 2.0) | closed | closed | closed | open | | Runs on | x86-64 PCs and thin clients, UEFI or BIOS | Dell thin clients | many x86-64 devices | x86-64 PCs, Raspberry Pi | x86 PCs | | Protocols | RDP only | Citrix, Omnissa/VMware, Microsoft and more | Citrix, Omnissa/VMware, Microsoft and more | many | many (you build the image) | | Management | shared folder + Windows tool, no server | Wyse Management Suite | UMS server | NoTouch Center | configuration files on a network server | | Updates | signed, from GitHub or your folder | vendor firmware | vendor | vendor | rebuild the image | | Install | ISO, live or automatic installation | preinstalled | installer or preinstalled | installer | build, then PXE or USB | If you need Citrix or Horizon, vendor support or certified hardware, the commercial systems are the right choice. OpenThinRDP is for networks built on Windows Remote Desktop that want a free, open client that is simple to manage, and for anyone who wants to put FreeRDP's UDP transport to work. Product names are trademarks of their owners; the comparison reflects public information and our understanding, corrections are welcome. Each of them in more detail, and **Linux Mint or Ubuntu with Remmina**: [docs/alternatives.md](docs/alternatives.md). Questions people ask first (hardware, drivers, UDP, Azure, PXE, 802.1X): [docs/faq.md](docs/faq.md). ## What comes next Ideas we would like to build, roughly in this order. Tell us in an issue which ones you need, or send a pull request: - **Network installation (PXE)** for many devices at once. - **More tested hardware** and more languages (one file each). ## Build your own image Your logo, your broker, your settings and keys built in (Linux with Docker or Podman): ``` cp -r image/site.example image/site # then edit image/site/site.conf and build.conf, add your logo image/build.sh ``` The result is `out/openthinrdp-.iso`. See [docs/building.md](docs/building.md). ## Documentation - [CHANGELOG.md](CHANGELOG.md): what changed in each release. - [docs/status.md](docs/status.md): what is in production, what is only tested, what does not exist yet. - [docs/configuration.md](docs/configuration.md): every setting. - [docs/faq.md](docs/faq.md): frequently asked questions. - [docs/alternatives.md](docs/alternatives.md): compared with Linux Mint + Remmina, IGEL OS, Dell ThinOS, Stratodesk, ThinStation, openthinclient. - [docs/manager.md](docs/manager.md): the Windows manager, page by page: devices, settings, pilot devices and going back. - [docs/management.md](docs/management.md): the management folder, central settings, SSH keys, signed updates. - [docs/building.md](docs/building.md): building and customizing the image. - [docs/security.md](docs/security.md): security model and known limits. - [docs/hardware.md](docs/hardware.md): tested hardware and firmware settings. - [docs/udp.md](docs/udp.md): the UDP transport and the FreeRDP the image ships. ## Layout | Folder | Content | |---|---| | `rootfs/` | everything installed on the thin client (`/opt/openthinrdp`, udev rules, systemd units) | | `image/` | the ISO build (live-build in a container), the FreeRDP build and the example site folder | | `management/windows/` | the Windows manager (PowerShell, no installation) | | `management/publish/` | building and signing update packages | | `tests/` | tests that run in a Debian container | ## Contributing Issues and pull requests are welcome. Please describe the hardware and the Windows side (broker, session host version) for anything related to connections or devices. See [CONTRIBUTING.md](CONTRIBUTING.md) and the [code of conduct](CODE_OF_CONDUCT.md). Security problems: please report them privately, see [SECURITY.md](SECURITY.md). ## License Apache License 2.0, see [LICENSE](LICENSE). OpenThinRDP uses FreeRDP (Apache 2.0), the noVNC viewer for the remote screen (included unmodified, MPL 2.0 and BSD, [rootfs/opt/openthinrdp/novnc](rootfs/opt/openthinrdp/novnc)) and Debian packages under their own licenses. Windows and Remote Desktop are trademarks of Microsoft; this project is not affiliated with Microsoft or with any vendor named above. --- # Frequently asked questions ## The project ### What is OpenThinRDP? An open source operating system for thin clients that connect to Windows with Remote Desktop: RDS session hosts, VDI collections behind an RD Connection Broker, or any Windows PC with Remote Desktop on. It is Debian 13 with a locked-down kiosk on FreeRDP, the RDP UDP transport built in, and a Windows manager that runs the whole fleet from a shared folder. ### Is it free? Yes. Apache 2.0 licence, no cost per device, no subscription, no account. The ISO and the signed updates are on [GitHub](https://github.com/OpenThinRdp/openthinrdp/releases/latest). ### Is it not too young to use in production? It is young as a public project (October 2026), but not as a system. It comes from the thin client fleet of a company, where it replaced a commercial thin client OS. It runs there every day on 14 devices at desks, tills, a laboratory and weighing stations, and every release reaches that fleet. On top of that: - 170+ system tests run on every commit; - every release is installed in QEMU/KVM with Secure Boot; - UDP was tested for hours with packet loss, cut networks and killed clients. What is in production, what is only tested and what is missing: [status.md](status.md). ### Who maintains it? The IT team of the company where it runs in production, in the open on GitHub. Bug reports, test reports on other hardware and pull requests are welcome. ## Hardware ### Which hardware does it run on? Any 64-bit x86 PC or thin client with UEFI or a legacy BIOS, 2 GB of RAM and 8 GB of storage (none to run it live from a USB stick). Tested: Dell OptiPlex 3000 Thin Client in production, QEMU/KVM with UEFI and with BIOS. Used thin clients such as Dell Wyse 5070, HP t630 or Fujitsu Futro S740 meet the requirements. See [hardware.md](hardware.md). ### Did it leave drivers out to be light? No. The ISO has the complete Debian 13 kernel (Linux 6.12) with every driver, and every firmware package of Debian. Whatever works with Debian 13, or with Ubuntu and Linux Mint of the same period, works here. It is light because it runs no desktop. ### Does it run on Raspberry Pi or ARM? Not yet. The Raspberry Pi 4 is in preparation ([#61](https://github.com/OpenThinRdp/openthinrdp/issues/61)). ### Does it run on old 32-bit thin clients? No: Debian 13 no longer supports 32-bit only CPUs. ## Remote Desktop ### Does it really use RDP over UDP? Yes. FreeRDP has no UDP transport in any release yet; OpenThinRDP ships FreeRDP with the pull request that adds it ([#13608](https://github.com/FreeRDP/FreeRDP/pull/13608)), which we test and report on. With `TRANSPORT=auto` the session uses UDP, and TCP when UDP is blocked. On a line losing 50% of its packets it kept 19-21 frames per second, where TCP was down to 0.6 at 20% ([udp.md](udp.md)). ### Which Windows can it connect to? Windows RDS session hosts and VDI collections through an RD Connection Broker, or a single Windows PC or server with Remote Desktop on (Pro, Enterprise, Server), on premises or in Azure, AWS or any cloud. Kerberos with NTLM as fallback, RD Gateway, and its own WireGuard or OpenVPN for devices outside the office. ### Does it work with Azure Virtual Desktop or Windows 365? Not yet: they need a Microsoft Entra ID sign-in and Microsoft's own gateway, which the kiosk does not do. An RDS deployment or Windows virtual machines in Azure work, through an RD Gateway or a VPN ([desktops in the cloud](configuration.md#desktops-in-the-cloud)). ### Citrix or VMware/Omnissa Horizon? No, OpenThinRDP is RDP only. For those, see [the alternatives](alternatives.md). ### Do webcams, headsets, USB keys and smart cards work? Yes: - Webcams through the RDP camera channel, and headsets through the RDP audio channels, so Teams, Zoom and the browser in the session see them. - USB keys go to Windows as drives, or as whole devices: signature keys, tachograph keys and other special devices, also when plugged in during the session. - Smart card readers through the smart card channel. ## Management ### Do I need a server to manage the devices? No. The devices report to a shared folder (SMB) and read their settings from it; a PowerShell manager for Windows shows the whole fleet, changes settings for all devices or one, and opens the screen of any device in your browser. No agent, no database, no management server ([the manager](manager.md)). ### How are updates done? Can I go back? OpenThinRDP updates come as signed packages, from GitHub or from your folder. They install in the background and apply when the session is over. A new version can go to a few pilot devices first, then to all. If something breaks, the whole fleet or one device goes back to the version before, FreeRDP included. Debian security updates install by themselves. ### Is it secure? - Updates are signed, and can be signed again with the key of your site only. - No desktop, no terminal, no text consoles. - SSH with keys only, off by default. - The device secrets are sealed in the TPM when there is one. - The keys of the fleet are kept in a vault with an access code. See [security.md](security.md) and [SECURITY.md](../SECURITY.md). ### Does it boot over the network (PXE)? Not yet. OpenThinRDP runs live from a USB stick or is installed, with an automatic installation for rollouts. Network boot is considered for a later version. ### Does it support 802.1X? Yes, since 0.2.1: 802.1X on the cable and WPA2/WPA3-Enterprise Wi-Fi, with PEAP (a domain or machine account) or EAP-TLS (a certificate from your CA). The RADIUS server is checked against your CA before any password is sent, and on a switch without 802.1X the device goes on by itself. Setup with Windows NPS or FreeRADIUS: [802.1X and Wi-Fi Enterprise](configuration.md#8021x-and-wi-fi-enterprise). ### Why not just Linux Mint with Remmina? The hardware support is the same, but Remmina has no RDP over UDP. A desktop also has to be locked down and set up PC by PC, and its updates come without pilot devices or a way back. See [the alternatives](alternatives.md#linux-mint-ubuntu-or-debian-with-remmina). ### How do I try it? Write the ISO to a USB stick and start a PC from it: the live system writes nothing to the disk. Set the broker and the domain, and sign in ([five minutes](../README.md#try-it-in-five-minutes)). --- # OpenThinRDP and the alternatives What OpenThinRDP does differently from the other ways of turning PCs into Remote Desktop clients, and when another choice is better. The facts about other products are public information as we understand it; corrections are welcome in the [discussions](https://github.com/OpenThinRdp/openthinrdp/discussions). - [Linux Mint, Ubuntu or Debian with Remmina](#linux-mint-ubuntu-or-debian-with-remmina) - [IGEL OS](#igel-os) - [Dell ThinOS](#dell-thinos) - [Stratodesk NoTouch](#stratodesk-notouch) - [ThinStation](#thinstation) - [openthinclient](#openthinclient) - [What only OpenThinRDP does](#what-only-openthinrdp-does) ## Linux Mint, Ubuntu or Debian with Remmina A general desktop with Remmina, or with FreeRDP itself, is the usual free answer. It is a good one for a PC or two. For a fleet of Remote Desktop clients, here is what it means. **Hardware support is the same.** OpenThinRDP is Debian 13 with its complete kernel and every firmware package ([hardware](hardware.md#drivers-all-of-debians)). A device that works with Mint or Ubuntu of the same period works with OpenThinRDP too. Neither side has a driver advantage. **No RDP over UDP.** Remmina and the `xfreerdp` of the distributions use the distribution's FreeRDP. FreeRDP has no UDP transport in any release yet: it is the pull request [#13608](https://github.com/FreeRDP/FreeRDP/pull/13608), which OpenThinRDP builds and ships. So on a busy server, on Wi-Fi or through a VPN the session runs over TCP. On a line that loses packets, TCP queues the screen while UDP keeps it moving ([the numbers](udp.md#on-a-lossy-line)). **A desktop to lock down.** Users can open other programs, change the settings, fill the disk. Making it a kiosk is your work: automatic sign-in, no terminal, no other applications, no text consoles, a session that comes back by itself, a device that switches off when the session ends. OpenThinRDP is all of that out of the box. **Each PC set up by hand.** There is nothing to see the state of the fleet, nothing that sets all devices or one from one place, and no screen of a device from your desk. The [manager](manager.md) of OpenThinRDP does this from a shared folder. **Updates of a whole desktop.** The whole operating system updates, FreeRDP included, whenever the distribution says so. OpenThinRDP updates come as one signed package that you can send to a few pilot devices first. If something breaks you go back to the version before, for the whole fleet in one click. FreeRDP never changes behind your back (Debian security updates still install by themselves). **The details of a thin client**, which you would have to rebuild: - the RD Connection Broker with its collection, fallback brokers, and Kerberos behind a shared broker name; - reconnecting after a network drop, but not after a deliberate sign out; - *Waiting for Windows* while the virtual machine restarts; - which USB devices go to Windows whole and which as drives; - webcams and headsets through the RDP channels, so that Teams sees them; - smart card readers; - a power schedule and Wake-on-LAN. **When Mint is the better choice:** the user also needs local programs (a real desktop with a remote session now and then), or there are one or two PCs and nobody to manage them. ## IGEL OS A commercial thin client operating system with a subscription per device, managed by the IGEL UMS server. It runs on many x86-64 devices, with certified hardware and vendor support, and connects with many protocols: Citrix, Omnissa (VMware) Horizon, Microsoft AVD, Windows 365 and RDP. **Better for:** Citrix or Horizon, Azure Virtual Desktop and Windows 365, support contracts. **OpenThinRDP is for:** networks built on Windows Remote Desktop that do not want a cost per device or a management server. ## Dell ThinOS The operating system of Dell (Wyse) thin clients, managed with Wyse Management Suite, with Citrix, Omnissa Horizon and Microsoft clients. It runs only on Dell thin clients. OpenThinRDP runs on those same devices too: the production fleet it comes from is Dell OptiPlex 3000 Thin Clients that ran ThinOS before. It also runs on any other PC. **Better for:** a Dell-only fleet with Citrix or Horizon, under Dell support. ## Stratodesk NoTouch A commercial thin client operating system with a licence per device, managed by NoTouch Center. It runs on x86-64 PCs and on Raspberry Pi, with many protocols. **Better for:** many protocols, Raspberry Pi today (OpenThinRDP's Raspberry Pi support is [in preparation](https://github.com/OpenThinRdp/openthinrdp/issues/61)), vendor support. ## ThinStation An open source, long-standing thin client toolkit. You build your own image with the protocols you need, and the devices boot it over the network (PXE) or from a USB stick or disk; the settings are files on a network server. **Better for:** network boot (OpenThinRDP has no PXE yet), protocols other than RDP, and very old or very small devices. **OpenThinRDP gives you:** - a ready image, installed or live; - the RDP UDP transport; - signed updates with pilot devices and going back; - a Windows manager with the remote screen; - the device secrets in the TPM. ## openthinclient An open source thin client system (with a commercial offer) whose devices boot over the network from the openthinclient server, managed from that server, with many protocols. OpenThinRDP is a different, unrelated project: it is installed on each device or runs live, and needs no server. **Better for:** network boot from a central server, protocols other than RDP. ## What only OpenThinRDP does - **RDP over UDP with FreeRDP**, tested on lossy lines and in daily production ([udp.md](udp.md)). Over TCP a line losing 20% of its packets gave 0.6 frames per second; OpenThinRDP over UDP gave 19-21 at 50%. - **No server to manage the fleet:** a shared folder and a Windows manager. Settings for all devices or one, the remote screen over SSH, and the problems of every device read together. - **Signed updates with pilot devices and going back**, from GitHub or from your folder, re-signed with your own key if you want. - **Built from a fleet in production.** It runs every day on 14 thin clients at desks, tills, a laboratory and weighing stations ([status](status.md)), a fleet that left a commercial thin client OS for it. - **Free and open** (Apache 2.0), with no cost per device. --- # Hardware ## Drivers: all of Debian's OpenThinRDP is light because it runs no desktop, not because drivers were left out. The downloadable ISO carries the **complete Debian 13 kernel (Linux 6.12) with every driver module, and every firmware package Debian has** (`firmware-linux-nonfree`, `firmware-realtek`, `firmware-iwlwifi`, `firmware-atheros`, `firmware-brcm80211`, `firmware-mediatek`, `firmware-amd-graphics`, `firmware-intel-graphics`, `firmware-sof-signed` and the others: the `FIRMWARE=all` build of [building.md](building.md)). A network card, Wi-Fi adapter or graphics chip that works with Debian 13, or with the Ubuntu and Linux Mint releases of the same period, works the same here. Only an image you build yourself with `FIRMWARE=thinclient` keeps just the usual thin client firmware, to save about 600 MB. The quickest way to know: start the ISO **live** from a USB stick. Nothing is written to the disk, and the diagnostics (ℹ on the sign-in screen) show the network card, the address and whether the brokers answer. ## Requirements | | | |---|---| | CPU | 64-bit x86 (x86-64): Intel, AMD, VIA. 32-bit only CPUs cannot run Debian 13 | | Firmware | UEFI, with or without Secure Boot, or a legacy BIOS | | Memory | 2 GB | | Storage | 8 GB to install; none to run it live from a USB stick | ## Tested | Device | Firmware | Notes | |---|---|---| | Dell OptiPlex 3000 Thin Client (Intel Pentium Silver/Celeron, eMMC, Realtek NIC, Intel Wi-Fi) | UEFI, Secure Boot | the production fleet: 14 devices, boot to sign-in form in about 6 s after the firmware | | QEMU/KVM virtual machine (OVMF) | UEFI, Secure Boot | every release: live system, automatic and confirmed installation, first update | | QEMU/KVM virtual machine (SeaBIOS) | legacy BIOS | live system and installation | ## Should work: models that meet the requirements These are common used thin clients with a 64-bit CPU, 2 GB of RAM or more and 8 GB of storage or more. We have not tested them: if you try one, a line in the [discussions](https://github.com/OpenThinRdp/openthinrdp/discussions/3) (model, what works and what does not) moves it to the table above. | Family | Examples | |---|---| | Dell Wyse | 3040, 5060, 5070, 5470 | | HP | t520, t620, t630, t640, t540 | | Fujitsu Futro | S740, S940, S920 | | Lenovo, IGEL and others | ThinCentre M625q, IGEL UD3 (x86-64 models), any office mini PC | Some of them ship with less storage than 8 GB in their smallest version: run them live from a USB stick or put in a bigger module. ## Not supported - **ARM** (Raspberry Pi and ARM thin clients): in preparation, the Raspberry Pi 4 first. - **32-bit only** CPUs (older Atom and VIA models): Debian 13 no longer runs on them. ## Firmware settings (Dell) - **Wake-on-LAN**: enable "Wake on LAN" and **disable "Deep Sleep Control"**. With deep sleep in S4/S5 the network card is off and the magic packet never arrives. The same setting blocks the RTC alarm used by `POWER_ON_TIME`. - **USB boot** must be enabled to install from a stick (Integrated Devices). - On Linux the settings can be changed without entering the setup screen, through `dell-wmi-sysman` (`/sys/class/firmware-attributes/dell-wmi-sysman/`), with the BIOS admin password if one is set. ## Monitors - A monitor switched on after the thin client (or a slow HDMI/DisplayPort link) leaves X without an output: the kiosk checks every 2 s and enables it. - Monitors with a built-in USB hub may expose a "Hub Controller" device: it is kept local. - Touch monitors stay local and their touches go to Windows as real touch input (`+multitouch`). ## USB devices seen in the field - Webcams with microphone (UVC + audio): camera through the RDP camera channel, microphone through the RDP audio channel (tested with an eMeet Nova and an HD Web Camera 05a3:9331 against Windows 11 and Zoom). - Signature keys with smart card reader + storage: the reader goes through the smart card channel, the storage as a whole device. - Composite devices that restart in another mode once they see a PC (e.g. tachograph download keys): passed with `/usb:auto`, so they are passed again after re-enumeration. Devices plugged in during the session are passed only with a FreeRDP that has the `filter:` option of the USB channel (merged upstream after 3.32.1); with older versions plug them in before signing in, or list them in `USB_REDIRECT`. - USB network adapters are never passed (the device would lose its own network). --- # Status: what you can deploy today OpenThinRDP is numbered 0.x, but it is not a prototype: it runs a company's whole thin client fleet every day. This page says what is used in production, what is available but not exercised by that fleet, and what does not exist yet, so you know what to test before rolling it out. ## The production fleet - **14 Dell OptiPlex 3000 Thin Clients** (BIOS 1.19 to 1.37) in offices, at the tills, in a laboratory and at weighing stations: 13 wired, one on **Wi-Fi** (a wall screen that shows a web page all day). - **Windows 11 VDI on Hyper-V**: personal desktops behind two RD Connection Brokers (DNS round robin), plus a handful of session hosts. - Every device was **converted in place** from the system it ran before, by an update package, with its settings carried over; none was reinstalled by hand. - Updates reach them from the **management folder, signed with the key of the site**: the manager checks each GitHub release (project signature and hash) and signs it again with that key. Installed in the background during sessions and applied when the session is over. Until October 2026 the devices took the GitHub releases straight. ## In production every day | Area | What | |---|---| | Sign-in | Kiosk sign-in screen, default user, two brokers with fallback, load balance info for VDI collections; **Kerberos with NTLM as fallback** behind the brokers' round-robin name; a session opened from another device is told apart from a VM shutdown | | Transport | **RDP over UDP** (`TRANSPORT=auto`) with fallback to TCP; reconnection after network drops | | Network | Wired (DHCP) and **Wi-Fi** (WPA2-PSK) | | Web | **Web button** that opens the company portal (app mode, allowed sites only) on every device; a wall screen in **web kiosk** mode | | USB | Whole-device redirection of signature keys and other special devices, also plugged in during the session | | Meetings | Webcams through the RDP camera channel, microphones and speakers through the RDP audio channels | | Smart cards | Readers through the smart card channel (pcscd) | | Touch | Touch screens at the tills, as real touch in Windows | | Management | Status and events of every device, central settings (common and per device), logo and background, Windows manager | | Updates | Signed packages from the **management folder** with the **site key** (`UPDATE_FROM=share`, `UPDATE_KEY=site`), the GitHub releases checked and signed again by the manager; straight from GitHub (`UPDATE_FROM=github`) before. Debian security updates by themselves | | Power | Shutdown at the end of the session, Wake-on-LAN (also from the BIOS), **power on and off on a schedule** (the wall screen: RTC wake alarm) | ## Available, not exercised by that fleet These work in our tests (QEMU/KVM at every release, the system tests in `tests/`, a test device), but the production fleet does not use them, so test them in your environment first: - **ISO installation** on new hardware: live system and automatic installation, Secure Boot. Tested in QEMU/KVM with Secure Boot at every release; the fleet itself was converted in place, not installed from the ISO. - Static addressing (the fleet uses DHCP). - Web button in free browsing mode (`WEB_BUTTON=browser`). - `RDP_CERT=verify` with your own certificate authority. - Devices outside the office: the RD Gateway (`RDP_GATEWAY`), tested against a Windows Server RD Gateway, and the VPN (`VPN=wireguard|openvpn`), tested against WireGuard and OpenVPN servers (tunnel, routes, DNS, a refused password). No device of the fleet works outside the office yet. - **A pilot group and going back** (`UPDATE_TO`): a version on a few devices first, the others kept where they are, and back to an earlier version with its FreeRDP. In the system tests (management folder and GitHub, going back with the packages of that version), new in 0.2.0. - **The manager updating itself** from the signed releases, new in 0.2.0. - **802.1X and WPA2/WPA3-Enterprise Wi-Fi** (PEAP, EAP-TLS): in the system tests and in a lab against hostapd and FreeRADIUS, new in 0.2.1; not yet on a real switch or with Windows NPS. - **Desktops in the cloud** (Azure, AWS, any provider): the same RDP, through an RD Gateway or a VPN, but we have no cloud deployment to test on ([what to know](configuration.md#desktops-in-the-cloud)). ## Not available yet - Citrix, VMware/Omnissa Horizon or any protocol other than RDP. - Azure Virtual Desktop and Windows 365 (Microsoft Entra ID sign-in and Microsoft's gateway). - ARM devices. - Network boot (PXE). - Disk encryption, signed central configuration. Tell us what you need first in the [discussions](https://github.com/OpenThinRdp/openthinrdp/discussions). ## Tested hardware | Device | Status | |---|---| | Dell OptiPlex 3000 Thin Client | production (14 devices, one of them on its built-in Wi-Fi) | | QEMU/KVM, UEFI with Secure Boot | tested at every release | Other x86-64 PCs and thin clients with UEFI should work: reports in the [discussions](https://github.com/OpenThinRdp/openthinrdp/discussions) are very welcome. ## When something goes wrong - **A device cannot reach the management folder.** It keeps the last settings it read (the last good configuration) and keeps working. The ℹ button of the sign-in screen shows the network and the reachability of the folder and of the brokers; the settings dialog (PIN) fixes the folder, user or password on the device. - **An update is interrupted.** A package is checked (signature, hash, syntax of every script) before anything is touched; Shut down waits for a running install. If the power goes in the middle, the device installs the same package again at its next check, because its version is still the old one. - **Going back.** Devices never install an older version. To go back, publish the older package again under a higher version number, or reinstall the device from the ISO: its settings come back from the management folder. - **A device is stolen.** Rotate the management folder password (`MGMT_PASSWORD` in the central settings) and, if it had one, the password of the session user. See [security.md](security.md). --- # The OpenThinRDP manager The manager is the one place where you run a fleet of OpenThinRDP devices: one PowerShell script for Windows 10/11, in English and Italian, with nothing to install. It works on the shared folder the devices report to: no server, no agent, no database. What you see is the files in that folder, and everything you change is a file in that folder too, with a backup and a line in the change log.

Devices page: every device with its state, user, version and last report; the selected device with its settings

- [What you need](#what-you-need) - [Devices: the whole fleet at a glance](#devices-the-whole-fleet-at-a-glance) - [Settings for all devices or one](#settings-for-all-devices-or-one) - [Updates: a few devices first, then all, and back when something breaks](#updates-a-few-devices-first-then-all-and-back-when-something-breaks) - [Remote screen](#remote-screen) - [Problems across the fleet](#problems-across-the-fleet) - [Vault, keys and the site key](#vault-keys-and-the-site-key) - [Logo, background, SSH keys, permissions](#logo-background-ssh-keys-permissions) - [The manager updates itself](#the-manager-updates-itself) - [Starting it, and the command line](#starting-it-and-the-command-line) ## What you need - A **shared folder** (SMB) that the devices can read and write their status to: `\\fileserver\thinclients`. How to create it, its account and its permissions: [management.md](management.md#account). - A **Windows 10 or 11 PC** of an administrator, with PowerShell 5.1 (it is part of Windows). For the remote screen, also the OpenSSH client of Windows (`ssh.exe`, usually there) and a browser. - **The manager itself**: `openthinrdp-manager-.zip` from the [latest release](https://github.com/OpenThinRdp/openthinrdp/releases/latest), unpacked into a folder only the administrators can write (a share works, so that everybody starts the same copy). From then on it [updates itself](#the-manager-updates-itself). - The devices set to that folder: `MGMT_SHARE` in their settings dialog, or in the image you build. Start `OpenThinRDP-Manager.vbs` (no console window) or `OpenThinRDP-Manager.cmd -Folder \\fileserver\thinclients`. ## Devices: the whole fleet at a glance The **Devices** page lists every device that reports to the folder, with a coloured dot for its state, the user signed in, the version it runs and its last report; the search box finds a device by name, address or user. Devices report every 5 minutes and at boot, and the list refreshes itself every minute. A device that stopped reporting, rejected the central settings or has a failed service stands out in its colour. Select a device and its header shows its address, version, last report and user, with its actions: **Remote screen**, **Logo and background...**, **Software version...**. Under it: | Tab | What it shows | |---|---| | Settings | every setting: the value in use on the device, where it comes from, and the value you set for it | | Summary | a readable report: session, transport in use (UDP or TCP), network and Wi-Fi, software and FreeRDP, the version it installs, security updates, management, secrets, uptime | | Events | the event log of the device: sessions, drops with their cause, updates, settings applied or rejected | | Full report | the status file as the device wrote it |

A pilot device: the list marks it (pilot), its summary says it installs the newest version

## Settings for all devices or one **Common settings** are for every device (`config\common.conf`); in a device's **Settings** tab, tick *Exception* to give that device a value of its own (`config\.conf`), which wins over the common one. Every row shows: - the value **in use on the device** (from its last report); - **where it comes from**: the device itself, the common settings, or an exception for this device; - in orange, ***on its way***: a value you saved that the device has not applied yet. It reads the folder every 5 minutes; when it stays orange, the device has refused the settings (its state says so). The help of the selected setting is in full under the list. Before saving, the manager checks every value with the same rules as the devices (a device refuses a whole configuration with one wrong value), and before saving the common settings it lists the devices that would refuse them. Every save keeps a copy of the file in `config\backup\` and adds a line to `config\changes.log`.

Common settings: every setting with its value and the help of the selected one

All the settings: [configuration.md](configuration.md). ## Updates: a few devices first, then all, and back when something breaks The **Updates** page shows the newest version in the folder, the version all devices are on, the pilot devices, the devices kept on a version of their own, the versions you can choose, and what the devices run now.

Updates page: newest published version, all devices on 0.2.0, one pilot device on the newest, the versions that can be chosen

Everything here is one setting, `UPDATE_TO`, written for you in the right file: | `UPDATE_TO` | where | means | |---|---|---| | `latest` | `common.conf` | every device takes each new version as soon as it is in the folder (the default) | | a version, e.g. `0.2.0` | `common.conf` | every device stays on that version, or **goes back** to it | | `latest` | `.conf` | a **pilot device**: it takes the newest version even while the others stay | | a version | `.conf` | that device stays on, or goes back to, its own version | A device installs a version also when it is **older** than its own, FreeRDP included, and installs nothing newer until the setting changes. As always, an update never interrupts a session: it is installed in the background and applied when the session is over. ### Try a new version on a few devices 1. **Choose the pilots.** In the header of a device, **Software version...** > *pilot device*. Pick devices that look like the rest of the fleet (the same model, the same kind of user), and users who will tell you when something is odd. The list marks them *(pilot)*. 2. **Bring the new version in for the pilots only.** **Release from GitHub...** (top bar or Updates page) downloads the latest release, checks its project signature and hash, and asks: *all devices* or ***pilot devices only***. With pilot only, all the other devices are first kept on the version they have (`UPDATE_TO=` in `common.conf`), and only then the new version goes into the folder. 3. **Watch the pilots** for a few days: their **Summary** and **Events**, and the **Problems** page. ### Release it to the whole fleet When the pilots are fine: **Updates > Release to all devices**. `common.conf` goes back to `UPDATE_TO=latest` and every device takes the new version within 5 minutes, each one when its session is over. ### Something broke: go back - **The whole fleet:** **Updates > Version for all devices...**, choose the version that worked (newest first, from `updates\versions\`). Every device goes back to it, FreeRDP included, and stays there. - **One device:** **Software version...** in its header > *its own version*. - **A version that is no longer in the folder:** **Version from GitHub...** fetches it (the release `v`, with the same checks) into the folder, without changing what the others get. When the fix is out, **Release from GitHub...** again, pilots first, and **Release to all devices**. **Good to know** - Devices go back only to **0.2.0 or later**: older versions do not know `UPDATE_TO`. - Devices that still run a version **before 0.2.0** ignore `UPDATE_TO` and take the newest version in the folder. The manager names them before a pilot-only release: the first time, release 0.2.0 to all devices, or update those devices first. - With `UPDATE_FROM=github` (no management folder for updates) the same setting works: the devices fetch the release `v` from GitHub. - Every published version keeps its signed index in `updates\versions\`; the newest 3 packages stay in the folder, plus every version a device is kept on. Details: [management.md](management.md#pilot-devices-and-going-back). ## Remote screen **Remote screen**, in the header of a device, shows its screen in your browser and lets you use it: to see what a user sees, or to sign in once to a web page on a screen with no keyboard.

The screen of a device in the browser, with the notice the device shows

It goes **through SSH only**, with the key of the manager's vault and a one-time password: nothing new listens on the network, and nothing is installed on your PC. The device shows *Remote assistance active* while it lasts and records it in its events; it ends when you close the small window, or by itself after 2 hours. `REMOTE_SCREEN=off` turns it off. How it works: [management.md](management.md#remote-screen). ## Problems across the fleet The **Problems** page reads the event logs of every device together, for today, 24 hours, 7 or 30 days, and groups what went wrong: failed sign-ins (by broker), desktops that did not start, sessions dropped (by cause), sessions taken over, UDP blocked, failed updates, rejected settings. Each line says how many times, on which devices, for which users, first and last time. A line is orange when 3 or more devices had the same problem: that points at a server or the network, not at a device. "Six devices could not reach the broker this morning" is one line instead of six logs. ## Vault, keys and the site key Two keys rule the fleet: the SSH key that opens the screen of any device, and the key that signs the update packages every device installs. The manager keeps both in `vault.otr`, next to it, encrypted with an **access code** that only the administrators know. Every administrator who starts the same copy opens it with the same code; without the code the settings can still be edited, but no screen opens and nothing is signed.

Vault page: the access code, the SSH key of the manager, the key that signs the updates

**A fleet on its own key.** With `UPDATE_KEY=site` your devices accept only updates signed by the key of your site, which exists only in your vault. The manager checks each GitHub release (project signature and hash) and signs it again with your key: a stolen project key installs nothing on your fleet. Set it up step by step: [management.md](management.md#a-fleet-on-its-own-key). ## Logo, background, SSH keys, permissions - **Logo and background...** (common settings, or a device): any picture, scaled and saved as PNG, for every device or one. - **SSH keys**: the keys of the administrators, sent to every device (`SSH=on`); **Add the key of the manager** puts the vault's key there for the remote screen. - **Check permissions** tells you what the account of the devices can do in the folder: it needs to write only its status, never the settings or the updates. - The window fits any screen, from a 27" monitor to a laptop with display scaling (down to 860×560). ## The manager updates itself Every release carries the manager of the same version, under the same signature as the update package. At start, in the background, the manager checks the latest release; when there is a newer manager, the top bar shows ***Manager X available***. **Update the manager** downloads it, checks signature and hash, keeps the files it replaces in `previous\`, and starts again. Your `vault.otr`, the launcher and anything that is not part of the manager stay as they are; if anything fails, the previous files come back. Update the copy the administrators start (on a share, from a PC that can write there): everybody who starts it gets the new version. The **Updates** page shows the manager's version and has **Check again**. Details: [management.md](management.md#the-manager-updates-itself). ## Starting it, and the command line - `OpenThinRDP-Manager.vbs` starts it without a console window; `build-launcher.ps1` makes an `.exe` with the OpenThinRDP icon to pin to the taskbar, and with `-Install` a shortcut on each PC that starts the copy on the share ([details](management.md#central-settings)). - `OpenThinRDP-Manager.cmd -Folder \\fileserver\thinclients -Language en|it`: the folder and the language (by default the Windows display language). - `OpenThinRDP-Manager.cmd -Sign \LATEST.txt`: signs an update package you built with `make-update.sh`, with the key in the vault ([Signed updates](management.md#signed-updates)). - `OpenThinRDP-Manager.cmd -UpdateManager`: updates the manager without a window (exit code 0 = updated, 1 = error, 2 = nothing to do). What is in the folder, file by file, and how the devices use it: [management.md](management.md). --- # Configuration Every setting is a `KEY=value` line. A device reads them in this order, the first file that has the key wins: 1. `/etc/openthinrdp/openthinrdp.conf`: this device (settings dialog ⚙, central settings) 2. `/etc/openthinrdp/site.conf`: your organization, built into the image from `image/site/site.conf` 3. `/opt/openthinrdp/defaults.conf`: project defaults An empty value in a higher file clears a lower one on purpose (e.g. `TITLE=` in the device file). The device file is written by the settings dialog **and** by the central settings, and the central settings win: in practice the order is `config\.conf` > `config\common.conf` > settings dialog > `site.conf` > defaults. A key removed from the central files goes back to `site.conf`/defaults, so all devices stay alike (see [management.md](management.md)). Values are validated by `save-config.sh` with the same rules wherever they come from (dialog, central settings, manager); invalid central settings are refused as a whole and the previous ones stay. The reference with comments is [`rootfs/opt/openthinrdp/defaults.conf`](../rootfs/opt/openthinrdp/defaults.conf). ## Remote desktop | Key | Default | Meaning | |---|---|---| | `BROKER` | | RD Connection Broker, or directly a session host or any Windows PC with Remote Desktop on: name or IP. Empty = the settings dialog opens at start | | `BROKER_FALLBACK` | | other brokers, space separated, tried in turn from the 3rd attempt | | `DOMAIN` | | Windows domain of the users | | `LOAD_BALANCE_INFO` | | RDS collection, as `loadbalanceinfo` in the .rdp file, e.g. `tsv://VMResource.1.VDI` | | `DEFAULT_USER` | | user name pre-filled in the sign-in form | | `TRANSPORT` | `auto` | `auto` = UDP, TCP when UDP is blocked or fails; `udp` = UDP only; `tcp` = TCP only. See [udp.md](udp.md) | | `RDP_AUTH` | `negotiate` | `negotiate` = Kerberos, NTLM when Kerberos is not available (`DOMAIN` must be the full DNS name, e.g. `example.local`); `kerberos` = Kerberos only: the sign-in fails instead of falling back to NTLM (for domains that turned NTLM off, and to see a broken Kerberos setup at once); `ntlm` = NTLM only. Behind an alias shared by several brokers the device connects to one of them by its own name, so that the Kerberos ticket is for the server it talks to | | `RDP_CERT` | `tofu` | `ignore`, `tofu` (trust on first use) or `verify` | | `RDP_GATEWAY` | | RD Gateway (Remote Desktop Gateway) for devices outside the office: `name[:port]`, 443 when no port. See [RD Gateway](#rd-gateway) | | `RDP_GATEWAY_USE` | `always` | `always` = every connection through the gateway; `detect` = a server on the local network straight, the others through the gateway | | `RDP_GFX` | `progressive,AVC420:off,AVC444:off` | `/gfx` options. Without a GPU on the server this keeps text pixel exact | | `RDP_KEYBOARD` | | keyboard layout id for Windows, e.g. `0x00000409`. Empty = taken from the X keyboard | | `MULTIMON` | `auto` | `auto` = every monitor when more than one is connected (`/multimon`), `off` = only the main one | | `RDP_EXTRA` | | extra xfreerdp3 options (letters, digits, `/:+,._=#-` and spaces only) | | `SESSION_THRESHOLD` | `10` | a session that ends within these seconds counts as a failed attempt | | `SHUTDOWN_SECS` | `10` | after the user disconnects, switch off after these seconds (0 = never) | The device switches off only after a deliberate end (disconnect, sign out, VM shut down by an administrator). Every other ending (network drop, crash, server restart) reconnects without asking. A session taken over by another device shows "Session opened elsewhere" with *Resume here* instead of reconnecting. When the broker answers but the virtual machine refuses the desktop (it is restarting or installing a Windows update), the device shows *Waiting for Windows* with a Cancel button and tries again every 20 s, without counting attempts, for up to 20 minutes. ### RD Gateway A device in a branch office or at home reaches the RDS deployment through its **RD Gateway**, the role that carries RDP over HTTPS (port 443), so that nothing but 443 is published on the Internet. Set `RDP_GATEWAY` to its public name, e.g. in `config\.conf` of the devices that are outside, or in `common.conf` with `RDP_GATEWAY_USE=detect` for devices that move: ``` RDP_GATEWAY=rdgateway.example.com ``` - The broker and, after its redirect, the virtual machine are reached through the gateway with the **credentials of the session**: the user signs in once. Who may connect, and to which servers, is decided by the policies of the gateway (RD CAP and RD RAP). - **TCP through the gateway.** FreeRDP carries no UDP through a gateway (its UDP transport, port 3391, is not implemented). With `detect`, a server reached straight on the local network keeps UDP (since 0.1.15; before, UDP was declined as soon as a gateway was set). `TRANSPORT=udp` is not enforced with a gateway. `/network:auto` stays: the server adapts frame rate and image quality to the line. - **Certificate.** The gateway presents its HTTPS certificate: with `RDP_CERT=verify` it must be trusted by the device (a public certificate, or your CA in the `ca/` folder of the site files). - **Kerberos** needs the domain controllers: outside the office, with no KDC proxy, use `RDP_AUTH=negotiate` (NTLM when Kerberos cannot be reached) or `ntlm`. - The diagnostics of the device (settings, *Diagnostics*) test the gateway on its port instead of the brokers on 3389. A session that the server closes with code 1 counts as ended: through a gateway the device cannot ask the virtual machine whether it still runs. ## USB | Key | Default | Meaning | |---|---|---| | `USB_MODE` | `all` | `all` = every device except keyboard, mouse, touch screen, hub, Bluetooth adapter, network adapter, smart card reader, webcam and USB headset goes to Windows as a whole device (RemoteFX USB redirection). `disks` = USB sticks stay on the thin client, are mounted there and appear in Windows as redirected drives (`\\tsclient`), also when plugged in during a session (since 0.1.7). `auto` = device by device: a device that is only storage (every interface mass storage: a stick, a card reader, a disk) as a drive, as with `disks`; a device with storage and something else (an instrument with a serial port, a key with its own Windows driver) whole, as with `all`, so that its driver gets all of it. Plugged in during a session, `auto` works as `disks`: FreeRDP's filter sees a class on any interface | | `USB_REDIRECT` | | `VID:PID` list always passed whole, in every mode (smart card readers excepted: they always go through the smart card channel) | A USB stick passed whole (`USB_MODE=all`) gets its drive letter from Windows inside the session of the user it is redirected to, as on a physical PC. That letter is not visible to services or to other sessions, so a tool running as SYSTEM sees no letter: this is how Windows works, not something to fix by assigning letters. Webcams and USB sound devices (headsets, speakerphones) stay on the thin client: the camera goes through the RDP camera channel and appears in Windows as "*name* (redirected)", the microphone and the speaker through the RDP audio channels ("Remote Audio"). Passed as whole USB devices they reach Windows as devices of the console session, and the apps of the remote session see no microphone at all. When there is no USB microphone the thin client's own input is used. A device listed in `USB_REDIRECT` is still passed whole. ## Network | Key | Default | Meaning | |---|---|---| | `NETMODE` | `dhcp` | `dhcp` or `static` | | `IPADDR`, `NETMASK`, `GATEWAY` | | static address (static mode only) | | `DNS1`, `DNS2` | | DNS servers; empty = from DHCP. Required with a static address | | `NTP` | | time servers; empty = Debian pool | | `HOSTNAME` | `thinclient-` + the last 6 digits of the MAC address | name of the device (the system host name, not stored in the settings file) | | `WIFI` | `off` | `on` = use Wi-Fi when no cable is connected | | `WIFI_SSID`, `WIFI_COUNTRY` | | network name, regulatory country (two letters) | | `WIFI_PSK` | | password or 64-hex key. Only in central settings or the dialog; stored only in `/etc/wpa_supplicant` | | `WIFI_SECURITY` | `psk` | `psk` = a password (WPA2-Personal, `WIFI_PSK`); `peap` or `tls` = WPA2/WPA3-Enterprise with the 802.1X settings below. See [802.1X and Wi-Fi Enterprise](#8021x-and-wi-fi-enterprise) | | `DOT1X` | `off` | 802.1X on the cable: `peap` (an account and its password) or `tls` (a certificate of the device). See [802.1X and Wi-Fi Enterprise](#8021x-and-wi-fi-enterprise) | | `EAP_IDENTITY` | | the identity: `DOMAIN\user`, `user@domain`, `host/name.domain`. With `tls`, empty = the common name of the certificate | | `EAP_ANONYMOUS` | | outer identity, sent in clear before the tunnel, e.g. `anonymous@example.com`. Empty = the identity | | `EAP_CA` | `system` | the authority that signed the certificate of the RADIUS server: `system` (the system trust store, with the CAs of the site) or the name of a `.crt` file in `/etc/openthinrdp/ca` | | `EAP_SERVER_NAME` | | name(s) the certificate of the RADIUS server must be for (or a name under them), space separated. Always set it | | `EAP_PASSWORD` | | the PEAP password. Only in the dialog, the central settings or sent by the manager; stored only as a secret of the device | | `VPN` | `off` | `wireguard` or `openvpn`: the device's own VPN, for devices outside the office. See [VPN](#vpn) | | `VPN_SERVER`, `VPN_SERVER_KEY` | | WireGuard: the server, `name[:port]` (51820 when no port), and its public key | | `VPN_ADDRESS` | | WireGuard: the address of this device in the tunnel with its prefix, e.g. `10.8.0.15/24`. One per device: ignored in `common.conf` | | `VPN_ROUTES` | | networks of the office through the tunnel (CIDR, space separated); empty = all the traffic (WireGuard). With OpenVPN, added to the routes the server pushes | | `VPN_DNS` | | DNS servers of the office, in front of the others while the tunnel is up. Empty: WireGuard keeps the DNS it has, OpenVPN takes the ones the server pushes | ### VPN The other way for a device outside the office: its own tunnel to the office network, WireGuard or OpenVPN. Through it go the broker, the management folder (status, central configuration, updates) and the DNS of the office, so the device works as it does inside. Compared with the [RD Gateway](#rd-gateway): no gateway server to publish, and inside WireGuard the session **keeps UDP**; a VPN server is needed instead, often the firewall of the office. **WireGuard**, set in the manager or in the configuration files: ``` # config\common.conf VPN=wireguard VPN_SERVER=vpn.example.com VPN_SERVER_KEY= VPN_ROUTES=192.168.0.0/23 VPN_DNS=192.168.1.10 # config\.conf VPN_ADDRESS=10.8.0.15/24 ``` - The **private key is made on the device** at the first start with `VPN=wireguard` and never leaves it (a secret, in the TPM when there is one). Its public key is in the status report (`VPN_PUBLIC_KEY`); in the manager, the Summary of the device shows it, and a double click or Ctrl+C on that row copies the `[Peer]` block to add on the server (the public key and `AllowedIPs` = the address of the device /32). Until the server knows it, the state says *no answer from the server yet*. - `VPN_ROUTES` empty sends **all the traffic** through the tunnel: the server itself stays on the local way, the default route of the local network stays and is used again as soon as the tunnel goes. - A server name is resolved again when the tunnel stays silent for 3 minutes (a dynamic address). The tunnel sends a keepalive every 25 s, so that a NAT router at home keeps it open. **OpenVPN**: `VPN=openvpn`, and the profile of the device (the `.ovpn` file the VPN provider or the firewall gives) sent from the manager with **VPN profile...** (device header, shown when `VPN=openvpn`), with the user and password when the profile asks for them (`auth-user-pass`). - Profile and password go **over SSH** with the key of the vault (`SSH=on` on the device) and are secrets of the device: they never go through the management folder, which every device can read, and never on a command line. The profile is refused when it would run commands or write files (`script-security`, `up`/`down`, `plugin`, `management`, `log`...): the scripts that run are the device's own. - The tunnel restarts by itself when it drops. A **password the server refuses** is tried again only after an hour (or as soon as the manager sends new credentials): retried every few seconds, it would lock a domain account. - DNS servers pushed by the server (`dhcp-option DNS`) are used unless `VPN_DNS` names others. The state of the VPN is in the report (`VPN=`), in the manager (Summary, Network) and in the diagnostics of the device. Events: *VPN: ...* in the event log. ### 802.1X and Wi-Fi Enterprise Corporate networks often admit nothing on a switch port that has not authenticated with **802.1X**, and enterprise Wi-Fi uses **WPA2/WPA3-Enterprise**, the same protocol over the air. The device does both with `wpa_supplicant`: - `DOT1X=peap` or `tls`: 802.1X on every wired adapter (`dot1x.sh`, one `openthinrdp-dot1x@` service each, `wpa_supplicant -D wired`). - `WIFI_SECURITY=peap` or `tls`: the Wi-Fi network of `WIFI_SSID` as WPA2- or WPA3-Enterprise. - **One block of EAP settings serves both**: the same account (PEAP) or the same certificate (EAP-TLS) on the cable and on the Wi-Fi, as a Windows PC uses the same credentials for both. A network that wants a different identity on each is rare enough not to double every setting. Two methods, the ones Windows NPS and FreeRADIUS offer for domain devices: | | PEAP (`peap`) | EAP-TLS (`tls`) | |---|---|---| | Who authenticates | an account and its password (PEAP-MSCHAPv2) | a certificate of the device and its private key | | Where the secret comes from | the settings dialog (*802.1X password*), `EAP_PASSWORD` in the central settings, or the manager (**802.1X...**, over SSH) | the manager (**802.1X...**, over SSH): a `.pfx` as Windows exports it, or a PEM certificate and its key | | Identity | `EAP_IDENTITY`, e.g. `EXAMPLE\svc-thin-8021x` | `EAP_IDENTITY`, or empty = the common name of the certificate | ``` # config\common.conf: every device on 802.1X ports with one service account (PEAP) DOT1X=peap EAP_IDENTITY=EXAMPLE\svc-thin-8021x EAP_ANONYMOUS=anonymous@example.com EAP_CA=corp-root.crt EAP_SERVER_NAME=nps.example.com ``` **The server is always checked.** The device sends nothing secret before the RADIUS server has shown a certificate signed by `EAP_CA` and made out to `EAP_SERVER_NAME` (or a name under it, `domain_suffix_match`); a server that fails either check is refused (*certificate of the server not accepted: ...*) and the password never leaves the device. `EAP_CA=system` trusts the system store, which holds the public authorities **and** the authorities of your site (`ca/*.crt` of the site files, `/etc/openthinrdp/ca`); a file name (e.g. `corp-root.crt`, from that folder) trusts that authority only. **Set `EAP_SERVER_NAME` always**: without it any server with a certificate of `EAP_CA`, with `system` any server with a public certificate, would get the password; the status of the device says so with a WARNING, and the manager notes it when the settings are saved. **On the cable**, in the order things happen: 1. The adapter comes up (boot, cable plugged in, settings changed): its supplicant starts **before** the address is asked (a `pre-up` of the adapter), and DHCP (or the static address) waits for the authentication, 40 s at most. 2. **Authenticated**: the port is open, DHCP goes on. When the port was authenticated after the address was already asked (a supplicant restarted, a switch that answered late), the address is asked again (`dhcpcd -n`): a switch may put an authenticated device in another VLAN than before. 3. **Refused**: DHCP goes on too, for the network the switch gives a refused device (a guest or restricted VLAN, or none). The reason is in the state: *password refused by the server*, *certificate of the server not accepted: Domain suffix mismatch (...)*, *refused by the server (certificate, account or network policy)*. A **PEAP password the server refuses is not tried again for an hour**, or until a new one arrives (dialog, central settings, manager): switches ask again every minute, and a domain account would be locked. 4. **No authenticator**: a port that does not answer 802.1X at all (a switch without it, a desk at home) gets the network without it after 15 s, and the state says *no authenticator: DHCP without 802.1X*. A device set up for 802.1X works on any switch; the supplicant keeps listening, and a switch that starts 802.1X later is answered. 5. **Cable out and in**: authenticated again (an EAPOL-Start of its own after 2 and 8 s, when the switch does not ask first), then the address again. Missing pieces are no error of the settings (the password or the certificate come later, from the manager): the state says *incomplete: no password* or *no client certificate*, and the network works without 802.1X meanwhile. **Secrets.** The PEAP password and the private key of the certificate are secrets of the device, kept like the Wi-Fi key: in the TPM when there is one (`TPM_SECRETS`), otherwise in root-only files; never in the settings files, never in the status. The manager sends them with **802.1X...** in the header of a device (shown with `DOT1X` or an enterprise Wi-Fi), over SSH with the key of the vault (`SSH=on`), on standard input: never through the management folder, which every device can read. A certificate is checked twice, in the manager (the password of a `.pfx`, its private key, the dates, the use for client authentication) and on the device with openssl (the key belongs to the certificate, not expired); the key is kept **decrypted**, its password nowhere. `EAP_PASSWORD` in `common.conf` works too (compared by its hash, applied once), but every device that reads the file can read it: per device, prefer the manager. **Getting there the first time.** A device on a port that admits nothing without 802.1X has no network until it is set up: type the PEAP settings in its settings dialog (section *802.1X*), or set it up first on a port without 802.1X, or one with MAC authentication bypass or a guest VLAN that reaches the management folder. EAP-TLS needs the network once anyway, for the manager to send the certificate. **State and troubleshooting.** The report has `DOT1X=` (adapter by adapter: *authenticated (PEAP, server ...)*, *failed: ...*, *no authenticator*, *incomplete: ...*, *no link*, or `off`), shown in the manager (Summary, Network) and in the diagnostics of the device; `WIFI_STATUS` says *(802.1X PEAP)* or what is missing. Events: *802.1X: authenticated on eth0 (...)*, *802.1X: failed on eth0: ...*, *802.1X: no authenticator on eth0 ...*. Everything the supplicant says: `journalctl -u openthinrdp-dot1x@eth0` (SSH). **Wi-Fi Enterprise** joins WPA2-Enterprise and WPA3-Enterprise networks (`WPA-EAP` and `WPA-EAP-SHA256`, protected management frames when the access point offers or requires them), with the same EAP settings and the same checks of the server. Not supported: WPA3-Enterprise 192-bit (Suite B), and WPA3-Personal (SAE) for `psk` networks. On Wi-Fi the supplicant's own back-off applies to a refused password (10 s, then longer), not the hour of the cable. #### Windows NPS: PEAP with domain accounts 1. Install the *Network Policy and Access Services* role and register the NPS server in Active Directory. 2. **Server certificate.** NPS needs a certificate for its name, e.g. `nps.example.com`, from your AD CS (the *RAS and IAS Server* template, autoenrollment for the NPS server), or any CA. Export the root CA as Base-64 (`.cer`), put it in the site files as `ca/corp-root.crt` (image or update package), and set `EAP_CA=corp-root.crt` (or `system`) and `EAP_SERVER_NAME=nps.example.com`. 3. **RADIUS clients**: the switches (and Wi-Fi controllers) with their address and shared secret. 4. **Network policy**: conditions *Windows Groups* = a group of the accounts the devices use, *NAS Port Type* = Ethernet (and Wireless - IEEE 802.11); constraints, *Authentication Methods*: **Microsoft: Protected EAP (PEAP)**, edited to use the server certificate above, with *Secured password (EAP-MSCHAP v2)* inside. Leave the other methods off. A refused device: the reason is in the NPS log (Event Viewer, *Network Policy Server*). 5. The device: `DOT1X=peap`, `EAP_IDENTITY=EXAMPLE\account` (a user account: a thin client has no domain computer password to use), its password with the manager or the dialog. #### Windows NPS: EAP-TLS with certificates from AD CS 1. A **certificate template** for the devices: duplicate *Workstation Authentication* (or *User*), Enhanced Key Usage *Client Authentication*, *Allow private key to be exported*, subject from the request or built from Active Directory. Issue one certificate per device, e.g. for a computer account created for it, subject `CN=host/thin1.example.com` (and that name as DNS name in the subject alternative name), and export it with its private key as `.pfx`. 2. NPS maps the certificate to an account in Active Directory. With the *strong certificate mapping* that Windows enforces (KB5014754), give the certificate the security identifier of the account (issued by an enterprise CA for that account) or map it explicitly on the account (`altSecurityIdentities`), otherwise the mapping, and the authentication, may fail. 3. **Network policy** as for PEAP, with the authentication method **Microsoft: Smart Card or other certificate** (its server certificate as above). 4. The device: `DOT1X=tls`, `EAP_IDENTITY` empty (the common name of the certificate: `host/thin1.example.com`), and the `.pfx` with **802.1X...** in the manager. #### FreeRADIUS ``` # clients.conf: each switch or Wi-Fi controller client switches { ipaddr = 192.0.2.0/24 secret = } # mods-available/eap, tls-config tls-common: the server certificate, and the CA of the device certificates (EAP-TLS) private_key_file = ${certdir}/nps.example.com.key certificate_file = ${certdir}/nps.example.com.crt ca_file = ${cadir}/corp-root.crt # mods-config/files/authorize: a local account for PEAP (or the mschap module with ntlm_auth for domain accounts) svc-thin-8021x Cleartext-Password := "..." ``` PEAP-MSCHAPv2 and EAP-TLS are enabled in the default `eap` module; its first proposal (MD5) is declined by the device, which asks for PEAP or TLS as set. The switch is a plain 802.1X authenticator with the FreeRADIUS server as its RADIUS server. Tested against hostapd (802.1X authenticator on a wired link, and a WPA2/WPA3-Enterprise access point on virtual radios) with its own EAP server and with FreeRADIUS 3.2: PEAP and EAP-TLS, a wrong password (one try, then the hour), a server of another CA or another name, no authenticator, the cable out and in. ### Desktops in the cloud OpenThinRDP connects with RDP to any Windows it can reach, wherever it runs. An RDS deployment (broker, session hosts or a VDI collection) or single Windows virtual machines in **Azure, AWS, Google Cloud** or with any other provider are set up as on premises: `BROKER` is the broker or the machine, `LOAD_BALANCE_INFO` the collection. - **How the devices reach it:** through the deployment's [RD Gateway](#rd-gateway) (HTTPS, the usual way to publish RDS in the cloud), through a [VPN](#vpn) to the cloud network (the device's own WireGuard or OpenVPN, or the site-to-site VPN of the office), or straight to a public address (not recommended: Remote Desktop open to the Internet). - **UDP** works straight and through a VPN, as on premises: the firewall of the cloud network (security group, NSG) must let UDP 3389 in, besides TCP 3389. Through an RD Gateway the session uses TCP. - **Sign-in** with the domain accounts of the deployment (Active Directory in the cloud, or a managed domain such as Microsoft Entra Domain Services), Kerberos or NTLM as on premises. Kerberos needs the domain controllers, so from outside with no VPN use `RDP_AUTH=negotiate` or `ntlm`. - **Management folder:** an SMB share the devices can reach, usually on a file server through the VPN. Devices without a management folder work too, set up on the device (settings dialog) and updated from GitHub. - **Not supported yet: Azure Virtual Desktop and Windows 365.** Their clients sign in with Microsoft Entra ID and connect through Microsoft's own gateway and broker, which the kiosk does not do (FreeRDP has experimental support, OpenThinRDP does not use it). Services with their own protocol (Citrix, Omnissa Horizon, Amazon WorkSpaces) are not RDP. OpenThinRDP runs in production on premises; it is the same RDP in the cloud, but we have no cloud deployment of our own to test on. Reports are welcome in the [Discussions](https://github.com/OpenThinRdp/openthinrdp/discussions). ## Management | Key | Default | Meaning | |---|---|---| | `MGMT_SHARE` | | management folder, e.g. `\\fileserver\thinclients`, see [management.md](management.md) | | `MGMT_USER` | | account of the management folder: `user`, `DOMAIN\user` or `user@domain`. Empty = `smb.cred` of the image | | `MGMT_PASSWORD` | | its password. Only in central settings or the dialog; stored only in `/etc/openthinrdp/smb.cred` | | `SECURITY_UPDATES` | `on` | Debian security updates, never FreeRDP, never a reboot | | `TPM_SECRETS` | `on` | with a TPM 2.0: folder password, Wi-Fi key, SSH host keys, VPN and 802.1X secrets kept encrypted in it ([security.md](security.md)); `off` = root-only files, and back out of the TPM at the next start | | `UPDATE_FROM` | `github` | OpenThinRDP software updates: `github` (signed releases, checked every 3 hours), `share` (`updates\` of the management folder) or `off`, see [management.md](management.md#signed-updates) | | `UPDATE_GITHUB` | `OpenThinRdp/openthinrdp` | repository of the releases, `owner/repository` (for a fork) | | `UPDATE_KEY` | `any` | signature an update from the management folder needs: `any` (project key or site key) or `site` (only the site key, once the device has it), see [management.md](management.md#a-fleet-on-its-own-key) | | `UPDATE_TO` | `latest` | `latest` = the newest version published; a version (e.g. `0.2.0`) = exactly that one, also an older one (going back, FreeRDP included), and nothing newer until it changes. A pilot group: the current version in `common.conf`, `latest` for the pilot devices. Going back works to 0.2.0 or later. See [management.md](management.md#signed-updates) | | `SSH` | `off` | `on` = SSH for the `otradmin` account, key login only | | `REMOTE_SCREEN` | `on` | `off` = no remote screen from the manager ([management.md](management.md#remote-screen)); it needs `SSH=on` | ## Web button and kiosk page | Key | Default | Meaning | |---|---|---| | `WEB_BUTTON` | `none` | `app` (only `WEB_URL` and `WEB_ALLOW`), `browser` (free browsing), `kiosk` (page full screen at power on) | | `WEB_LABEL` | `Web` | button label | | `WEB_URL` | | web address for `app` and `browser` | | `WEB_ALLOW` | | other sites allowed in `app` mode (URL prefixes) | | `KIOSK_URL` | | the kiosk page. The sign-in form shows for 5 s first: touching anything keeps it | | `KIOSK_RELOAD_MIN` | `10` | reload the page every N minutes when idle (0 = never) | ## Power schedule | Key | Default | Meaning | |---|---|---| | `POWER_ON_TIME` | | `hh:mm`, RTC wake alarm (firmware support needed, see [hardware.md](hardware.md)) | | `POWER_OFF_TIME` | | `hh:mm`, with a 60 s warning that can be cancelled; waits for an open session to end | | `POWER_ON_DAYS` | `1234567` | 1 = Monday ... 7 = Sunday | ## Look and language | Key | Default | Meaning | |---|---|---| | `LANGUAGE` | `en` | `en`, `it` (a file in `/opt/openthinrdp/lang/`) | | `KEYBOARD` | `us` | X11 layout | | `TITLE` | `Remote desktop` | window titles | | `LOGO` | `/opt/openthinrdp/branding/logo.png` | logo in the dialogs. A logo in the management folder wins, see [management.md](management.md#logo-and-background-picture) | | `BG_TOP`, `BG_BOTTOM` | `#1b2733`, `#000000` | background gradient (boot splash and behind the form) | | `SUPPORT_TEXT` | | extra line in error messages, e.g. "Call the IT desk, ext. 123" | | `SETTINGS_PIN_SHA256` | | protects the settings dialog. Set it from the manager, which stores `pbkdf2-sha256:::` (salted PBKDF2-SHA256); by hand: `python3 -c 'import hashlib,os,sys; s=os.urandom(16); print("pbkdf2-sha256:600000:"+s.hex()+":"+hashlib.pbkdf2_hmac("sha256",sys.argv[1].encode(),s,600000).hex())' 123456`. The plain SHA-256 of 0.1.4 and earlier still works. Never shown in the status files | `HOSTNAME`, `DEFAULT_USER`, `NETMODE` and `IPADDR` identify a device: in central settings they are accepted only in `config\.conf`, never in `config\common.conf`. --- # Management folder One SMB share (a folder on any Windows or Samba file server) is the whole management system. Every device mounts it every 5 minutes, at boot and at every new event, writes its status and reads settings and updates. ``` \\fileserver\thinclients\ status\.txt status: KEY=value lines + last errors and events status\-events.log event log status\SUMMARY.csv one line per device (rewritten by the devices) config\common.conf settings for every device config\.conf settings for one device (wins over common.conf) config\authorized_keys SSH public keys of the administrators (optional) config\branding\logo.png, splash.png logo and background picture for every device (optional) config\branding\\logo.png, splash.png for one device (wins over the common ones) config\backup\, changes.log written by the Windows manager updates\LATEST.txt(.sig) signed update index of the newest version, and the packages updates\versions\.txt(.sig) the index of every version that can be chosen (UPDATE_TO) ``` ## Account The devices log in with `/etc/openthinrdp/smb.cred` (root only). Two ways to get it there: - **On the device**, also with the downloadable ISO: in the settings dialog, *Management folder user* (`user`, `DOMAIN\user` or `user@domain`; without a domain, `DOMAIN` is used) and *Management folder password*. The user is a normal setting (`MGMT_USER`); the password goes only into `smb.cred`, is never shown again, never in the status files, and an empty field keeps the saved one. Central settings can change both (`MGMT_USER=`, `MGMT_PASSWORD=` in `config\common.conf` or `config\.conf`), e.g. to rotate it. - **In your own image**: `image/site/smb.cred`: ``` username=svc-thinclients password=... domain=EXAMPLE ``` Every device can read this password, so use an account that can do as little as possible: not a member of Domain Users, no rights outside this folder, and inside it **write access to `status\` only**. A device only writes its status and events there; settings, branding and updates it only reads. So a stolen device gives its holder an account that can fill `status\` with nonsense, but cannot change the settings of the other devices or put anything in `updates\`. | folder | devices' account | administrators | |---|---|---| | the management folder itself | Read (to list and open the subfolders) | Full control | | `status\` | Modify | Full control | | `config\` (with `branding\`) | Read | Full control | | `updates\` | Read | Full control | With PowerShell on the file server (example account `EXAMPLE\svc-thinclients`, folder `D:\thinclients`): ```powershell icacls D:\thinclients /grant "EXAMPLE\svc-thinclients:(OI)(CI)RX" icacls D:\thinclients\status /grant "EXAMPLE\svc-thinclients:(OI)(CI)M" ``` The icacls lines only add rights: whatever the folder inherits stays, and a group such as Users or Domain Users with write access inherited from above still lets the devices, or every user, change the settings. **Check permissions** in the Windows manager reads the permissions of the folder for the account the devices report (`MGMT_ACCOUNT` in their status, from 0.1.6; older devices: it asks for the account) and lists: - whether the devices can write in `status\`; - every place outside `status\` they can change (the folder itself, `config\` and its files, logos, `updates\`), and through which account or group; - every place outside `status\` that Everyone, Authenticated Users, Users or Domain Users can change. It follows the order of the rules as Windows does (the first rule that allows or denies a right decides it), and the groups of the account in the domain, nested ones included. It reads the NTFS permissions only: the permissions of the share can only take rights away. Check also from a device that the account cannot list anything outside the folder. ## Central settings `config\common.conf` and `config\.conf` hold the same keys as [configuration.md](configuration.md). Files saved on Windows are fine (BOM, CRLF, spaces around `=`, `#` and `;` comments). A device: - merges common, then its own file, over its current settings; - validates the result with the same rules as the settings dialog, and applies it only if something changed; - refuses the whole result if anything is invalid (event "central configuration REJECTED", reported once), keeping the previous settings; - keeps the last good copy when the folder is unreachable; - does not apply them while a software update is installing (since 0.1.7): the next run does. Central values win: a key in the central files is written into the device settings at every run (every 5 minutes and at boot), so a change made in the settings dialog of a device lasts only until then. Keys that the central files do not set stay as set on the device. Removing a key from the central files gives it back to the device defaults: the device drops it from its settings and follows `site.conf` or the project defaults again, like a device installed today. If the result would not be valid (e.g. removing `WEB_URL` while `WEB_BUTTON=app` stays), the key is kept, the event is logged once and the release is retried at every run. The Windows manager (`management/windows/OpenThinRDP-Manager.vbs`, or the `.cmd` to pass `-Folder`/`-Language`) shows the devices, their status and events, and edits these files with the same validation, a backup of every file it overwrites and a change log. Before saving `common.conf` it lists the devices that would reject it. The window has six pages on the left: **Devices** (the list, and the selected device with its settings, a summary, its events and its full report), **Common settings**, **Updates** (see [Pilot devices and going back](#pilot-devices-and-going-back) and [The manager updates itself](#the-manager-updates-itself)), **SSH keys**, **Vault** and **Problems**; the actions on the folder (refresh, open, release from GitHub, check permissions) and the state of the vault are in the bar at the top. In a settings list each row shows the value in use on the device, where it comes from (*the device itself*, *common settings*, *this device* for an exception) and *on its way* when a saved value has not reached the device yet (it reads the folder every 5 minutes; if it stays, the device has probably rejected the configuration: see its state). The help of the selected setting is under the list; the search box finds a setting by name, key or help text. It is available in English and Italian: it follows the Windows display language (English for any language without a file in `management/windows/lang/`), or `-Language en` / `-Language it` chooses one. The window opens at 85% of the screen (at least 1440×880, maximized on a smaller screen) and works down to 860×560: the columns of every list and grid share the width instead of hiding behind a scroll bar, the rows of buttons wrap, and the buttons of the top bar and of the device header keep only their icon (their text in the tooltip) when the room is short. **A program to pin.** `build-launcher.ps1` builds `OpenThinRDP-Manager.exe` next to the script, with the OpenThinRDP icon, from `Launcher.cs` and the C# compiler that comes with Windows: it starts the manager with no console window. Its arguments can live next to it, so a shortcut needs none: ``` .\build-launcher.ps1 -Name Thin-clients -Folder \\fileserver\thinclients -Language en ``` writes `Thin-clients.exe` and `Thin-clients.args`. Copy the `.exe` with the other files of the manager (it is not signed: an antivirus that holds back unknown programs may want it allowed). With the manager on a file share, a shortcut to the `.exe` on the share shows a blank icon: Windows reads the icon of a shortcut's program only from a local disk. On each PC that starts the manager, run once from the share: ``` \\fileserver\it\manager\build-launcher.ps1 -Name "Thin clients" -Folder \\fileserver\thinclients -Install ``` It builds the `.exe` in `%ProgramData%\OpenThinRDP` with a `.script` file that points to the manager on the share (one copy of the manager for everybody), and puts a shortcut with the icon on the desktop. ### The manager updates itself Every release carries `openthinrdp-manager-.zip`: the files of `management/windows` (without the launcher `.exe`, which is built where it runs) and a `VERSION` file. Its name and SHA-256 are in the release's `LATEST.txt` (`manager=`, `manager_sha256=`), under the same project signature as the update package. - At every start the manager reads `VERSION` next to it, and in the background (the window does not wait) the `LATEST.txt` of the latest GitHub release of `UPDATE_GITHUB`, checked against the project key next to it (`openthinrdp-release.asc`). When that release has a newer manager, the top bar shows *Manager X available*. - **Update the manager** (that notice, or **Updates > This manager**) downloads the zip, checks its hash against the signed `LATEST.txt`, checks that it holds `OpenThinRDP-Manager.ps1` and `VERSION` of that version, copies the files it replaces into `previous\` next to the manager (the copy of the update before goes), puts the new ones in place and starts the manager again. `vault.otr`, the launchers (`.exe`, `.args`, `.script`) and anything else that is not in the zip stay as they are. A file in use is renamed to `.old` and removed at the next start. If anything fails, the files of `previous\` go back. - The copy that matters is the one the administrators start: on a file share, update it from a PC that can write there (the manager says so when it cannot). PCs with a launcher made by `-Install` start the share copy, so they get the new version at their next start. When `Launcher.cs` changed, the manager asks to build the `.exe` again with `build-launcher.ps1` (the old one keeps working). - Without the window, from a console or a script: `OpenThinRDP-Manager.cmd -UpdateManager` (exit code 0 = updated, 1 = error, 2 = nothing to do). - A copy without `VERSION` (the repository) is a development copy and never updates itself. The manager keeps the SSH host keys of the devices by device **name** in its own file (`%LOCALAPPDATA%\OpenThinRDP\known_hosts`, `HostKeyAlias`), not by address: DHCP may give a device the address another device had. A device that presents another key under its name (reinstalled) is reported, and the remote screen asks whether to forget the old key. ## Logo and background picture `logo.png` is the logo of the dialogs and of the sign-in form; `splash.png` is the background picture, shown behind the sign-in form on the `BG_TOP`/`BG_BOTTOM` gradient. In `config\branding\` they are for every device, in `config\branding\\` for that device only; a device without them uses the ones of its image. The manager's **Logo and background...** button (common settings, or a device) takes any picture, scales it down and saves it as PNG there; files copied by hand are fine too.

Manager: logo and background

The boot screen never changes from here: it always shows the picture and colors of the image (OpenThinRDP's, or your own when you build your image with `image/site/splash.png`). At power on the device shows the boot screen, then the background of your site from the sign-in form on. A device accepts PNG files only, up to 2 MB, a logo up to 400 x 240 pixels and a background up to 1024 x 640 (anything else is refused once with an event, and the previous picture stays), and shows them within 5 minutes. The status file says where each picture comes from: `BRANDING=logo=common splash=image` (`image`, `common` or `device`). ## Problems across the fleet Every device sends its event log to `status\-events.log`. The **Problems** tab of the Windows manager (since 0.1.7) reads them all together for a period (today, 24 hours, 7 or 30 days) and groups what went wrong: sign-in attempts that failed (by the broker really contacted), desktops that did not start, sign-ins refused, sessions dropped (by cause: network lost, FreeRDP stuck, error code), sessions taken over, UDP blocked, failed updates, rejected settings. A dropped session's event ends with FreeRDP's last error, and the device keeps its FreeRDP log as `/tmp/kiosk.log.prev`. Each line of the tab says how many times, on which devices and for which users, first and last time; a line is orange when the same problem hit 3 or more devices, which points at a server or the network rather than at a device. The line at the top sums up the problems that touched most devices; selecting a line shows its events. A device keeps the last 2000 lines of its log, which is weeks for a normal device. ## SSH keys With `SSH=on`, the `otradmin` account accepts only the keys in `/etc/openthinrdp/authorized_keys`: from the image (`image/site/authorized_keys`) or from `config\authorized_keys`. Removing `config\authorized_keys` after it was used revokes those keys and brings back the image ones. ## Vault Two keys give power over the whole fleet: the SSH key that opens the screen of any device, and the OpenPGP key that signs the update packages every device installs. The manager keeps both in `vault.otr`, next to `OpenThinRDP-Manager.ps1`, encrypted with an **access code**: - At the first start the manager asks for the code (at least 8 characters) and creates the vault, with a new SSH key inside (`openthinrdp-manager`) that it adds to the **SSH keys** page: Save there, and the devices take it within 5 minutes. At every later start it asks the code again; **Go on without** keeps the manager usable for the settings, without remote screen and signing. - Every administrator who starts the same copy of the manager (e.g. from a file share only the administrators can write) opens the same vault with the same code: nothing to copy from PC to PC. **Change the code...** re-encrypts it, and the old code stops working at once. - The code is not stored anywhere. Keep it where you keep the other passwords of your servers: if it is lost, the keys are lost with it (a new SSH key is easy; a lost signing key means a key change, see [SECURITY.md](../SECURITY.md)). - Encryption: PBKDF2-HMAC-SHA256 with 600000 rounds (about 2 seconds to open, on purpose), AES-256-CBC and HMAC-SHA256 over the whole file (`management/windows/OtrVault.cs`). The secrets stay in memory while the window is open; the SSH key is written for `ssh.exe` only into a folder that just your account and SYSTEM can read (`%LOCALAPPDATA%\OpenThinRDP\vault-keys`), removed when the vault closes. Vault page of the manager: the vault, the SSH key of the manager, the key that signs the updates On the **Vault** page: | Block | Actions | |---|---| | Vault | Open / Close, Change the code | | SSH key of the manager | Add to the devices (to the SSH keys page, then Save), Create a new key, Import a private key (e.g. the one used so far: the file stays where it is, delete it yourself) | | Key that signs the updates | Sign an update (see [Signed updates](#signed-updates)), Send to the devices and Create the site key (see [A fleet on its own key](#a-fleet-on-its-own-key)), Save the public key (`update-key.asc`), Import the key: the file of `gpg --export-secret-keys --armor ` from a key without a passphrase; the manager offers to delete that file afterwards. | The page says whether the signing key is the project key (releases for every installation) or one of yours (`update-key.asc` of your image). ## Remote screen **Remote screen...**, in the header of the selected device, shows its screen in your browser and lets you use it: to see what a user sees, or to sign in once to a web page on a screen with no keyboard (the kiosk page keeps the sign-in in its profile). **What it needs** - On the device: `SSH=on`, the key of the manager among the SSH keys, and `REMOTE_SCREEN=on` (the default). On the **SSH keys** page, **Add the key of the manager** adds it (from the [vault](#vault)); then Save, and the devices take it within 5 minutes. - On your PC: the OpenSSH client of Windows 10/11 (`ssh.exe`, an optional feature that is usually there), the vault open with the access code, and a browser. Nothing else is installed. Only the key of the vault is offered to the device (`ssh -i ... -o IdentitiesOnly=yes`): keys in your profile (`%USERPROFILE%\.ssh`) are not used. **How it works** 1. Over SSH, as `otradmin`, the manager asks the device to start the remote screen (`remote-screen.sh start`). The device starts x11vnc on its screen and the noVNC viewer with a small WebSocket bridge (`vncbridge.py`), both on 127.0.0.1 only and as the account that owns the screen, not root, with a one-time password. 2. The manager opens an SSH tunnel from a free port of your PC to the viewer and opens your browser on it with that password. The screen is reached only through the tunnel: nothing new listens on the network. 3. While it is on, the device shows *Remote assistance active: an administrator can see this screen* in the top right corner, and its event log records the start and the end. 4. Closing the small window in the manager ends it on the device (`remote-screen.sh stop`); it also ends by itself after 2 hours. During a remote session the screen is the Windows desktop the user is working on: tell the user, as the notice does. `REMOTE_SCREEN=off`, for all devices or one, turns it off. The viewer is noVNC 1.7.0, included unmodified (`/opt/openthinrdp/novnc`, MPL 2.0 and BSD licenses). **When it does not start** the manager shows why: SSH off on the device, no address reported yet, the vault closed or without an SSH key, the key not accepted (Add the key of the manager on the SSH keys page, then Save), or a device that has not received the version with the remote screen yet. A device reinstalled with the same address has a new SSH host key: remove the old one with `ssh-keygen -R
`. ## Signed updates Software updates (kiosk, FreeRDP with UDP, fixes) come from where `UPDATE_FROM` says: | `UPDATE_FROM` | Source | Signature accepted | |---|---|---| | `github` (default) | the latest release of `UPDATE_GITHUB` (not pre-releases), asked every 3 hours | the project key only | | `share` | `updates\` in the management folder: you decide when the devices get a version | the project key, or your own key | | `off` | none | | The project key is `/opt/openthinrdp/keys/openthinrdp-release.asc`, in the image and in every update (fingerprint `8552 734B 5C1B 67D7 C576 F1AB 79B2 3AE6 1CD4 F6AF`, see [SECURITY.md](../SECURITY.md)). A device installs a package only if `LATEST.txt` carries a valid signature, the hash of the package matches, and the version is newer than the installed one (or than the image, when no update was installed yet), or is the one `UPDATE_TO` asks for (see [Pilot devices and going back](#pilot-devices-and-going-back)). Installation runs in the background and never interrupts a session: a running kiosk is replaced as soon as the session is over. Three failed attempts per version at most. The status file shows the source (`UPDATE_FROM`) and the result. **A fleet you manage**: set `UPDATE_FROM=share` in `config\common.conf`. **Release from GitHub...** in the manager copies the latest release into `updates\` (package first, then signature and index), and into `updates\versions\.txt` (+ `.sig`) too: every device checks the signature itself. The 3 newest packages stay, and any package a device is kept on (`UPDATE_TO`, below); a package removed takes its `versions\` index with it. ### Pilot devices and going back `UPDATE_TO` says which version a device installs: `latest` (the default, the newest published) or a version such as `0.2.0`, installed also when it is older than the installed one, and kept until the setting changes. In `common.conf` it is the version of every device, in `.conf` of one device. A device kept on a version reads `updates\versions\.txt` and its signature (with `UPDATE_FROM=github`, the release with tag `v`). Devices with a version that does not know `UPDATE_TO` ignore it and install the newest. For the same reason a device goes back only to **0.2.0 or later**: an older version would bring back an updater that does not know `UPDATE_TO` and goes straight up again. The device refuses it and says so in its status. - **Pilot devices.** **Software version...** in the header of a device makes it a pilot (`UPDATE_TO=latest` of its own); the list marks it *(pilot)*. **Release from GitHub...** then asks: *all devices*, or *pilot devices only*. Pilot only keeps every other device on the version published now (`UPDATE_TO=` in `common.conf`, written before the new release is there; a folder published before `versions\` existed gets its index from the current `LATEST.txt` first), and only the pilots take the new one. - **Release to all devices** (page **Updates**) puts `common.conf` back to `UPDATE_TO=latest` when the pilots are fine. The page shows the newest version, the one all devices are on, the pilot devices, the devices on a version of their own and what the devices run now; the status bar too. - **Going back.** **Version for all devices...** (page **Updates**) or **Software version...** of a device chooses any version of `updates\versions\`, newest first. A version that is not there comes from GitHub with **Version from GitHub...** (the release with tag `v`, with the same checks: project signature, hash, signed again with the site key when there is one); an older version goes into `updates\` and `updates\versions\` without changing `updates\LATEST.txt`. Every change goes through the same files as any setting (`config\backup\`, `config\changes.log`), and the devices apply it within 5 minutes, never during a session. ### A fleet on its own key With `UPDATE_KEY=any` a device on the folder accepts the project key too: whoever held the project key could get a package installed, if they could also write into `updates\`. With `UPDATE_KEY=site` the devices accept only the key of your site, which exists only in the [vault](#vault) of your manager: the releases still come from GitHub, but nothing reaches the devices unless an administrator with the access code passes it on. 1. **Vault > Create the site key...**: an Ed25519 key made in the manager, kept only in the vault. **Save the public key...** writes its public half, `update-key.asc` (nothing secret: put it into your image too, if you build one). 2. **Vault > Send to the devices...**: the public half to every device that is on, over SSH with the key of the manager (`site-key.sh` checks it and logs it). It cannot come through the folder: whoever may write there would choose the key. Each device reports it (**Summary > Update signatures**); send it again for the devices that were off. 3. `UPDATE_FROM=share` and `UPDATE_KEY=site` in `common.conf` (a pilot device first, as an exception). 4. From then on **Release from GitHub...** checks the project signature, **signs the release again with the site key** and publishes it: the new signature replaces the project one. A release whose project signature cannot be checked in the manager is never signed again. A device without the site key yet (off during step 2, or reinstalled from the ISO) still accepts the project key, so it is never left without updates; it reports `site key: none` until it gets it. If the vault and its code are lost, the devices on `UPDATE_KEY=site` install nothing more from the folder: create a new site key, send it over SSH (step 2: SSH is enough, the folder is not involved), and they accept the new one. **Your own packages** (changes of yours, or your site files) need your own key, built into your image as `/etc/openthinrdp/update-key.asc`. Create it once, on a machine you trust, without a passphrase (the vault protects it), put it into the [vault](#vault) and remove it from gpg: ``` gpg --quick-gen-key "Thin client updates " ed25519 sign never gpg --armor --export it@example.com > image/site/update-key.asc gpg --armor --export-secret-keys it@example.com > it-signing.asc # Vault > Import the key..., then delete it gpg --delete-secret-keys it@example.com ``` Build a version without signing it (Linux or WSL), then sign it in the manager: ``` management/publish/make-update.sh -v 2026.10.1 OpenThinRDP-Manager.cmd -Sign \out\update-2026.10.1\LATEST.txt ``` `-Sign` asks only the access code and writes `LATEST.txt.sig` next to `LATEST.txt` (also **Vault > Sign an update...**). It checks the format of `LATEST.txt` and, when the package is next to it, its hash; the signature is the one `gpg --armor --detach-sign` would write (Ed25519, SHA-512), checked by `sqv` on the devices. Then copy the package, `LATEST.txt.sig` and `LATEST.txt`, in this order, into `updates\`. With the key still in gpg on the build machine, `-k` signs there and `-o` publishes at once (Linux, WSL or Git Bash with Gpg4win; the folder mounted or reachable as a path): ``` management/publish/make-update.sh -v 2026.10.1 -k it@example.com -o /mnt/thinclients ``` `-d ` adds `.deb` files (e.g. your own FreeRDP build, installed only when newer); `-s image/site` ships your site files too. To keep FreeRDP as it is on one device, create `/etc/openthinrdp/freerdp-pinned` with the reason inside. --- # UDP transport Windows sends Remote Desktop graphics over UDP when the client supports MS-RDPEMT/RDPEUDP. Over TCP a busy server or a lossy line makes the screen fall behind: every lost packet holds up everything after it. Over UDP the server measures the line and lowers frame rate and quality instead, so the session stays responsive. FreeRDP does not ship UDP support yet: it is under review upstream ([FreeRDP pull request #13608](https://github.com/FreeRDP/FreeRDP/pull/13608)). OpenThinRDP ships FreeRDP built with it, so it works out of the box. ## What travels over UDP With the UDP transport Windows moves the dynamic virtual channels onto the reliable UDP tunnel (with Soft-Sync): graphics, USB redirection, audio, camera, input and the other dynamic channels. The static channels stay on the TCP connection: clipboard, drive redirection (smart cards and serial ports included) and printing. The TCP connection also carries sign-in and licensing and is always needed, also with `TRANSPORT=udp`: if it drops, the session drops. A USB stick passed whole over the UDP tunnel moved 1 GiB each way and 4000 small files each way, all with the same SHA-256 at both ends. ## Modes `TRANSPORT`, per device or for all of them (settings dialog, central settings, manager): | Value | Behaviour | |---|---| | `auto` (default) | UDP. When UDP is blocked, or fails during the session, FreeRDP goes on over TCP; the next connection tries UDP again | | `udp` | UDP only. A connection that runs without UDP for 30 s is closed and opened again; after 3 in a row the device shows "UDP not available". For networks where UDP is known to work, and for testing | | `tcp` | TCP only | The kiosk runs FreeRDP with `/network:auto` for `auto` and `udp`, and with `-multitransport` for `tcp`. It does not use `/auto-reconnect`: when FreeRDP's own reconnection fails it exits with the same code as an administrator disconnect, and the device would stop. The kiosk reopens the session itself instead. The `status\.txt` line `TRANSPORT=auto (in use: udp)` shows what the session really uses. ## Server side Nothing to install. UDP must be allowed: - Group Policy *Computer Configuration > Administrative Templates > Windows Components > Remote Desktop Services > Remote Desktop Session Host > Connections > Select RDP transport protocols*: not configured, or "Use both UDP and TCP". - UDP port 3389 open on the session hosts (Windows opens it by default) and on any firewall in between. The broker does not need it: it only redirects. ## On a lossy line It started with a switch port with a faulty transceiver, losing **43% of the large packets** (5% of the small ones). We reproduced it with netem on the thin client, on the traffic from the virtual machine only, then raised the loss on every packet step by step until the session gave up. Windows 11 VDI behind the broker, full-screen load that changes every pixel at 30 frames per second; frame rate and loss as reported by the server; 90-120 s per step. | packets lost | OpenThinRDP over UDP | TCP, same line and load | |---|---|---| | 0% | 15-21 fps | not measured | | 5% | not measured | 7-12 fps | | 10% | not measured | 6 fps | | 20% | not measured | **0.6 fps**, the screen barely moves | | 30% | not measured | 0.2 fps, 26 frames a second dropped by the server | | 43% of the large packets only\* (the faulty port) | **16-21 fps**, 7 minutes in a row | 0-0.1 fps | | 50% | **19-21 fps** | not measured (already at 0.2 fps with 30%) | | 60% | 14-21 fps | not measured | | **70%** | **18-19 fps**\*\* | not measured | | 80% | 4 fps, then the tunnel gives up after 18 s | not measured | \* Every other row loses that share of all the packets from the virtual machine; this one, as on the faulty port, 43% of the large packets and 5% of the small ones. \*\* With the FreeRDP of OpenThinRDP 0.1.5 (no 1 s freeze at every wrap of the channel sequence; 9-13 fps before). At 70% the first thing to give up is the TCP connection that RDP keeps beside the UDP tunnel: after 20 s without an acknowledgement (`/timeout`, set by the kiosk) it ends the session, which happened once after 100 s and not in another run of 150 s. On UDP no frame was dropped by the server for network reasons up to 70%. A session opened when the line is already bad is set up over UDP too (14-19 fps at 43%). **Limits of these numbers:** one environment (Dell OptiPlex 3000 Thin Client, Windows 11 VDI on Hyper-V behind an RD Connection Broker), one kind of load, loss emulated with netem on the client. The method and the scripts are in [tests/udp-bench](../tests/udp-bench/README.md): numbers from other networks and servers are very welcome. Over TCP every lost packet holds up everything behind it until it is resent; over UDP the lost packets are resent while the rest of the stream goes on. Getting there took a fix to the UDP transport: after the channel sequence wrapped, an unsigned difference went below zero, the client believed the stream had been stuck for ages and ended the connection within 1-25 seconds of the loss starting. We reported it with a fix in the pull request; its author fixed the root cause there (the client now skips channel sequence 0 like Windows does). ## The FreeRDP in the image `image/freerdp/freerdp.conf` names the exact source: commit `dda5414` on branch [`openthinrdp-udp-3.32.2-pr58`](https://github.com/Ceschi-ops/FreeRDP/tree/openthinrdp-udp-3.32.2-pr58) of a FreeRDP fork, which is the **FreeRDP 3.32.2** release (the security release of 2026-10-12) with the 58 commits of pull request #13608, the UDP transport, at its head `f54ad1d36`, applied unchanged. Several of those commits came from our tests: no false "stream is stuck" on lossy lines (see above), ten SYN attempts instead of four (SYN and SYN+ACK are padded to the MTU, the first packets a bad line loses), run-length ACK vectors, reconnecting through the broker after its one-time token, and UDP declined only when the connection really goes through an RD Gateway. On top, nine commits of FreeRDP master that came after 3.32.2: - The header of bandwidth measure payloads is counted, as mstsc does. With `/network:auto`, which the kiosk passes with UDP, Windows otherwise reported a bandwidth of 0 and started every session **5 seconds late**: from the broker's redirect to the desktop took 5.1 s, now 0.13–0.18 s. - `/network` is applied before the other options (ours, [#13748](https://github.com/FreeRDP/FreeRDP/pull/13748)). Without it `/network:auto` turned on the H.264 codecs that `RDP_GFX` turns off, and the server encoded the whole desktop with AVC444 (0.1.12). - The microphone keeps capturing when the server changes only the bit rate (ours, [#13751](https://github.com/FreeRDP/FreeRDP/pull/13751)). Windows 11 switches between AAC entries several times a second, and every switch closed and reopened the capture: the other end of a call heard gaps. - RD Gateway: a possible out-of-bounds read, RDG packet handling, a partly read header (five commits). - USB: a failed kernel driver detach no longer stops the redirection (two commits). FreeRDP 3.32.2 already contains the USB fixes found while building OpenThinRDP: #13674 (deadlock between USB hotplug and the closing of the USB channel) and #13679 (from our #13677: no hang on disconnect with a USB transfer pending), plus #13618 (no real USB device reset). When the UDP transport reaches a FreeRDP release, the image goes back to Debian's packages. The packages are built by `image/freerdp/build-debs.sh` (Debian's trixie-backports packaging, with `-DWITH_RDPEUDP=ON`). The image build downloads the prebuilt ones from the release named in `freerdp.conf` and checks them against `image/freerdp/SHA256SUMS`, or builds them when that file is missing. To use Debian's FreeRDP instead (TCP only), set `FREERDP_FROM=backports` in `build.conf`. ## Reporting Problems with UDP itself (a session that freezes, drops, falls back to TCP) are worth reporting upstream, in the pull request thread, with: - the FreeRDP log of the session (`/tmp/kiosk.log` on the device; for a session that dropped, `/tmp/kiosk.log.prev`) and the events (`status\-events.log` in the management folder: the DROPPED event ends with FreeRDP's last error); - what the network was doing (Wi-Fi, VPN, loss), and the Windows version of the session host. Problems with the kiosk or the image: issues in this repository. --- # Security model A thin client is a public-facing device: anyone can sit at it. OpenThinRDP assumes that whoever is at the keyboard is untrusted and that the management folder is readable by the devices only. ## What protects what - **No shell for the user.** Openbox without menus or key bindings; the only programs are the sign-in form, FreeRDP and, if enabled, Chromium with policies (no downloads, extensions, developer tools, guest or incognito windows; in `app` mode only the allowed sites). - **No text consoles.** Ctrl+Alt+F1..F12 do not leave X (`DontVTSwitch`), there are no spare consoles with a login prompt (`NAutoVTs=0`), and the `kiosk` account is locked: it logs in automatically on tty1 only. (0.1.0 left `kiosk` with an empty password and the spare consoles on: anybody at the keyboard could get a shell with Ctrl+Alt+F2. Fixed in 0.1.1, also on devices updated from 0.1.0.) - **Diagnostics without a PIN** (the ℹ button of the sign-in form): read only, it shows the network, the reachability of the remote desktop hosts and the last events, never a setting or a secret. - **Settings dialog behind a PIN** (`SETTINGS_PIN_SHA256`). Without a PIN anyone at the device can point it to another broker. The "Terminal" button of the dialog opens a shell as the `kiosk` user. - **`kiosk` has one root privilege:** `save-config.sh`, which validates every value before touching the system. - **No password in the image.** `root` is locked; `otradmin` has no password unless you set a hash in `build.conf`; SSH is off by default and accepts only keys. - **Signed updates.** A package is installed only with a valid signature (verified with `sqv`): the project key, or with `UPDATE_FROM=share` also your own key ([management.md](management.md#signed-updates)), and a matching hash; never an older version. - **Credentials.** The user's password goes to FreeRDP on its command line, which FreeRDP overwrites with `*` right after start (visible to the local `kiosk` account for a moment at most); it is never stored. The Wi-Fi key and the management folder password are never reported or printed back (`save-config.sh --print` shows only their hash). The settings PIN hash is never written to the status files. - **The device's secrets in the TPM, when there is one.** The management folder password, the Wi-Fi key, the SSH host keys, the VPN key and profile, and the 802.1X password and the private key of the 802.1X certificate ([configuration.md](configuration.md#8021x-and-wi-fi-enterprise)) are the only secrets on the device. With a TPM 2.0 (most PCs and thin clients since about 2016) they are kept in `/etc/openthinrdp/sealed/`, encrypted with a key that never leaves the TPM (`systemd-creds --with-key=tpm2`), and decrypted at every start into `/run/openthinrdp/secrets/`, in memory only, before the network and SSH start. A disk or eMMC taken out of the device, or an image of it, gives nothing. They are bound to the TPM only, not to firmware measurements (PCRs), so a BIOS update or a Secure Boot change does not lose them. Devices set up before move their plain files into the TPM at the next start; the status file says which mode a device is in (`SECRETS=tpm` or `SECRETS=file (no TPM)`, also in the manager). Without a TPM they stay in root-only files (600), as before, so older PCs keep working. The folder password, the Wi-Fi key and the 802.1X certificate key keep their usual paths, links to the copy in memory, so the network configuration never changes; a secret is read back from the TPM and compared before its plain file is deleted. `TPM_SECRETS=off` (central or per device) keeps a device out of the TPM, and puts back in root-only files the secrets of a device that had them there, at the next start; set centrally, it counts from the first start after the update. To roll it out, set it off in `common.conf`, on for one device, restart that one twice, then remove the line. If the TPM is cleared or replaced, or the disk moved to another device, the secrets cannot be read: an event says so, the SSH host keys are created again, and the folder password and the Wi-Fi key must be set again. - **Remote screen only through SSH.** x11vnc and the viewer's WebSocket bridge listen on 127.0.0.1 only, run as the account that owns the screen (not root), start only when an administrator asks over SSH (`otradmin`, key login) and need a one-time password. While the remote screen is on, the device says so on its screen and its event log records start and end; it stops by itself after 2 hours. `REMOTE_SCREEN=off` turns it off ([management.md](management.md#remote-screen)). ## Known limits - `smb.cred` is on every device: a stolen device gives its holder that account. Give it write access to `status\` only and read access to the rest of the management folder ([management.md](management.md#account)): then that account cannot change the settings of the other devices nor offer them anything to install. Change the password if a device is lost (`MGMT_PASSWORD` in the central settings rotates it on every device that can still reach the folder). - The management folder is trusted: whoever can write `config\` can change the settings of every device (within the validation rules) and can make devices mount another folder. Updates still need a valid signature (the project key, or your own key). - `RDP_CERT=ignore` accepts any server certificate. The default `tofu` trusts the first certificate seen per server; `verify` requires certificates trusted by the system. - The PIN is a speed bump, not a vault. The manager stores it salted and stretched (PBKDF2-SHA256, 600,000 iterations, about 0.2 s per try on a thin client): a 6-digit PIN takes days to brute-force from a stolen settings file, a 4-digit one less than an hour, so use 6 digits or more. A PIN set by 0.1.4 or earlier is a plain SHA-256 and still works; set it again from the manager to store it the new way. - The disk is not encrypted as a whole: the code is public and no user data is stored on the device, so only the secrets above are protected, and only with a TPM. Without one, physical access to the disk gives them. With one, the whole device in someone's hands still starts and uses them, like any device that boots unattended; lock the firmware setup and USB boot with a BIOS password, and rotate the folder password if a device is lost. Plain copies deleted when the secrets moved into the TPM may survive in flash memory blocks until overwritten. Report security problems privately to the maintainers (GitHub security advisories) rather than in a public issue.