Community Pick: Many members of our community have endorsed this article.

Remote Desktop Services (RDS): Setup Guide & Best Practices

Philip ElderSenior Technical Architect - HA/Compute/Storage
Philip is a Senior Technical Architect specializing in high availability solutions for SMB/SME businesses and hosting companies.
Remote Desktop Services setup guide for physical and/or virtual deployment. We've been building RDS environments in both all-in-one and TS/.RD Farm mode on Terminal Services and then Remote Desktop Services with RD Gateway in Server 2008. What follows are some of the key takeaways. Enjoy!


This article stems from a question on this forum. So, the following is something to keep in mind:
 * User Count: 10
 * On-Premises: Thin Client Requested
 * User Usage: Browsing, Zoom, Office Apps

Thin Client

I suggest an Intel NUC with i3 processor, Windows 10 Pro for an OS, and the appropriate Group Policy structure to manage them down to thin client status.

The cost will be as good or better than a dedicated thin client with all of the Windows goodness for driver compatibility and overall management experience.

MAC and Windows Modern RDC

It is important to note that when the MAC or Modern Remote Desktop Client (RDC) is in use that port UDP 3391 needs to be allowed inbound via the WAN to the RD Gateway and that there is a firewall exception in place on the Session Hosts.

Also, port UDP 3391 gives a performance boost to the RDP stream for all other RD Clients. It's recommended to use it.

RDS Environment

Browsers are killers in an RDS Session Host shared environment. Microsoft's CrEdge is probably one of the better ones for management and user resource usage.

Bandwidth and video quality will be an issue. The Internet pipe needs to be a consideration here even with 10 users.

RD Physical Server or Virtualization Host

We virtualize. We don't do standalone and have not done so in years, though as a caveat we don't work with high transaction SQL where bare metal is preferred.

You could do an all in one RDS Role setup:
 * Broker, Gateway, Web, and Session Host

While this may seem like a good idea, it's not best practice to do so.

