In-browser RDP with Cloudflare Tunnel — Complete practical setup on (Tested and working on windows 11)

This is a hands-on, step-by-step guide post you can use to publish a Windows host with in-browser RDP using Cloudflare Tunnel and Cloudflare Zero Trust Access. Read it once, then follow each step. I wrote this so you can copy, paste, edit small values, and run a single PowerShell script at the end to finish the setup on Windows.

Short summary

I had my domain registered with hostinger so  i moved my domain DNS management into Cloudflare, point your domain (update nameservers at Hostinger), create a Cloudflare Tunnel, install the tunnel agent on Windows, configure the tunnel to route a public hostname to an internal RDP host, create a Zero Trust Access app (RDP with browser rendering), and test from a browser.

Step 0 — Key information you should have

  • A domain name (example: yourdomain.com).
  • Cloudflare account with Zero Trust (Access) enabled.
  • Hostinger account where your domain currently has DNS (so you can change nameservers).
  • Windows machine where you will run cloudflared (Administrator access required).
  • Private IP of the RDP target (example: 192.168.1.100), and RDP enabled on that target.

Step 1 — Add your domain to Cloudflare and switch nameservers (I had my domain registerd on Hostinger)

  1. Log in to Cloudflare and add your domain (the dashboard will give you two Cloudflare nameservers).
  2. Log in to Hostinger, find your domain’s DNS / Nameservers section, and replace the current nameservers with the two Cloudflare nameservers Cloudflare gave you.
  3. Save changes. Wait for Cloudflare to accept the domain (it typically takes a few minutes to a few hours to propagate). You can check the Cloudflare dashboard until the domain shows as active on Cloudflare.
  4. Once active, Cloudflare manages DNS for your domain and you will perform public hostname mapping within the Cloudflare dashboard on this process, later. (no more changes needed at Hostinger for the hostname you will use).

Step 2 — Prepare Zero Trust and create a Tunnel

Do this in the Cloudflare dashboard under Zero Trust (sometimes called Cloudflare for Teams / Access → Tunnels). The UI may show options to create tunnels or generate a one-time service install token. Follow the UI to create the tunnel and either:

  • Generate a one-time service install token (recommended for unattended Windows service install), or
  • Create the tunnel and note the Tunnel UUID (if you prefer interactive CLI login later).

Also in Zero Trust you will create network routing entries (CIDR / routes) so Cloudflare knows which internal addresses the tunnel can reach:

  1. Under the Zero Trust / Network or Tunnels area, add the internal network routes (CIDR ranges) or specific IPs that the tunnel should be able to reach — for example your LAN range or the private IP of the RDP host (e.g., 192.168.1.0/24 or the single IP 192.168.1.100).
  2. Create any network policies required to allow the tunnel to access those CIDR ranges from the Cloudflare side (these options vary by UI but are usually grouped under “Network” or “Private networks”).

Step 3 — Create targets and allow admin users

  1. Still under Zero Trust, register the internal target(s) you will access (the RDP host IP). This tells Cloudflare where to forward inbound session traffic that arrives via the tunnel.
  2. Create a Zero Trust policy or Access rule that allows your admin account(s) to use the application. This is done in Cloudflare Access / Policies — add an “Allow” policy that specifies the Cloudflare user or group who can open the RDP app.

Step 4 — Configure a public hostname (Public Hostnames / DNS)

You will map a hostname that your users can visit in the browser to reach the tunnel and then the RDP target.

  1. Go to Zero Trust → Tunnels → your tunnel → Public hostnames (or Public Hostnames / DNS mapping area).
  2. Add a public hostname, for example rdp.yourdomain.com.
  3. Set the service to the internal RDP target: rdp://192.168.1.100:3389 (replace with your private IP and port if different).
  4. Save. Cloudflare will create the correct CNAME behind the scenes (it points the public hostname to [TUNNEL_UUID].cfargotunnel.com), or you can create that CNAME in Cloudflare DNS manually if needed.

Step 5 — Create an Access application (Browser RDP)

  1. In Zero Trust → Access → Applications, click Add application.
  2. Set Application domain to rdp.yourdomain.com.
  3. Choose Application type: Self-hosted and select RDP.
  4. Enable Browser rendering (this renders the RDP session in the browser).
  5. Create an Allow policy that includes your Cloudflare user (email) or a group that contains the admin accounts.
  6. Save the application.

Step 6 — Install cloudflared and run the tunnel on Windows

Below is the one-shot PowerShell script I use. Edit the top variables ($ServiceToken, $tunnelUUID, $hostname, $targetIP) to match your values, then copy the full code in one-go, paste it on powershell, hit enter.

$UseToken      = $true
$ServiceToken  = ''
$tunnelUUID    = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
$hostname      = 'rdp.yourdomain.com'
$targetIP      = '192.168.1.100'
$targetPort    = 3389

$exeDir     = 'C:\Program Files\cloudflared'
$exePath    = Join-Path $exeDir 'cloudflared.exe'
$sysCfgDir  = 'C:\Windows\System32\config\systemprofile\.cloudflared'
$configPath = Join-Path $sysCfgDir 'config.yml'
$credPath   = Join-Path $sysCfgDir ($tunnelUUID + '.json')

