Getting certificate for Windows UniFi Controller

Please fill out the fields below so we can help you better. Note: you must provide your domain name to get help. Domain names for issued certificates are all made public in Certificate Transparency logs (e.g. |, so withholding your domain name here does not increase secrecy, but only makes it harder for us to provide help.

My domain is:

I ran this command:
Java -jar lib\ace.jar new_cert "Hugo Enterprises, LLC" Omaha NE US
More unifi_certificate.csr.pem

It produced this output:

My web server is (include version):
Unknown. UniFi requires installation of Java (build 1.8.0_291-b10)

The operating system my web server runs on is (include version):
Windows Server 2019 Datacenter v1809 (OS build 17763.1971)

My hosting provider, if applicable, is:
DNS is through GoDaddy

I can login to a root shell on my machine (yes or no, or I don't know):
I can run an administrative command prompt or powershell session

I'm using a control panel to manage my site (no, or provide the name and version of the control panel):
I don't believe so, no.

The version of my client is (e.g. output of certbot --version or certbot-auto --version if you're using Certbot):
Haven't installed yet. I'm following the instructions from here:

In the video, he copies the output of a .pem file into another cert website and then get's emailed the cert back. Let's Encrypt doesn't appear to work that way. I see no place to begin this process.

Does anyone have experience with creating a cert for a UniFi Windows Controller?

1 Like

Let's Encrypt is designed to be used by machines, not humans, which makes it tricky sometimes to translate instructions that are designed for humans to follow. :slight_smile:

The basic idea of how it works is that you need some kind of software, called an ACME client, to request the certificate from Let's Encrypt. While you can, technically, find one that will use the CSR generated by your controller, manually complete one of the validation challenges, and manually install the certificate, you would need to repeat the manual process every couple months. There are people who just set a bunch of reminders up and do things that way, but it's certainly a pain, but doing things this way isn't really how Let's Encrypt is designed to be used. (It's not like it breaks any "rules" or anything, it's just that Let's Encrypt is designed to be set up for systems to be able to just automatically request their own certificates as needed.)

I did a quick search on the Unifi forums and found this post of somebody setting up win-acme with Unifi, though I haven't tried it and I'd certainly recommend checking through that batch file and making sure it's doing what you expect and you probably want to change the directory paths and passwords and such:

Note that regardless of how you do things, if you want to use Let's Encrypt then you need to either have the hostname's port 80 open on the firewall (so that it can complete the HTTP-01 challenge) or you need to update your DNS (ideally automated as well, to complete the DNS-01 challenge).


Thanks so much! Trying it out now..

1 Like

Getting an error with the wacs.exe tries to dial in.
[] Authorizing...

[ Authorizing using http-01 validation (SelfHosting)

[] Authorization result: invalid

[] {

"type": "urn:ietf:params:acme:error:connection",

"detail": "Fetching Timeout during connect (likely firewal problem)",

"status": 400


I have confirmed that port 80 is opened on the VM and forwarded, but I'm guessing the Java webserver isn't listening? Should I redirect incoming requests on port 80 of the public IP to port 443 of my VM?

I found this post which seems to address using 443:

But I don't think those are Windows clients.

Is there documentation on manually/automatically updating the DNS-01 challenge?

P.S. I also made sure to open the firewall on the Windows Server, specifically for port 80.

1 Like

Win-Acme's SelfHosting should be getting the request if I'm understanding your log right, which means that it should be the one listening on port 80. Though it's possible that unifi listens on 80 as well and is interfering, maybe? You need to ensure that all firewalls have that port open. (And if you're, on, like, a residential ISP, they sometimes block that port.)

That doesn't really make any sense. If your ISP blocked port 80, it's possible that you'd need to do the TLS-ALPN challenge, but even then you'd be staying on 443 the whole time, and you'd need to ensure it didn't interfere with Unifi running on the same port.

Well, a lot depends on your DNS provider and what kind of API they give you. Win-Acme has support for many built in. If your DNS provider doesn't have an API built in, you can delete the _acme-challenge. TXT record to something like acme-dns, which win-acme also looks to support.

1 Like

Well, I did leave my UniFi controller running when I launched win-acme. I'll try shutting it off now. My luck is I'll need to restart the win-acme application all over again to get things moving correctly. Thanks for the tips.

I'll let you know.

We're using GoDaddy, and it appears to support DNS-01 challenges and API, although I don't quite know how we'll automate that. It looks like win-acme allows you to choose DNS-01 challenge instead of HTTP-01 so that part seems straightforward. I'll look into acme-dns if restarting win-acme and rolling through the process doesn't work.

1 Like

If you're comfortable with a PowerShell solution, Posh-ACME has a native GoDaddy DNS plugin and can use the CSR you generated via lib/ace.jar.

I just tested this out on a demo machine with the latest UniFi Controller software I could find (6.2.25) on Windows Server 2019. There's an annoying amount of tweaking you have to do to the final cert/chain files in order for the lib/ace.jar's "import_cert" command to work including providing a copy of the root CA in addition to the chain files.

Grab your GoDaddy API key and secret from here if you don't have them already. Once you have Posh-ACME installed, run the following to create the basic certificate.

cd "$($env:USERPROFILE)\Ubiquiti UniFi"

# setup the parameters to use with the new cert function
$certParams = @{
    CsrPath = '.\data\unifi_certificate.csr.pem'
    AcceptTOS = $true
    Contact = ''  # your email
    Plugin = ‘GoDaddy’
    PluginArgs = @{ GDKey='xxxxx'; GDSecret='yyyyy' }  # your keys
    Verbose = $true

# request the cert
New-PACertificate @certParams

Assuming that worked, you should see a bunch of verbose output followed by the details of your new cert. Now we need to import it, the chain, and the root certs into the Java keystore used by the UniFi software. The fact that the UniFi software will only import the cert with the root CA included is not very common and this process is going to make some assumptions that will eventually break if/when the root ever changes.

cd "$($env:USERPROFILE)\Ubiquiti UniFi"

# create a reference to the cert
$cert = Get-PACertificate

# create flattened (no line breaks) copies of the cert/chain/root files because 
# for some reason the import process doesn't like the normal format
(Get-Content $cert.CertFile) -join '' | Out-File data\flat-cert.pem -Encoding ascii
(Get-Content $cert.ChainFile) -join '' | Out-File data\flat-chain.pem -Encoding ascii
((Invoke-RestMethod -split "`n") -join '' | Out-File data\flat-root.pem -Encoding ascii

# import everything into the keystore
java -jar lib/ace.jar import_cert data\flat-cert.pem data\flat-chain.pem data\flat-root.pem

If the controller is already running, you'll need to stop and restart it in order to pick up the changes.


Can you use the "alternate" chain, and then use the ISRG Root X1 root instead? I doubt you need old-Android support for connecting to internal network infrastructure (I hope?), and that root will be around longer than the soon-to-expire DST Root.

1 Like

You certainly could. You'd just add PreferredChain = 'ISRG Root X1' to the parameters for New-PACertificate and then change the link to download the root to

This would make it a much longer lived solution for sure. But anything hard coded like that URL which isn't part of the ACME protocol itself is inherently brittle.


OK, thanks. I'll give this a try tomorrow.

I'm guessing the $certParams is a PowerShell thing?

1 Like

Yeah, that’s just a fancy way of passing parameters to a function in PowerShell called splatting that coincidentally also makes it easier to read and understand in a forum post like this.


This was profoundly useful to me and our organization. Thanks so much for the time and effort in placing this information here.

I suppose my last question is, so we need to run this Powershell script once every 3 months (6 months?) to renew the Let's Encrypt's cert? Anything else necessary?

You're most welcome. There's some additional work to be done with regards to renewals. The standard Submit-Renewal stuff within Posh-ACME should work for getting an updated copy of the cert. But I didn't test whether the lib/ace.jar import_cert stuff will overwrite the old cert with the new cert.

If it overwrites without issue, the renewal process might look something like this:

if ($cert = Submit-Renewal) {

    cd "$($env:USERPROFILE)\Ubiquiti UniFi"

    # create flattened (no line breaks) copies of the cert/chain/root files because 
    # for some reason the import process doesn't like the normal format
    (Get-Content $cert.CertFile) -join '' | Out-File data\flat-cert.pem -Encoding ascii
    (Get-Content $cert.ChainFile) -join '' | Out-File data\flat-chain.pem -Encoding ascii
    ((Invoke-RestMethod -split "`n") -join '' | Out-File data\flat-root.pem -Encoding ascii

    # import everything into the keystore
    java -jar lib/ace.jar import_cert data\flat-cert.pem data\flat-chain.pem data\flat-root.pem

    # call whatever to restart the controller to pick up the changes

Throw that into a scheduled task that runs daily as the same user who ran the initial call to New-PACertificate. Ideally, set the trigger to not on be on a :00 hour mark and perhaps add a random delay of a few minutes. Then every 60 days or so you'll have a nice new cert.