Plus, if something hangs that requires a reboot you lose your RD Gateway for a minimum of reboot times (physical hosts BIOS post times are huge in today's servers so keep this in mind if going physical), plus the delay before the RD Gateway service is started.

We set up two virtual machines as follows:
 * RD Broker, Gateway, Web
 * RD Session Host(s)

Either way, a single Collection is the place to start with your Session Host(s).

Update IIS for /RDWeb by Default

By default if a user goes to they will hit the IIS logo and that's it.

Use the following code:
<?xml version="1.0" encoding="UTF-8"?>
        <httpRedirect enabled="true" destination="/RDWeb/Pages" exactDestination="false" childOnly="true" httpResponseStatus="Permanent" />
Create a Web.Config file and drop it into the default C:\inetpub\wwwroot folder:
Once that's done, users will automatically be redirected to the RDWeb Logon Page.

SETUP: Session Host Registry Entry for Firewall Profiles

Make sure to set the following registry entry on all Session Hosts in the RD Farm or on the All-in-One server:
Addresses an issue that slows server performance or causes the server to stop responding because of numerous Windows firewall rules. To enable this solution, use regedit to modify the following and set it to 1:

Type: “DeleteUserAppContainersOnLogoff” (DWORD)

Path: HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\SharedAccess\Parameters\FirewallPolicy
The above registry entry will make sure there are no duplicates for Modern Apps firewall rules which is a bug that seems to live on even in Windows Server 2022 as of this writing (2023-02-21).

If the Start Menu and Taskbar are no longer responding, along with Search, then the following script will clean it up:
$profiles = get-wmiobject -class win32_userprofile

Write-Host "Getting Firewall Rules"

# deleting rules with no owner would be disastrous
$Rules = Get-NetFirewallRule -All | 
  Where-Object {$profiles.sid -notcontains $_.owner -and $_.owner }

Write-Host "Getting Firewall Rules from ConfigurableServiceStore Store"

$rules2 = Get-NetFirewallRule -All -PolicyStore ConfigurableServiceStore | 
  Where-Object { $profiles.sid -notcontains $_.owner -and $_.owner }

$total = $rules.count + $rules2.count
Write-Host "Deleting" $total "Firewall Rules:" -ForegroundColor Green

$result = measure-command {

  # tracking
  $start = Get-Date; $i = 0.0 ; 
  # $total = $rules.Count

  foreach($rule in $rules){

    # action
    remove-itemproperty -path "HKLM:\System\CurrentControlSet\Services\SharedAccess\Parameters\FirewallPolicy\FirewallRules" -name $

    # progress
    $i = $i + 1.0
    $prct = $i / $total * 100.0
    $elapsed = (Get-Date) - $start; 
    $totaltime = ($elapsed.TotalSeconds) / ($prct / 100.0)
    $remain = $totaltime - $elapsed.TotalSeconds
    $eta = (Get-Date).AddSeconds($remain)

    # display
    $prctnice = [math]::round($prct,2) 
    $elapsednice = $([string]::Format("{0:d2}:{1:d2}:{2:d2}", $elapsed.hours, $elapsed.minutes, $elapsed.seconds))
    $speed = $i/$elapsed.totalminutes
    $speednice = [math]::round($speed,2) 
    Write-Progress -Activity "Deleting rules ETA $eta elapsed $elapsednice loops/min $speednice" -Status "$prctnice" -PercentComplete $prct -secondsremaining $remain

  # tracking
  # $start = Get-Date; $i = 0 ; $total = $rules2.Count

  foreach($rule2 in $rules2) {

    # action  
    remove-itemproperty -path "HKLM:\System\CurrentControlSet\Services\SharedAccess\Parameters\FirewallPolicy\RestrictedServices\Configurable\System" -name $

    # progress
    $i = $i + 1.0
    $prct = $i / $total * 100.0
    $elapse = (Get-Date) - $start; 
    $totaltime = ($elapsed.TotalSeconds) / ($prct / 100.0)
    $remain = $totaltime - $elapsed.TotalSeconds
    $eta = (Get-Date).AddSeconds($remain)

    # display
    $prctnice = [math]::round($prct,2) 
    $elapsednice = $([string]::Format("{0:d2}:{1:d2}:{2:d2}", $elapsed.hours, $elapsed.minutes, $elapsed.seconds))
    $speed = $i/$elapsed.totalminutes
    $speednice = [math]::round($speed,2) 
    Write-Progress -Activity "Deleting rules2 ETA $eta elapsed $elapsednice loops/min $speednice" -Status "$prctnice" -PercentComplete $prct -secondsremaining $remain

$end = get-date
write-host end $end 
write-host eta $eta

write-host $result.minutes min $result.seconds sec
Original script is here.

RD Single Sign-On and RSS

Another benefit to using the Intel NUC is the ability to set up RD Single Sign-On and publish the RemoteApp RSS Feeds in Active Directory via Group Policy. This makes the entire user experience while in the office seamless.

The RSS Feed is also available via Internet and is device agnostic though using RemoteApps on iDevices can sometimes "feel" like mousing through Jell-O.


Do _ not_ publish any port to the Internet for an RD Listener. Period. Full-Stop. None. Nada. Never!

Remote Desktop Gateway is the only way to properly, and securely, publish a Remote Desktop Services setup.

Third party DUO is an excellent way to secure access via multi-factor authentication. There are others out there, but DUO is our preference.

Resource Setup

For resources, considering the various environments we support:
 * A minimum of 8 vCores (8 pCores if physical)
 * A minimum of 2GB vRAM/pRAM per user
 * A minimum of 2GB vRAM/pRAM for the Session Host OS
 ** Suggested: 16GB minimum whether physical or virtual
 * A smallish SATA SSD RAID 1 should more than satisfy storage, IOPS, and throughput needs in that environment.
 ** Intel SSD DC-S4610 (D3-4610 new name) in 1.9TB or larger if needed
 ** Host OS can go down on 128GB Intel SSD RAID 1

With the above in mind, we would actually start off with four virtual processors (vCPUs) and 4GB of virtual RAM assigned to the RD Session Host(s). We would then tune both settings to the actual user usage patterns over a week or two.

Virtualzed Session Host NOTE: Assigning more virtual CPUs/RAM does not necessarily translate to increased Session Host performance!

Virtualizing leaves the option to add more virtual servers if needed now or in the future.

For example, if we need to set up a dedicated Collection for a resource hungry app that kills user's Session Host Desktop experience, we can set up a new virtual machine, prep it with the needed app, and publish it at the Broker then back to the required users via Group Policy.

Disk latency is a key metric to user experience. The following is an overview chart as far as what happens as disk latency goes up:
 * 0ms to 25ms: Awesome user experience
 * 26ms to 35ms: Great user experience
 * 36ms to 50ms: User experience will lose parity with local machine experience. Some possible complaints.
 * 50ms to 75ms: User experience is definitely impacted. Expect complaints/tickets.
 * 76ms+: Time to investigate where the bottleneck is.
Resource Monitor Disk Tab: Sort Response Time

In a virtual setting, keeping an eye on CPU consumption is important. Disk latency may be well below the 25ms mark, but having four virtual CPUs (vCPUs) saturated at most points throughout the day will bring about user complaints/tickets.

RD Session Host tuning is a very experiential thing. Over time, folks managing RD standalone and farm environments will pick-up on what apps cause the most grief and what virtual machine setup will bring about the best user experience.

RD Farms

In a small setting breaking up the RD Roles is a good idea. In fact, as already mentioned, it is a best practice to keep them separate.
 * RD Broker, Gateway, and Web (with NPS)
 * Session Host 1, 2, 3, ETC
 ** Custom Collection configuration breaking up Session Host(s) to apps

Some benefits to configuring in RD Farm mode:
 * Reboot the Session Host without losing RD Gateway
  ** Leaves user's connections to other on-premises endpoints intact
  ** Allows users to reconnect to the Session Host that much quicker due to delayed start of RD Gateway
 * We can scale out by adding more Session Hosts as user count goes up
  ** Never underestimate the impact that the setup will have on management
  ** Once folks see for themselves the benefits of an RDS setup they start asking for more.
 * We can contain resource hog apps in their own Collection/Session Host(s)
  ** Helps to keep user experience up
 * We can tune our virtual machine setup to the actual user environment in play

RDS Licensing

And finally, the elephant in the room. ;)

Remote Desktop Services CALs come in User and Device flavours. Device CALs are great for shift work environments where two or three, or more, users may use the same terminal to access RDS resources.

An RD License Server can be set up either on a dedicated virtual machine, or with the Broker/Gateway/Web VM, or on a domain controller which is not our preference. We generally place it on the Broker.

RD High Availability

In our larger RD Farm settings we tend to be deploying on a Hyper-V cluster. When we do, we set up node affinity for the RD Session Hosts in the farm so that a host failure does not impact all users. This requires a bit of planning as far as Session Host allocation as user's sessions will try and reconnect to the now reduced Session Host count in the Collection.

If the host the Broker/Gateway/Web is running on goes down, the VM will spool up on a different cluster host and be back online within a few minutes keeping in mind that the RD Gateway service has a bit of a delay before it kicks in.

Of note, we have not had the requirement to deploy a highly available RD Farm where each component in the farm itself is highly available. This requires a lot of additional server resources that are beyond the scope of this article.

User Profile Disks

User Profile Disks, or UPDs for short, are preferrably network share located differencing virtual hard disks. They use a template .VHDX as the parent that gets created when the Session Collection is configured.

Their primary use is to allow a user to float across two or more Session Hosts and a Remote Desktop Services Farm situation. We, on the other hand, always deploy them. They make working with a user's local profile that much easier.

  • TIP: Freebie utility SIDDER can be used to see SID to UPD mapping when troubleshooting.

By default, the setup wizard chooses 20GB for the size of the template and all subsequent UPDs that get created when a new user logs on.

  • TIP: If the average user's profile is 10GB while the largest is 15GB then choose 30GB or more for the default. Once the TEMPLATE.VHDX file is created, mount it, and shrink the PARTITION down to a starting place for everyone. Say, 5GB. Then, if someone hits that limit, log them off, mount the .AVHDX file, bump the partition size up, dismount, and done.
We always edit the default UPD template .VHDX file to decrease the size of the partition as a starting place because once the initial size is chosen it can never be changed.

That means editing all subsequent UPDs to increase their size manually or via PowerShell to do so.

With Microsoft's purchase of FSLogix one can now deploy their products if one has Remote Desktop CALs/SALs in place. Working with FSLogix is on the To Do List for us, but for now the native UPDs have been working quite well across many a RD Farm.

Group Policy Setup - UserVille Update Prompts
Make sure Active Directory is set up with the Central Store (my EE Article) and the PolicyDefinitions folder is up to date.

Then, have a GPO that is User Only set up and linked with the Standard Users OU with DENY/DISABLED for Windows Updates. If this is not set properly, the pop-up will prompt a user, or users, to click GO on updates but they won't be able to reboot the Session Host because they are not admins on that Session Host.

Then, over a bit of time in a half-baked situation the Session Host will start hiccupping in strange ways. We've seen them accept incoming requests to a black screen and just sit there. When we'd flip the incoming setting to OFF on the Broker the Session Host in question would not be able to be set back to ON until a reboot is done.

Installing Updates % O
That's what we'd see when we reboot the Session Host in question. Then, once rebooted RDS won't set it back to ON because now the Broker/Gateway/Web and the other Session Hosts are not on the same patch level. Lots of fun!

Make sure all members of an RDS Farm are updated to the same patch level prior to allowing users to log back on!
Philip ElderSenior Technical Architect - HA/Compute/Storage
Philip is a Senior Technical Architect specializing in high availability solutions for SMB/SME businesses and hosting companies.

Comments (0)

Have a question about something in this article? You can receive help directly from the article author. Sign up for a free trial to get started.