If (-not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltinRole]::Administrator)) {
    Write-Error 'Run PowerShell as Administrator'; Break
}

New-Item -ItemType Directory -Force -Path $exeDir | Out-Null
$downloadUrl = 'https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-windows-amd64.exe'
if (-not (Test-Path $exePath)) {
    try { Invoke-WebRequest -Uri $downloadUrl -OutFile $exePath -UseBasicParsing -ErrorAction Stop; Unblock-File $exePath -ErrorAction SilentlyContinue } catch { Write-Error "Download failed: $downloadUrl"; Break }
}
& "$exePath" --version

Get-WmiObject Win32_Service | Where-Object { $_.PathName -and ($_.PathName -match 'cloudflared') } | ForEach-Object {
    try { Stop-Service -Name $_.Name -Force -ErrorAction SilentlyContinue } catch {}
    sc.exe delete $_.Name | Out-Null
}
taskkill /IM cloudflared.exe /F 2>$null

New-Item -ItemType Directory -Force -Path $sysCfgDir | Out-Null

if ($UseToken) {
    if (-not $ServiceToken) { Write-Error 'Set $ServiceToken'; Break }
    & "$exePath" service install $ServiceToken
    Start-Sleep -Seconds 2
}

$possibleCreds = @("$env:USERPROFILE\.cloudflared\$tunnelUUID.json", "C:\ProgramData\cloudflared\$tunnelUUID.json", (Join-Path $exeDir ($tunnelUUID + '.json')))
$found = $possibleCreds | Where-Object { Test-Path $_ } | Select-Object -First 1
if ($found) { Copy-Item -Path $found -Destination $credPath -Force; Write-Host "Copied credentials to $credPath" } else { Write-Host "No credential file found; service may be running with token mode." }

$ingressService = "rdp://$targetIP`:$targetPort"
$yaml = @()
$yaml += "tunnel: $tunnelUUID"
$yaml += "credentials-file: $credPath"
$yaml += ""
$yaml += "ingress:"
$yaml += "  - hostname: $hostname"
$yaml += "    service: $ingressService"
$yaml += "  - service: http_status:404"
$yaml -join "`n" | Out-File -FilePath $configPath -Encoding UTF8 -Force

try { & "$exePath" tunnel route dns $tunnelUUID $hostname 2>$null; Write-Host "Tried CLI DNS mapping" } catch { Write-Warning "CLI DNS mapping failed; create CNAME: $tunnelUUID.cfargotunnel.com" }

try { reg add "HKLM\SYSTEM\CurrentControlSet\Control\Terminal Server" /v fDenyTSConnections /t REG_DWORD /d 0 /f | Out-Null; Enable-NetFirewallRule -DisplayGroup 'Remote Desktop' | Out-Null } catch {}

$svcObj = Get-WmiObject Win32_Service | Where-Object { $_.PathName -and ($_.PathName -match 'cloudflared') } | Select-Object -First 1
if ($svcObj) {
    try { Restart-Service -Name $svcObj.Name -Force; Start-Sleep -Seconds 3; Get-Service -Name $svcObj.Name | Select Name,Status,StartType } catch {}
}

Get-Content $configPath -ErrorAction SilentlyContinue | Select-Object -First 40 | ForEach-Object { Write-Host $_ }
& "$exePath" tunnel list 2>&1 | ForEach-Object { Write-Host $_ }

Step 7 — Map DNS (if CLI mapping didn’t work)

  1. Open Cloudflare DNS for your zone.
  2. Create a CNAME record:
    • Name: rdp (which gives rdp.yourdomain.com)
    • Target: <TUNNEL_UUID>.cfargotunnel.com
    • Proxy: DNS only (or keep proxied depending on your Access configuration).

Step 8 — Test from a browser

  1. Open https://rdp.yourdomain.com in a browser.
  2. Authenticate via Cloudflare Access (the policy you created will allow admin users).
  3. If everything is correct, the RDP session is rendered in the browser window.

Troubleshooting

  • If the service fails to start, stop the service and run the tunnel interactively to read logs:
    Stop-Service Cloudflared -Force
    & "C:\Program Files\cloudflared\cloudflared.exe" tunnel --config "C:\Windows\System32\config\systemprofile\.cloudflared\config.yml" run <TUNNEL_UUID>
  • If DNS mapping via CLI fails, create the CNAME in Cloudflare DNS as described above.
  • Ensure the cloudflared host can reach the RDP target on port 3389.
  • Confirm the tunnel status is Healthy in the Cloudflare dashboard before testing the browser connection.

Security notes

  • Limit Access application policies to specific admin accounts or groups only.
  • Turn on network level authentication (NLA) on the Windows RDP target and use strong passwords.
  • Keep your service tokens and credential files private. Rotate them if exposed.

Reference

Documentation: https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/use-cases/rdp/rdp-browser/

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top