UPDATE: Due to the positive responses I’ve received about this script, and repeated requests for Let’s Encrypt support, it now supports standard and Let’s Encrypt SSL certificates. Read on for details.
The Ubiquiti UniFi Controller comes with its own self-signed SSL certificate, which means that every time you try to access the web interface, your browser complains about the security of the self-signed certificate, then makes you jump through hoops to get there (I’m looking at you, Google Chrome). I run a hosted version of the UBNT UniFi Controller on a CentOS Linux web server that already has its own a valid SSL certificate. And because the UniFi Controller’s GUI runs on a different port than the standard HTTPS connection on that web server, I can use the same existing SSL certificate for that hostname for my UniFi Controller. This post will show you a quick and easy way to use your existing SSL certificate (including a Let’s Encrypt SSL certificate) on your Linux-based UniFi Controller using my unifi_ssl_import.sh script.
In order to use an existing SSL certificate with a UniFi Controller, you need the following:
- A valid SSL certificate (.crt)
- The private file (.key) generated with the SSL certificate’s signing request
- The complete certificate authority chain from your signer (usually a .pem or .crt file). This is the part that normally causes the most frustration when trying to import an SSL into a UniFi Controller, but I’ll explain and walk you through it below.
If you Don’t Already Have an SSL Certificate
If you don’t already have a valid SSL certificate… go get one. There are plenty of free options, so there’s no excuse not to have one. In fact, I’ve written this easy-to-follow guide that walks you through all the steps necessary to generate the the private key and download a free one-year SSL certificate from StartSSL to use with your UniFi Controller (renewals are free, too). Another popular option is Let’s Encrypt, which I’ll address separately below.
Using Let’s Encrypt SSL Certificates with UniFi Controller
A recent popular option for free SSL certificates is Let’s Encrypt. Compared with traditional SSL certificates, they are easier to retrieve, but they expire every 90 days — meaning you’ll need to renew them before they expire. Let’s Encrypt recommends using the certbot client (and so do I) to automate the renewal process. You can run a twice-daily certbot cron job that checks the expiration date on your SSL certificate, and if it expires within 30 days, it will renew it automatically.
I should inform you that there are scripts out there that combine the functions of checking, renewing, and importing a Let’s Encrypt SSL certificate into the UniFi Controller — but I don’t like any of them, which is why I added Let’s Encrypt support to my dedicated UniFi SSL import script. The “all-in-one” scripts are never as flexible, and they usually force you into a single method of certificate renewal, which may not work for your server. I wrote my script to do only one thing, and do it well. Because the recommended certbot client is so easy to configure and use, I recommend setting up two cron jobs that run twice per day: one that uses certbot (or any of the the other Let’s Encrypt clients) to check for and download a renewed Let’s Encrypt certificate when needed, and a separate cron job that runs my unifi_ssl_import.sh script. The script is lightweight and smart enough not to do anything unless the downloaded Let’s Encrypt SSL certificate is newer than the one currently in the UniFi Controller.
The UniFi Controller Keystore File
SSL certificate information for the UniFi Controller’s web GUI is stored in a file named keystore. On Ubuntu and other Debian-based systems, the file is located in /var/lib/unifi/data/keystore. On CentOS, Fedora, and other non-Debian systems, it’s stored in /opt/UniFi/data/keystore.
The script’s method for using your own SSL certificate with a UniFi Controller involves modifying the keystore file, so it’s extremely important to make a backup of your original keystore before doing anything. My method doesn’t touch any other files associated with your UniFi Controller’s installation, so if your UniFi Controller stops working after using my script, you can simply restore your backup keystore file to its original location, restart the UniFi Controller, and you’ll be back online.
What the unifi_ssl_import.sh Script Does
The script is heavily commented, so you can read through it to see exactly what it does. But in basic terms, the script:
- Makes sure it has everything it needs to attempt an import.
- Backs up your keystore file.
- Uses the openssl command to export data from your private key, SSL certificate, and SSL certificate chain to a temporary PKCS12 file, along with a password that the UniFi Controller is expecting.
- Uses the keytool command to import the temporary PKCS12 file into the keystore file.
- Uses the UniFi Controller’s java-based import_cert function to update the controller’s SSL certificate chain.
- Restarts the UniFi Controller to apply the changes.
Building an SSL Certificate Chain
An SSL certificate chain is a list of certificates that ensures a trusted relationship all the way from the “root” certificate of the signing authority, through any “intermediate” certificates from other signing authorities, and eventually to the “end user” certificate on a web server. To see how they connect, take a look at Ubiquiti’s certificate chain from ubnt.com:
The root certificate (there’s always only one) belongs to Starfield Services. There are two intermediate certificates belong to Amazon (Amazon Root CA 1 and Amazon), and the client certificate (also always only one) belongs to ubnt.com. All four certificates make up the SSL certificate chain — indicating that Amazon trusts ubnt.com (client), Amazon trusts Amazon (intermediate), Starfield trusts Amazon (intermediate), and Starfield is a a root level authority that deserves to be trusted by everybody.
The UniFi Controller won’t let you connect to the GUI unless a chain of trust can be validated all the way to the root certificate. To ensure that the UniFi Controller can display the “green lock” when you connect to it, it needs to know about and trust every certificate in the certificate chain. The easiest way to ensure that is to download a copy each of the certificate authority files (also referred to as CA files) in the chain. Don’t worry, they’re easy to get. And once you have them, you can easily combine them into a single chain file, which you can then import into the UniFi Controller along with your client certificate, so it knows about the entire chain.
If you’re using Let’s Encrypt, the good news is that it automatically downloads a chain file named chain.pem file into the same directory as your private key and signed certificate. The bad news is that because Let’s Encrypt is relatively new, one of the certificates referenced in that chain.pem is cross-signed by an IdenTrust root (this IdenTrust root, to be exact) so that IdentTrust cross-signed certificate needs to be downloaded and imported separately into the UniFi Controller’s keystore. But the other good news is that the unifi_ssh_import.sh script handles all that for you, so Let’s Encrypt users don’t need to worry about chain files at all.
If you’re using a SSL certificate from another provider like StartSSL, you can build your own certificate chain by downloading and combining the root and intermediate CA files together into a single chain file that the unifi_ssh_import.sh script can import. All certificate authorities are required to make their certificates publicly available, so no matter which signing authority you use, their CA files are easy to find with a web search. StartSSL’s CA files are are located here. If you followed the instructions in my Create a Free SSL Certificate with StartSSL article, then you’ll have a root_bundle.crt file located in your server’s certificates folder (we’ll assume it’s /etc/ssl/certs) which you unzipped with your signed certificate. Combine that root_bundle.crt CA file with StartSSL’s Root 1 CA file and you’ll have the whole chain. On your server, do:
# cd /etc/ssl/certs # wget https://www.startssl.com/certs/ca.crt
That will download the Root 1 CA file into your certificates folder. Now combine it with your root_bundle.crt CA file into a chain file with:
# cat ca.crt root_bundle.crt > startssl-chain.crt
Use the full path to that startssl-chain.crt file as the CHAIN_FILE configuration option in the unifi_ssh_import.sh script, and you’ll be all set!
Using the unifi_ssh_import.sh Script
First, download my unifi_ssl_import.sh script to your Linux box (I recommend putting it in /usr/local/bin) and make it executable with chmod a + x. You’ll need to edit few configuration options before you run it for the first time:
- UNIFI_HOSTNAME: the full name of the server where your UniFi controller runs, such as unifi.example.com
- UNIFI_DIR: on Fedora, CentOS, and other RedHat systems, this will be /opt/UniFi. On Ubuntu and other Debian-based systems, this should be /usr/lib/unifi. Both options are in the file, so just uncomment the one you need and comment the other one out.
- UNIFI_SERVICE_NAME: the name of the sysvinit or systemd service that runs the controller. It’s the word you type between “service” and “start” or “stop” from your command line to start and stop the controller service. If you’re using one of my UniFi Controller Linux start-up scripts (try one… you might like it!), this should be UniFi. If you’re using someone else’s script (sniff, sniff), it might be unifi.
- LE_MODE: If you’re using Let’s Encrypt, then set this to yes, which will user the UNIFI_HOSTNAME to automatically locate the certs.pem, privkey.pem, and the script find all the right file paths and automatically build a complete chain file that the UniFi controller won’t complain about.
- LE_LIVE_DIR: This is set to the Let’s Encrypt default of /etc/letsencrypt/live, so you probably won’t need to change it, but you can if you need to.
- PRIV_KEY: the full path to your private key (default is /etc/ssl/private/hostname.example.com.key). Set this to the correct name and location of your key. This option is ignored if LE_MODE is enabled.
- SIGNED_CRT: the full path to your signed SSL certificate (default is /etc/ssl/certs/hostname.example.com.crt). Set this to the correct name and location of your cert. This option is ignored if LE_MODE is enabled.
- CHAIN_FILE: the full path to the CA chain file you created above (default is /etc/ssl/certs/startssl-chain.crt). Set this to the correct name and location of your chain file. This option is ignored if LE_MODE is enabled.
There are a few other configuration options such as ALIAS, KEYSTORE, and PASSWORD, but I don’t recommend changing them, as these are set to what the UniFi Controller is expecting, and changing them will almost certainly cause something to barf.
If you’ve already got a Let’s Encrypt certificate on your server, simply set the UNIFI_HOSTNAME, verify the correct UNIFI_DIR for your system, set LE_MODE to yes, and run the script.
For any type of user, if the script is unable to find any of the required input files, it will warn you and exit. Fix the problem and run it again.
The first time the script runs, it will backup your original keystore file as keystore.orig. If you’ve never modified this file, it still contains the UBNT self-signed certificate. If you run the script a second time (or multiple times, as with a cron job) it recognizes the existence of keystore.orig, assumes that means the current keystore file has been modified, and backs up the modified keystore file as keystore.bak (instead of overwriting the original backup). That way, if things ever get really hosed, you can copy keystore.orig to keystore, restart the controller, and regain access to the GUI.
If you’re using a standard SSL certificate provider like StartSSL, GoDaddy, Comodo, DigiCert, Verisign, etc., you’ll only need to run unifi_ssl_import.sh every 1-6 years, depending on when your certificate expires and you have to install a new one. So it’s probably pointless to set it up as a cron job. 🙂
If you’re using Let’s Encrypt, however, your certificate will expire every 90 days. Let’s Encrypt recommends running a twice-daily certbot cron job to check the status of your certificate (which can also renew it when necessary). I recommend that Let’s Encrypt users run a cron job that also executes unifi_ssl_import.sh twice daily, about 2-3 minutes after the certbot job. If the certificate is new, it will get automatically imported into the UniFi Controller. If not, the script will exit quietly.
The script is unifi_ssl_import.sh, and is part of my UniFi Linux Utils GitHub repo here:
Once you run the script, give the controller a few moments to restart (don’t panic if you can’t connect for up to 30 seconds), then open the URL of your UniFi Controller in a web browser (UBNT currently seems to favor Chrome). If the warnings don’t appear and you see a happy green https://, you’ve successfully imported a signed SSL certificate into your UniFi Controller!
Hopefully, this article and script will help you make short work of the often frustrating task of using a signed SSL certificate with the Ubiquiti UniFi Controller. Your comments, suggestions, and feedback are welcome in the comments below, but I don’t offer technical support on my blog. If you’re having trouble with this script, please post in this thread in the UniFi Wireless section of the UBNT Forums:
I monitor that thread, and am happy to provide help there.