Using Let's Encrypt to secure Windows Remote Desktop connections

The Tricky Bit

If you are using Remote Desktop Gateway use instructions here: Select an Existing Certificate for Remote Desktop Gateway | Microsoft Learn

If you want this for a standalone server (my case) then use instructions here: Remote Desktop listener certificate configurations - Windows Server | Microsoft Learn

I used the command below:

wmic /namespace:\root\cimv2\TerminalServices PATH Win32_TSGeneralSetting Set SSLCertificateSHA1Hash="FINGERPRINT"

Acquiring the fingerprint:

I used notepad to clean up the fingerprint (not spaces)

Telling Remote Desktop To Use the Cert:

No restart needed.

Confirm cert is being used

This can be hard as it works with domain name, So i login using IP to verify the cert


this is a fairly manual process but it works

it’s up to you if you use certbot on windows (read my other article on how to get this working) or one of the other clients such as ACME Sharp or

you can write the remote desktop update as a hook after the cert is installed.

you will also need to use powerhsell to install the certificate:

note: our internal domain is a internet based name so we can do DNS challenges etc

The flow would look something like

Obtain Certificate
hook code:

  1. Obtain Certificate Thumbprint
  2. Convert to PFX
  3. Import PFX to Store
  4. Associate Certificate with Remote Desktop



Thanks for all these details, @ahaw021! They should be super-useful and I’ll try to remember this thread in case people ask about this in the future.

For removing the spaces from the fingerprint, do you have an equivalent of tr in the Windows command line, maybe with Powershell? In Unix you could use tr -d ' ' to remove spaces from a string, without having to do it by hand in a text editor.

I would use the replace powerhsell command.

there is a trim command in powershell but it removes leading and tailing spaces so not what we are after in this case :smiley:

i usually do things "manually" first and then write the scripts


$SHATHUMB = " 90 JF EF 83 DF"
$SHATHUM = $SHATHUMB.replace(" ","")

will write this as a script later and integrate it with my certbot run

i believe for windows it will be a mix of python and powershell for a while as powershell has really good commandlets for doing windows related functions.


1 Like

Andrei, your posts are awesome! I’m glad you shared your knowledge here.

Also, I didn’t know about, it’s a great starting point for experimentation!
I bookmarked this conversation so that I can try to accomplish the same on my own, later on when I’ll set this up.

As far I understand it, this should be completely automatable through PowerShell and Python, right?
For example, at renewal you would run certbot, with a hook for DNS validation, and once we have the certificate then obtain the thumbprint and store it in a PowerShell variable (with space removal), convert the certificate to PFX and import it. This last step is still not clear to me: in your screenshot you used the key store GUI, can you use PowerShell to import the certificate, or update it on renewal? On Windows does it work and makes sense to use something such as symlinks like on Unix OSes?

Thank you so much.

You mean this?

Bookmarked too.

yes that’s the one article

this is where i am currently having challenges with windows

i am trying to figure out how to put the RSA key in to the store. There is no point having a cert without a key.

PFX allows us to do this.

There is the certutil.exe utility which will generate a CSR and create a key in the right store (you then fulfill the challenge and import the cert only)

Windows IIS cannot make use of certs on file stores they have to be in the cert store

I am working my way through the powershell side also going to log a request with the boulder team to make PFX an option as being able to download a PFX file from letsencrypt will simplify things for windows users


in the mean time if openssl is on the system the openssl syntax can be scripted

but i would like to do as much as i can with windows native tools :smiley:


there is also a library called iis_bridge which I am quite impressed with.

so tossing up how to create an IIS installer plugin at the moment :smiley:

whether to use hooks or the plugin system

sorry for the ramblings :smiley:


No, that’s all good to know! And I agree that less external dependencies the better.

Are you going to publish your scripts in a nice configurable way?

