Remote access to Windows servers is one of those problems everyone has and nobody enjoys. For years the default answer has been a VPN plus an RDP client. Cloudflare now offers another option: RDP in a browser tab, with no VPN, no client software, and no inbound ports open on your network.
This article is a practical guide to that feature. It covers how it works, what you need before you start, how to set it up, how it compares with the VPN approach, and what went wrong when we deployed it for a public sector organisation. We deployed it in [Month YYYY], within a year of the feature reaching general availability. To our knowledge, that makes ours one of the first production deployments of the native service, so treat this as field notes from early in the product's life.
TL;DR
- What: Cloudflare Access and Cloudflare Tunnel let administrators reach private Windows servers over RDP from a web browser. No VPN, no RDP client, no inbound firewall rules.
- Who it's for: sysadmins, cloud engineers, and security leads replacing VPN + RDP, especially in air-gapped environments.
- My deployment: An infrastructure on Cloud, with multiple network segments, many Windows Server targets, and one outbound-only tunnel.
- The error:
Code 4: An unexpected error occurred during connection setup. Detailed error: received an RDCleanPath error: general error (code 1)can mean the target's RDP security layer is set to legacy RDP mode(RDP Security Layer = 0). Switching it to Negotiate (Security Layer = 1)fixed it for me. Cloudflare's docs also list SSL (Security Layer = 2)as supported, which I did not test.
Why VPN + RDP wears thin
The organisation I worked with had a typical setup. Administrators connected to an SSL VPN, then opened Microsoft's Remote Desktop client (mstsc) to reach servers in a private cloud network. It worked, but it came with familiar costs:
- Two things to install and maintain on every administrator's machine: the VPN client and the RDP client.
- Network-level access. Once a user is on the VPN, they typically see far more of the network than the one server they need.
- Two places to manage joiners and leavers: the VPN and the servers themselves.
- Hard-to-evidence access. Answering "who connected to what, and when?" for an auditor means stitching logs together.
RDP itself also has a poor security reputation. Cloudflare's own write-up on the feature points to weak credentials and exposed port 3389 as the usual culprits, and to vulnerabilities such as BlueKeep (CVE-2019-0708) as the reason you don't want RDP reachable from the internet. Putting RDP behind identity-aware access, with nothing exposed inbound, addresses that directly.
What browser-based RDP is, and how it works
Browser-based RDP is part of Cloudflare Access, alongside the clientless SSH and VNC options that already existed. A user signs in (enforced MFA/OTP/SSO) through CloudflareAccess portal (with your customizable team name in the URL), picks the Windows server, enters their Windows credentials, and gets a desktop rendered in the browser.
The moving parts, in plain terms:
- Cloudflare Access authenticates the user against your identity provider and checks them against your policies. Access applications are deny-by-default.
- A browser RDP client (Cloudflare uses IronRDP, written in Rust) runs in the page. Browsers can't speak raw RDP over TCP, so the client wraps the session in a WebSocket over TLS.
- Cloudflare's network proxies that session to your environment and applies policy and logging along the way.
- Cloudflare Tunnel (
cloudflared) makes an outbound-only connection from your private network to Cloudflare, so there is nothing to open inbound. Can be installed on the target server, but since we are avoiding installing anything on the target server, we go with a linux server with cloudflared installed and an active tunnel alongside being reachable to the target servers. - The Windows server receives a normal RDP connection from the connector side and authenticates the user with their Windows credentials. Cloudflare doesn't manage those credentials.
Two design details matter later in this article. First, Cloudflare connects to the target over TLS. Second, Cloudflare's launch post says it deliberately keeps legacy, weak RDP security features from reaching your servers. Keep both in mind for the troubleshooting section.
Sources: Cloudflare launch post and Cloudflare docs.
A short history: from Guacamole to a native service
Browser-based RDP behind Cloudflare is not new in spirit. What's new is that Cloudflare now provides it natively.
| When | What happened |
|---|---|
| Before 2025 | Teams that wanted browser-based RDP behind Cloudflare Access ran their own gateway, such as Apache Guacamole or Devolutions Gateway, and published it as an Access application. |
| March 2025 | Cloudflare announced a native browser-based RDP service in closed beta. |
| End of June 2025 | Open beta for all customers. |
| September 2025 | General availability. |
| Since then | Still evolving. At the time of writing, file transfer is labelled beta in the docs. |
The practical difference is that you no longer run and patch a gateway yourself. Cloudflare's launch post lists infrastructure complexity, maintenance burden, and extra security reviews as the pain points of the self-hosted approach. For a network and security team that has to justify every extra component, that matters.
Architecture
Figure 1: High-level design. No inbound ports are opened on the private network.
Following the numbers in the diagram:
- The administrator opens the App Launcher over HTTPS.
- They select the RDP application, which hands them to Cloudflare Access.
- Access sends them to the identity provider for SSO and MFA.
- Once an Allow policy matches, Access starts a browser RDP session.
- The session travels through the Cloudflare Tunnel, which the connector opened outbound.
- The connector reaches the Windows target on TCP/3389, and the administrator signs in with Windows credentials.
For the detail behind each box (addressing, ports, security group rules, and the registry settings on the target), here is the low-level design:
Figure 2: Low-level design. Open the image full size to read the detail.
What has to be true before you start
These requirements come from Cloudflare's current documentation as of October 2026. Check the docs for changes, because this is a young feature.
| Requirement | Detail |
|---|---|
| Cloudflare domain | An active domain on Cloudflare (full or partial setup) for the application hostname |
| Windows version | Windows Server 2016, 2019, 2022, 2025, or Windows 10/11 Pro and Enterprise |
| RDP security layer | Set to Negotiate or SSL (per the docs), not the legacy RDP option. Security Layer = 1 or 2* (Legacy is Security Layer = 0) [I have tried and tested Security Layer = 1 but not 2. The Cloudflare docs do confirm it though.] |
| Connectivity | A Cloudflare Tunnel with a route covering each target's IP |
| Hostname | Browser RDP works with public hostnames only |
| Policies | Allow or Block only. Bypass and Service Auth aren't supported for browser-rendered apps |
| Client auth | "Authenticate with Cloudflare One Client" must stay off for this app |
| User credentials | Users still need valid Windows credentials on the target |
| Browser | Current Chrome, Edge, Firefox, or Safari. Clipboard sharing needs extra configuration in Firefox |
The bold row is the one that cost me a lot of time.
*I have tried and tested Security Layer = 1 and not 2 because this was before Cloudflare had this in their docs. So I was just trying the next layer and if it did not work I would have moved to 2.
My sincere apologies if you came here for that.
And due to time, I moved to production after confirming it was working.
And as you guys already know, If it works don't touch it.
Setup walkthrough [On Cloudflare Dashboard]
This is a condensed version of the flow. UI labels change, so follow the current Cloudflare docs for exact clicks.
1. Create the tunnel and install the connector
Create a cloudflared tunnel in the Cloudflare dashboard and run the install command it generates on your connector host. The connector can run on the RDP server itself or on any host inside the private network. We used a dedicated Linux VM.
# The cloudflare dashboard gives you the exact install steps for your OS. # You can chose to install as a service (runs automatically) or run it manually # I installed it as a service. # For a token-based tunnel, the final step looks like this: sudo cloudflared service install <TUNNEL_TOKEN> Confirm the tunnel shows as Healthy in the dashboard.
2. Route your subnets through the tunnel
Add a Private CIDR (Networking > Tunnels > Routes > Add Route > Private CIDR) route for each subnet that contains targets. Use virtual networks (Networking > Routes > Virtual Networks) to keep environments logically separate. We used one virtual network per segment.
3. Add targets
Create a target (Zero Trust > Access Controls > Targets) for each Windows server: a friendly hostname plus its private IP and virtual network. The hostname is just a label for grouping and selection, and isn't used for DNS resolution. An IP and virtual network pairing can belong to only one target.
4. Create a DNS record for the application hostname
Create a proxied DNS record for the hostname users will visit. It doesn't have to point at anything live, because Cloudflare's RDP proxy handles the routing. The docs give placeholder values for A, AAAA, and CNAME records.
5. Create the Access application
Create a self-hosted application on your public hostname, enable the browser-based RDP option, and choose RDP. Then select your target hostnames and the RDP port (normally 3389), attach your Allow policies, choose your identity provider, and set a session duration.
Figure 3: The browser-based RDP application settings.
6. Write policies
Policies decide who gets in. We used email-based Allow policies, one for the organisation's administrators and one for the implementation partner's engineers. In current Cloudflare docs, new policies deny clipboard and file transfer by default, and you enable each direction explicitly. For a public sector environment, that default is worth keeping.
7. (Recommended) Add a Gateway network policy
Cloudflare recommends an explicit Gateway policy that allows traffic to Access infrastructure targets, so that Access policies are evaluated in the intended order. Also make sure "Enforce Cloudflare One Client session duration" is off, or users will be blocked.
8. Prepare the Windows servers and the network
- Set the RDP security layer to Negotiate or SSL, per Cloudflare's docs. We verified Negotiate (more on this below).
- Allow TCP/3389 to each target only from the connector's private IP, in both the cloud security group and the Windows firewall.
- Don't expose 3389 to the internet at all.
9. Connect as a user
Users sign in to the App Launcher at https://<team-name>.cloudflareaccess.com, authenticate with the identity provider, select a server tile, and enter Windows credentials.
Figure 4: The customised App Launcher that administrators/users use as their entry point.
Our deployment
The environment, anonymised:
| Item | Detail |
|---|---|
| Platform | Huawei Cloud, private VPC |
| Previous access method | SSL VPN + Microsoft RDP client (MSTSC) |
| Targets | Multiple Windows Server hosts (Windows Server 2019, domain-joined), across a few network segments - /24 subnets |
| Connector | One Ubuntu VM running cloudflared, deployed token-based |
| Tunnel | A single tunnel advertising all subnets |
| Segmentation | One virtual network per subnet |
| Identity | Email-based Allow policies, MFA enforced, and a custom-branded login page and App Launcher |
| Isolation | Cloudflare Browser Isolation applied to the RDP sessions |
| Logging | Cloudflare Access audit logging enabled |
| Deployed | Q3 2026 |
Design decisions worth copying:
- A dedicated connector host. It does one job. Its only inbound rule is SSH port 22 from an administrator's IP, and its outbound traffic reaches Cloudflare.
- Least-privilege network rules. Each Windows target accepts RDP only from the connector's private IP.
- One tunnel, many routes. It's easy to operate, and adding a subnet means adding a route.
- Branding. A custom-branded login page and App Launcher give administrators a recognisable, legitimate-looking entry point.
Before and after
An honest comparison, including the trade-offs:
| Dimension | Before: SSL VPN + MSTSC | After: Cloudflare browser-based RDP |
|---|---|---|
| Software on admin devices | VPN client and RDP client | A web browser |
| Network exposure model | VPN gives network-level reach once connected | Access is granted per application and per target |
| Inbound ports | VPN gateway exposed to the internet | No inbound ports for RDP |
| Authentication | VPN credentials, then Windows credentials | Identity provider SSO and MFA, then Windows credentials |
| Joiners and leavers | Manage the VPN and the servers | Update Access policies (the Windows accounts still exist separately) |
| Access evidence | Logs spread across systems | Cloudflare Access logs, centrally available |
| Operational dependency | VPN gateway | Cloudflare's service plus the connector host |
| Per-server prerequisites | Few | RDP security layer must allow TLS |
| Feature limits | Full RDP client features | Some limits (see below - Limitations section - you'll have to scroll a bit) |
The trade-offs are real. You gain a dependency on a third-party service, you have to prepare each server or enforce domain level GPOs, and the feature set is smaller than a full RDP client. For this organisation, the security and management gains outweighed that but your answer may differ.
When it failed: the troubleshooting story
Everything looked right. The tunnel was healthy, the routes were in place, users could sign in through Cloudflare, and the target list appeared. But every connection died right after the user entered their Windows credentials:
Unable to connect to your remote desktop. Code 4: An unexpected error occurred during connection setup. Detailed error: received an RDCleanPath error: general error (code 1) Over two troubleshooting sessions, we worked down the stack.
What I checked, and what each check told me
| Layer | How we checked | Result |
|---|---|---|
| Tunnel | Cloudflare dashboard | Healthy and connected |
| Network | nc -zv <target-ip> 3389 from the connector | Connected |
| Cloud security groups | Console review | Correct |
| Windows RDP service | netstat -an on the target | Listening on 3389 |
| Windows firewall | Review | Not blocking |
| Native RDP | VPN + MSTSC to the same server | Worked |
| Credentials | Several account types and username formats | No change |
| Windows Security log | Event Viewer | No failed-logon events at all |
That last row was the most puzzling clue. If credentials were being rejected, there would have been failed-logon events. There were none, which meant the connection was dying before Windows authentication.
Reading the Windows event logs
The pattern of events that are present and absent tells you how far a connection got:
| Event | Log | Meaning | Seen in our failures? |
|---|---|---|---|
| 261 | TerminalServices-RemoteConnectionManager | RDP listener received a connection | Yes |
| 1149 | TerminalServices-RemoteConnectionManager | RDP authentication stage reached | No |
| 4624 | Security | Successful logon | No |
| 4625 | Security | Failed logon | No |
| 4798 | Security | Local group membership enumerated | Yes |
Event 261 says the connection reached the Windows RDP listener. The absence of everything after it says the session never got to authentication.
One caveat: treat 1149 as a progress marker, not proof that credentials were valid, because its exact behaviour varies with NLA settings.
Rely on 4624 and 4625 for actual logon outcomes.
Reading the tunnel connector logs
The cloudflared logs showed a sequence of "origin connection established", "proxy stream acknowledged", and "tcp proxy stream finished successfully", followed by a "use of closed network connection" error. In other words, the tunnel reached the server and the TCP connection opened, and then something closed it. That ruled out the tunnel and the network and pointed at the RDP layer.
The root cause
I queried the RDP security settings on the target:
Get-ItemProperty ` 'HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp' | Select SecurityLayer, MinEncryptionLevel, UserAuthentication SecurityLayer : 0 MinEncryptionLevel : 2 UserAuthentication : 0 SecurityLayer = 0 was the server's original state. It selects the legacy RDP security layer, which doesn't use TLS:
| SecurityLayer | Mode | Works with Cloudflare browser RDP? |
|---|---|---|
| 0 | RDP (legacy) | No |
| 1 | Negotiate | Yes (tested by us) |
| 2 | SSL (TLS) | Listed as supported in the docs; not tested by us |
Cloudflare's documentation now states this requirement explicitly: the browser RDP service connects to the target over TLS, so the security layer must be Negotiate or SSL, and a server set to legacy RDP will fail to connect. When I deployed, the documentation did not cover this requirement. As an early adopter I have watched both the product interface and the docs change since, so check the current docs. If you're reading this before deploying, you can skip the two troubleshooting sessions.
Native RDP clients still worked against the same server, because MSTSC is happy with the legacy layer. That is why "RDP works from my machine" proved nothing here.
The fix
# Change the security layer from legacy (0) to Negotiate (1) Set-ItemProperty ` 'HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp' ` -Name SecurityLayer -Value 1 # Restart Remote Desktop Services to apply Restart-Service TermService -Force Note: use restart, not stop and start. Once you stop, you may not be able to connect to it again, unless you have direct console access.
After the change, browser RDP sessions connected and the Windows desktop rendered in the browser. We set the value to 1 (Negotiate). We did not test 2 (SSL), so we can't confirm it from experience.
Three practical warnings:
- Restarting Remote Desktop Services drops active RDP sessions. If you're connected via RDP, you'll be disconnected. Make sure you have another way in (the cloud console, for example) before you restart.
- On domain-joined servers, check Group Policy first. If a policy sets the security layer, it will take precedence over the local registry value. The relevant Windows policy is "Require use of specific security layer for remote (RDP) connections", and setting it there is also the consistent way to fix a whole fleet.
- Be careful when relaxing NLA. Cloudflare's docs warn that if you disable Network Level Authentication (needed for Entra-joined accounts), you should turn off NLA only and not switch the security layer to legacy RDP. Our targets had NLA disabled and the fix worked, but we didn't test with NLA enabled, so we can't say it's required for other account types. Test in your own environment.
Red herrings that wasted our time
- Certificate errors from the
cloudflaredCLI. On a token-deployed tunnel, running commands likecloudflared tunnel runby hand producescert.pemerrors. That is expected for token-based tunnels and doesn't mean the production tunnel is broken. Check health in the Clouflare dashboard instead. - "Native RDP works, so the server is fine." Native clients tolerate the legacy layer, and Cloudflare's service doesn't.
- "TCP/3389 is reachable, so the network is fine." True, but a successful TCP connection says nothing about whether the RDP handshake will succeed.
- Toggling NLA and trying more accounts. It didn't help, because the failure happened before authentication.
An error decoder for Code 4 / general error (code 1)
That error message is a catch-all, and the same headline can have different causes. This table reflects our experience and what other users report, so treat it as a starting point:
| What you see | Likely layer | First things to check |
|---|---|---|
| Same error with a WSA 10060 timeout appended | Network reachability | Route covers the target IP in the right virtual network. Cloud security group and Windows firewall allow 3389 from the connector. nc -zv from the connector. |
| No timeout detail. Event 261 appears, but no 1149, 4624, or 4625 | RDP security layer / TLS | Check SecurityLayer. It must not be 0. I fixed ours with 1 (Negotiate). |
| Event 261 plus logon failures (4625) | Credentials or authorisation | Username format, account rights, Remote Desktop Users membership |
| Tunnel shows unhealthy | Connector | cloudflared service on the connector host, outbound connectivity |
| Everything works for Chrome but clipboard fails in Firefox | Browser settings | Firefox needs additional clipboard flags per the docs |
Public sector considerations
If you work in government or a regulated environment, these points deserve attention:
- Identity and MFA at the front door. Access policies and your identity provider decide who reaches a server, before any Windows login.
- Audit evidence. Cloudflare Access logs give you a central record of access decisions, and audit logging is enabled in our deployment. Confirm what session-level detail your own plan retains and for how long, and whether it satisfies your audit requirements.
- Clipboard and file transfer controls. New policies deny both by default. Enable only what each role needs.
- Policy hygiene. Review Allow policies on a schedule and remove leavers promptly. This is my recommendation, not a Cloudflare requirement.
- Third-party and data-flow review. Traffic and metadata flow through Cloudflare's network. Check this against your data sovereignty, procurement, and security assessment rules. Cloudflare said at launch that it planned to include browser-based RDP in its FedRAMP High offering, so check its current compliance status if that matters to you.
- A break-glass path. Keep an alternate administrative route, such as the cloud provider's console or even the VPN + mstsc on standby, so a bad RDP change or service outage can't lock everyone out.
Limitations, and when it may not fit
From Cloudflare's current docs(maybe be outdated depending on when you are reading this), known limitations include:
- Cloudflare uses TLS to connect to the target but doesn't verify the target's TLS certificate.
- No audio redirection.
- Clipboard transfers are capped at 500 KB, text only for the text controls.
- File transfer is in beta, with size and count limits.
- Local printing is limited to PDFs via the file transfer panel.
- Entra-joined accounts need NLA enforcement turned off.
It may not suit you if you need full RDP client features such as audio or rich device redirection, if you can't route administrative traffic through a third party, or if your servers can't use a TLS-capable security layer.
Alternatives include self-hosted gateways like Apache Guacamole or Devolutions Gateway (more control, more to run and patch), the Cloudflare One Client with a native RDP client (needs software on devices), or a conventional VPN.
Pre-flight checklist for each new target
- [ ] Server's subnet is covered by a tunnel route
- [ ] Cloud security group allows TCP/3389 only from the connector's private IP
- [ ] Windows firewall allows 3389 from the connector
- [ ] RDP security layer is not legacy: Negotiate (verified by me) or SSL (per the docs). Check for a Group Policy override
- [ ] Windows OS version is on Cloudflare's supported list
- [ ] Target created with the correct IP and virtual network
- [ ] Target hostname is selected in the Access application
- [ ] User matches an Allow policy
- [ ]
nc -zv <target-ip> 3389succeeds from the connector (dedicated Ubuntu server) - [ ] End-to-end test from a browser, outside the VPN
- [ ] Alternate administrative access confirmed before changing RDP settings
FAQ
Do users need a VPN or RDP client?
No. They need a supported web browser and valid Windows credentials.
Does Cloudflare store or manage the Windows passwords?
No. Users authenticate to Windows with credentials already configured on the server.
Can the connector run on the RDP server?
Yes, or on any host inside the private network. We used a dedicated Linux VM.
Does it work with domain-joined servers?
Yes. Our targets were domain-joined. Cloudflare's docs list supported username formats, including DOMAIN\user and UPN.
What does "Code 4 ... general error (code 1)" mean?
It's a generic proxy-side failure. Use the decoder table above to narrow the cause.
Is it generally available?
Yes, since September 2025. Check the docs for the current feature status.
Wrapping up
Browser-based RDP through Cloudflare removed the VPN client, the inbound exposure, and the two-system user management for this organisation, in exchange for one new dependency and some per-server preparation. The one setting that blocked us, SecurityLayer = 0, is now documented as a prerequisite, so check it first on any server you plan to onboard.
If you're evaluating this for a public sector or regulated environment and want to talk through the design, you can get in touch: [Your name, company, and contact link].