I’ll give you the manual steps that I take. I only have a couple of client stations that it is needed on, so I have not taken the time to automate the process. Besides, this is the best way to see what is actually happening.

  • Even though we have a valid LetsEncrypt certificate in the server’s certificate store [Remote Desktop]-[Certificates], RDP clients still see a “The identity of the remote computer cannot be verified” message when trying to connect.
  • We need to digitally sign the RDP files on the client machines with an SSL certificate to get rid of the message.
  • First - Copy the Let’s Encrypt certificate that you want to use from [Personal]-[Certificates] and Paste it into the certificate store under [Remote Desktop]-[Certificates].
  • Export that LetsEncrypt certificate on the server from the server’s certificate store under [Remote Desktop]-[Certificates].
  • You MUST choose to include the cert’s Private Key when exporting.
  • Copy the exported PFX file from the server to the client machines that have RDP files.
  • On the client machine, import the server’s PFX format certificate into the client’s Personal certificate store.
  • Now, we can use rdpsign.exe to sign the client’s RDP file that connects to the server.
  • Go into the client’s certificate store to [Personal]-[Certificates].
  • Open the imported cert and go the [Details] tab of the [Certificate Information] dialog.
  • Choose [All Fields] from the first dropdown list.
  • Find the value of the [Signature Hash Algorithm] … (probably SHA256, or maybe SHA1 on older certs).
  • Find the value of the [Thumbprint] field. This will be a series of two byte characters that are separated by spaces.
  • Copy the Thumbprint value and edit all of the white-space out of it, so that it is a solid string of characters. There may be an odd character at the front of the string - if so, that will need to be deleted. You might not be able to see this character, so it is best to place the cursor at the front of the string and just hit delete until the first real character of the thumbprint string is deleted - and then retype that character at the beginning of the string.
  • Launch a Command Prompt (with admin privileges).
  • Run this command with YOUR OWN values:
    rdpsign.exe /sha256 f428629df8fdaefc701ee0335e956edc9844b0aa “C:\Users\bubba\Desktop\MyServerConnection.rdp”
  • If rdpsign completes successfully, you will see the message “All rdp file(s) have been succesfully signed.”
  • Now, when that signed client is used to connect to its server the first time; the user will see the server name, etc.; and they will have to click on [Ok] the first time.
  • After that, future RDP connections will connect with no messages.


And you need to sign the clients again each time that the certificate is renewed?

Yes. Since you will have a new Let’s Encrypt certificate when it is set to renew, you will need to copy the new cert to the RDP cert location in the server’s certificate store - and then perform the export and the rest of the steps each time.

Hmmm @mcdado… I never thought about it before, but you may be able to perform the RDP client script signing on a single client workstation - import the new cert on the rest of your client workstations - and copy the signed RDP script to those other client workstations. Not sure, but if it works it could save a tedious step.


I bet a PowerShell expert can automate most of this process. I understand that Microsoft has exposed a significant amount of Windows configuration to PowerShell in one way or another. So I expect there could be a script made that takes care of most of these steps for you.

This limitation throws me off somehow… for me automating the server is one thing, automating n clients is another. Plus is not a situation where you enter the host and credentials on the fly and you can access securely, you’d need to have a signed .rdp file that expires whenever there’s a renew.
Also, I’d need to figure out how to sign macOS clients.

Thanks so much for the feedback @CBruce , soon I’ll be able to experiment myself when I get the local domain and DNS going.

@mcdado, since you are setting up a Windows Domain and Active Directory - you might want to look at using AD Group Policies to automate the distribution of the certificate to the client workstations:

Deploy Client Computer Certificates:

And IF a single signed RDP client will work when copied to other client workstations - you can also use Active Directory to deploy that signed RDP client file to the other workstations:

Using Group Policy Preferences for copying files:

Group policy preferences copy files:


1 Like

following on from what has been discussed

A) If you are going to use letsencrypt it is definitely a good idea to have the letsencrypt intermediate certificates on all servers and workstations in the domain (so it is trusted)
B) Deploying signed RDP files is also a good idea
C) Usually for these kinds of problem I would be running and internal ca (which AD will automatically distribute certificates for) however the requirement was for letsencryt with RDP
D) From my testing RDP seems to include the Intermediate certificate (i deleted the LetsEncrypt intermediate form the intermediate store)


Hi mcdado,

I just put together a script to automate the issuing of server certificates for RDP listeners based on Marc Durdins script for Windows Web Servers. Just take a look at…automatisieren. The text is in German but you can just take the script This is for example a PowerShell snippet to bind a certificate to the RDP listener:

Bind the certificate to the RDP listener

Write-Output “Bind certificate with Thumbprint $certThumbprint”
$wmipath = (Get-WmiObject -class “Win32_TSGeneralSetting” -Namespace root\cimv2\terminalservices -Filter “TerminalName=‘RDP-tcp’”).__path
Set-WmiInstance -Path $wmipath -argument @{SSLCertificateSHA1Hash=$certThumbprint}

Have fun!


Thanks @Daniel_MSFT

I briefly looked through the links you posted and it looks very interesting. If I understand this correctly, the ACMESharp is able to generate dns-01 challenges, but you’d need either to update your zone manually or using APIs from your DNS provider, is that right? (unless you’re running a public-facing DNS of course, which we’re not).

In this case, it would be interesting for me to implement the connector for my DNS provider in PowerShell, so that I could set it up on a local server in my local network to automatically perform renews based on the public facing DNS.

Thanks for your input!

This is really good work :smiley:

would you mind if i modified it slightly and add it to the list of clients

there is a lot of confusion around windows and your powershell script should clear some of this up

I am going to use this for some of my projects as well

Thanks for sharing.


1 Like