Let’s Encrypt UniFi

How To Install a Let’s Encrypt SSL Certificate on UniFi

*** THIS GUIDE IS NOW OUTDATED – Check out the Definitive Guide to Hosted UniFi instead!

————– Or continue below…though it’s a total waste of time. ———-

Let’s Encrypt allows you to have a FREE signed SSL certificate on your UniFi Controller without having to spend any money.  When done correctly, the Let’s Encrypt certificate will continuously renew, and you will no longer have any security warnings in the browser bugging you about insecure HTTPS.

This article is based on my 15 Minute Hosted UniFi Controller setup, so start with that article, and then come back to this article when your UniFi Controller is up and running.  One additional step is that you need to create a DNS A Record that points to the IP address of your UniFi Controller.  Something like unifi.company.com.  That should be done first in order to ensure that DNS has time to propagate before you need to create the Let’s Encrypt certificate.

The first thing we have to do is to open up HTTP port 80 and HTTP port 443 so that Let’s Encrypt can renew itself.  If anyone browses directly to those services, they will get a connection refused response.

Log into your UniFi controller and run the following commands to allow those ports through the firewall:

sudo ufw allow 80/tcp

sudo ufw allow 443/tcp

Note that it would be better for security if you could lock these rules down to the FQDN’s that Let’s Encrypt requests are coming from (outbound1.letsencrypt.org and outbound2.letsencrypt.org), however it is not possible to use FQDN in iptables rules.  It *should* technically be possible to create a script that periodically checks the IP address resolution for those FQDN’s and updates the iptables rules with any changes to the IP addresses for Let’s Encrypt.  If anyone creates such a script, Contact Me and I’ll post an update!

Next, let’s install Let’s Encrypt:

sudo apt-get update

sudo apt-get install letsencrypt

Now we need to generate our certificates.

sudo letsencrypt certonly

This command will ask for your email and FQDN – it will also have you accept the terms of usage.  When complete, you should get a message that says ‘Congratulations!  Your certificate and chain….’  This means you successfully created the certificate.

A developer named Steve Jenkins create a really great script that automates the rest of the process, making it super easy.  So, thanks to Steve, and let’s download his script and modify a few settings.

sudo wget https://raw.githubusercontent.com/stevejenkins/unifi-linux-utils/master/unifi_ssl_import.sh -O /usr/local/bin/unifi_ssl_import.sh

sudo chmod +x /usr/local/bin/unifi_ssl_import.sh

Next, edit the /usr/local/bin/unifi_ssl_import.sh file that we imported:

sudo nano -w /usr/local/bin/unifi_ssl_import.sh

Find the line that says ‘UNIFI_HOSTNAME’ and change it to your own FQDN:

UNIFI_HOSTNAME=unifi.company.com

Next, since we are on a Ubuntu Digital Ocean droplet instead of AWS (which the script was based on), we need to comment out the AWS stuff and uncomment the Debian/Ubuntu stuff:

# Uncomment following three lines for Fedora/RedHat/CentOS
#UNIFI_DIR=/opt/UniFi
#JAVA_DIR=${UNIFI_DIR}
#KEYSTORE=${UNIFI_DIR}/data/keystore

# Uncomment following three lines for Debian/Ubuntu
UNIFI_DIR=/var/lib/unifi
JAVA_DIR=/usr/lib/unifi
KEYSTORE=${UNIFI_DIR}/keystore

Next, enable Lets Encrypt mode:

LE_MODE=yes
LE_LIVE_DIR=/etc/letsencrypt/live

Save and exit nano by doing CTRL+X followed by Y.

Finally, run the script!

sudo /usr/local/bin/unifi_ssl_import.sh

Now, this is great, and if you now close your browser and then re-open it to https://unifi.company.com:8443, you should no longer have the security warnings, and you will have a valid HTTPS certificate installed with a green padlock.  BUT – one caveat with Let’s Encrypt is that it expires pretty quickly (every 90 days if I remember correctly), so we also want to automate the process of renewing the certificate periodically.  To do this, we need to add a couple of lines to our /etc/crontab file, which will process these commands automatically on a schedule that we set (in this case, every 12 hours):

sudo nano -w /etc/crontab

Now, add the following two lines to the end of the file:

0 */12 * * * root letsencrypt renew
5 */12 * * * root unifi_ssl_import.sh

Save and exit nano by doing CTRL+X followed by Y.

The first command renews the certificate every 12 hours on the hour, and the second command re-runs the UniFi script 5 minutes later.

That’s it!

 

 

Comments 47

  1. Very helpful. Perhaps my ignorance, but a 12-hour renewal seems a bit “aggressive”. Any reason this isn’t further out like a month or two if the cert is, in fact, good for three months?

    Love your stuff and thanks for all the tips!

    1. Post
      Author

      I didn’t mention this in my video (I should have), but this only *checks* every 12 hours. Once the cert is within X number of days of expiring (I forget what X is – maybe 30 days), then it renews. Otherwise, all is well.

      1. I think the debian package creates /etc/cron.d/certbot that autorenews certs. I have not checked on ubuntu, but it might be unnecessary to create a renew crontab. The default for that crontab is also once every 12 hours.

      2. Right…. but the unifi_ssl_import.sh script going to restart your UniFi controller every 12 hours.
        Seems like unnecessary downtime, especially if you are using features that require the controller to be online, like guest portal.

  2. Rather than opening 80 and 443 all the time, you should use this command in your renewal command in crontab. This will open the ports as certbot runs the renewal then automatically close the ports when it finishes.

    certbot renew –pre-hook “ufw allow 80&&ufw allow 443” –post-hook “ufw delete allow 80&&ufw delete allow 443” –quiet

    1. Hi Erik,

      Not sure how to do what you suggested.

      I have installed certbot, do I just add,

      certbot renew –pre-hook “ufw allow 80&&ufw allow 443” –post-hook “ufw delete allow 80&&ufw delete allow 443” –quiet

      to the sudo nano -w /etc/crontab

      thanks in advance

      1. Wondering this myself. More than willing to add, but also have an additional question. If I do a 80 —> 443 redirect, will that affect certbot’s ability to renew?

  3. Any chance for a DNS-01 version of this? My ISP blocks port 80.

    I’m currently doing that manually on a different machine (with godaddy dns), but I’ve never successfully got it to be fully automated. I think I’ve just found a complete walk-through on how to get it going and am not quite skilled/smart enough to manage it.

    But also, I’m going to have to dig through this a few times. I think you’re showing me how to get my certs into the unifi controller with unifi_ssl_import.sh. I have been wanting to do that with the controller and unifi video server as well.

    1. Hi Matteo,

      Is this still an issue? I want to start an installation using the CloudKey on a new implementation and was wondering if this is going to work.

  4. First: Thanks for the script. I know this an a few more but knowing that it is tested was a good thing.

    There seems to be nothing in the script preventing it from just running through everytime. So it will restart your controller twice a day!!
    You should do the following:
    1) Save this to `/usr/local/bin/unifi_post-renew.sh`
    “`
    #!/bin/sh

    set -e

    for domain in $RENEWED_DOMAINS; do
    case $domain in
    unifi.company.com)
    /usr/local/bin/unifi_ssl_import.sh
    esac
    done
    “`

    2) Change `0 */12 * * * root letsencrypt renew` to `0 */12 * * * root letsencrypt renew –deploy-hook /usr/local/bin/unifi_post-renew.sh`

    This will only run the script when the cert is actually renewed

    1. Would you still leave the “5 */12 * * * root unifi_ssl_import.sh” in the crontab section?

      It looks like you are wanting to run it from the first part?

      Thanks for the feedback

    2. Adding to the question of Erik, do you need to customise the “unifi.company.com)” part of you script or it refer to something else? Thank you!

  5. Hi, Thanks for the script, all is work good, but and after install the public ssl, I have a problem with guest portal, on the admin page is without problem, but on the guest portal, sometime i get warrning for unsecure, and when i check site on ssl checker https://www.geocerts.com/ssl-checker i get this message.

    “A valid Root CA Certificate could not be located, the certificate will likely display browser warnings”

    Do you know, what is a possible problem ?

    Thank you

  6. Hey! I followed all the steps, but I believe the script is defective. The execution of the script is looped with the = yes statement. Can you verify this? Thank you

  7. So, I ran into a problem with this script and I figured out the solution. Just in case anyone else runs into this, here’s how I resolved my problem. I don’t know if it’s because this wasn’t a fresh controller install, but essentially, the original KeyStore for Unifi was in the jsk format instead of pkcs12, so this script broke the Tomcat Web Server startup. I am running Debian 9.6 as a Virtual Container on Proxmox with the latest controller package 5.9.29 (installed via apt when it was in 5.8, I believe).

    I first noted a long litany of errors in the /var/log/unifi/server.log including:
    – ERROR StandardService – Failed to start connector [Connector[HTTP/1.1-8443]]
    – Caused by: org.apache.catalina.LifecycleException: Protocol handler start failed
    – Caused by: java.lang.IllegalArgumentException: No aliases for private keys found in key store
    and more.

    These posts helped me resolve this:
    https://stackoverflow.com/questions/9299133/why-doesnt-java-send-the-client-certificate-during-ssl-handshake/9300727#9300727
    http://mail-archives.apache.org/mod_mbox/tomcat-users/200305.mbox/%3C4.3.2.7.2.20030504095807.00b47f60@wells.cisco.com%3E

    There were no keys in the Keystore, so I restored the keystore.orig file and the controller worked again (without the new key). I scrolled back in the SSH buffer from the LetsEncrypt script and found (I did not see this while the script was running):
    – keytool error: java.io.IOException: DerInputStream.getLength(): lengthTag=109, too big.

    So, to fix my issues, here’s what I did:
    Restored the old keystore: cp /usr/lib/unifi/data/keystore.orig /usr/lib/unifi/data/keystore
    Converted Keystore from jks to pkcs12 format: keytool -importkeystore -srckeystore keystore -destkeystore keystore -deststoretype pkcs12
    Removed the MD5 hash file generated by this script (if you don’t, it will not see a change in the key and not complete the entire script): rm /etc/letsencrypt/live/{hostname}/privkey.pem.md5
    Re-ran the script above: /usr/local/bin/unifi_ssl_import.sh

    Voila, everything is up and running. Sorry for the long post, but wanted to share in case anyone else has this problem. Thanks to Chris and Steve Jenkins for streamlining this process!

    1. Found that you had to be in /usr/lib/unifi/data/ to convert the store with keytool, but it is asking for a password, and to set a new one, what should i set that to. Dont want any issues with this later.

  8. Hey guys!

    I think, there is something wrong with the script.
    No matter if I try on DigitalOcean oder on Vultr, when running the Letsencrypt installation script it contains the following lines, an from that moment it is no longer possible to reach the server in the brwoser:

    Importing SSL certificate into UniFi keystore…
    Importing keystore /tmp/tmp.BU9AFS9wW1 to /var/lib/unifi/keystore…
    keytool error: java.io.IOException: DerInputStream.getLength(): lengthTag=109, too big.

    Removing temporary files…

    Restarting UniFi Controller to apply new Let’s Encrypt SSL certificate…

    Done!

    Any ideas what could be the problem?

    1. Take a look at my post from 12/15. I had the same issue – basically, it’s using jks as the keystore format, you need to change it to pkcs12.

  9. I am getting this error, presumably because im running docker contains using lets encrypt to reverse proxy on a separate vm inside my network….

    IMPORTANT NOTES:
    – The following errors were reported by the server:

    Domain: unifi.domain.net
    Type: unauthorized
    Detail: Invalid response from http://unifi.domain.net/.well-
    known/acme-challenge/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
    [X.X.X.X]: 404

    To fix these errors, please make sure that your domain name was
    entered correctly and the DNS A record(s) for that domain
    contain(s) the right IP address.

  10. Today, on 4th Jan 2019 generating LetsEncrypt cert in Script-installation from here, will ask for Webroot challenge. I have no idea what that is.

  11. Hi
    I’ve been trying to debug this on my Centos7 install, using LE certbot -certonly which seems fine, I have some certs.

    First problem seems to be that it thinks there is a signed CRT file to include when there isn’t – so it gives the syntax of Usage: pkcs12 [options] and I presume something wasn’t right there, so I commented these out and just ran the ‘no crt’ else procedure.

    That’s as far as I got, but it now still creates a keystore file (in /opt/Unifi/data for me) which is about 32 bytes long, and I have to delete and restore the original for the server to even accept my browser request. Strangely the systemctl status unifi.service does seem happy that it is running – so I presume Java is happy until it comes to serve the duff certificate and then it gets confused.

    Can anyone offer anything on this? I’m happy to help troubleshoot.
    Thanks

  12. Hi
    I managed to work out the main issue I was having with this script (Running on Centos7 but it might not make a difference).

    the section which imports the PKCS12 file into the UniFi Keystore specifies the deststoretype, which seems to be incorrect. Removing this line, but adding the switch -noprompt after -trustcacerts might also help.

    So my section reads:
    # Import the temp PKCS12 file into the UniFi keystore
    printf “\nImporting SSL certificate into UniFi keystore…\n”
    keytool -importkeystore \
    -srckeystore ${P12_TEMP} -srcstoretype PKCS12 \
    -srcstorepass ${PASSWORD} \
    -destkeystore ${KEYSTORE} \
    -deststorepass ${PASSWORD} \
    -destkeypass ${PASSWORD} \
    -alias ${ALIAS} -trustcacerts -noprompt

    My other problem which I reported but doesn’t look like it’s been published (maybe not authorised) yet, is about the script not correctly detecting that no .crt file exists. For me I simply commented out the block and copied the procedure for ‘no CRT file’. But I didn’t figure out why it doesn’t detect that the variable isn’t defined.

  13. My Ubuntu 16.04 runs fine,- until I use this manual.
    After installing the cert .. the GUI becomes unavailable. I tried a couple of times now, with the same result.

    I’m no total beginner in Linux, just an ordinary user, but I’m not skilled enough to troubleshoot.

    The webpage does not respond any more. No idea.

  14. Russ Kollmansberger

    My compliment and deep respect.
    I followed your method,- and it worked perfectly, thanks a lot.

    I’m not sure, what the problem was, or how your solved it. It’s above my head in understanding.
    Now I’m just happy 🙂

    Any problems when renewing the certificate, or is everything perfect?

    Cheers
    Jørn Madsen

  15. Hi,

    Thanks for the site, it is greatly appreciated!!!!
    Just received a notice from Let’s Encrypt.

    TLS-SNI-01 validation is reaching end-of-life. It will stop working
    temporarily on February 13th, 2019, and permanently on March 13th, 2019.
    Any certificates issued before then will continue to work for 90 days
    after their issuance date.

    You need to update your ACME client to use an alternative validation
    method (HTTP-01, DNS-01 or TLS-ALPN-01) before this date or your
    certificate renewals will break and existing certificates will start to
    expire.

    I’m afraid I’m not as versed as I want to be yet.
    Can anyone assist in pointing me to a solution?

    1. Any updates on this. I am getting emails saying the SSL version hits end of life on feb 13 but the SSL says it doesn’t expire till april.

  16. Hi Guys,
    So, the protocole for validation is gonna be discontinued and need to be updated to one supported. In my case, I found the http one to be working best. So, here is how to upgrade to it.

    First of all, we need to update Certbot:
    sudo apt-get update
    sudo apt-get install software-properties-common
    sudo add-apt-repository universe
    sudo add-apt-repository ppa:certbot/certbot
    sudo apt-get update
    sudo apt-get install certbot python-certbot-apache

    After that, we need to do some changes to our site conf file.
    You can find it using these 2 commands:
    sudo cd /etc/letsencrypt/renewal
    sudo ls

    Now, we need to edit it:
    sudo nano exemple.com.conf

    Be sure to have this parameter set to this setting:
    pref_challs = http-01,

    Now, we need to do some tweaks to the server config itself.
    Let’s say you are like me and don’t want the port 80 exposed but only the port 443, here’s what we need to do to get the certbot running automatically:

    First of, let’s edit the cli.ini file from certbot:
    sudo nano /etc/letsencrypt/cli.ini

    Then, add these lines and save the file:
    pre-hook “ufw allow http&&service apache2 stop”
    deploy-hook “/usr/local/bin/unifi_ssl_import.sh”
    post-hook “ufw deny http&&service apache2 start”

    After that, we will edit the cronjobs:
    crontab -e

    We’ll make sure that only this line for letsencrypt is present as we run the import script from certbot itself, which will prevent a restart of unifi twice a day, then, we’ll save it:
    0 */12 * * * root letsencrypt renew

    Now, to be sure everything is working, we can do a test run or simply launch it manually, here’s how:
    Test run:
    sudo letsencrypt renew –dry-run
    Manual renewal:
    sudo letsencrypt renew

    If you still get an error for tls, simply renew it with this argument:
    sudo letsencrypt renew –preferred-challenges http

    Good luck!

    1. Hi!
      So, I changed a little my setup as blocking http with ufw don’t work

      Simply change this part in the /etc/letsencrypt/cli.ini:
      Open it using sudo nano /etc/letsencrypt/cli.ini
      Change the following lines:
      pre-hook ufw allow 80&&service apache2 stop
      deploy-hook /usr/local/bin/unifi_ssl_import.sh
      post-hook ufw deny 80&&service apache2 start

      Also, if needed, you can redirect the traffic from port 8443 to 443 or 8080 to 80 directly from the ubuntu/debian server using these lines:
      sudo iptables -t nat -A PREROUTING -p tcp –dport 443 -j REDIRECT –to-port 8443
      sudo iptables -t nat -A PREROUTING -p tcp –dport 80 -j REDIRECT –to-port 8080
      They can also be removes using these commands and modifying them accordingly to the results:
      To list the redirections:
      iptables -t nat -L –line-number
      To delete it, simply replace “number” by the line number value:
      iptables -t nat -D PREROUTING “number”

  17. UBNT 18.04 – UNIFI 5.10.24 – I installed Let’s Encrypt first on 443 then downloaded and ran the script, worked out just fine.

  18. First off, GREAT script. Everything works. My only issue is I am having a hard time getting crontab to work.
    SHELL=/bin/sh
    PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin

    # m h dom mon dow user command
    17 * * * * root cd / && run-parts –report /etc/cron.hourly
    25 6 * * * root test -x /usr/sbin/anacron || ( cd / && run-parts –report /etc/cron.daily )
    47 6 * * 7 root test -x /usr/sbin/anacron || ( cd / && run-parts –report /etc/cron.weekly )
    52 6 1 * * root test -x /usr/sbin/anacron || ( cd / && run-parts –report /etc/cron.monthly )
    #
    00 */12 * * * root letsencrypt renew
    05 */12 * * * root unifi_ssl_import.sh

    1. Post
      Author
  19. Thanks, had a server with letsencrypt already running a few sites so used the script to pull it in to unifi. Worked great.

  20. Hello

    I have a problem. with generating keys and port 80. My equipment is Unifi Cloud Key 2gen plus. I have an error like this.

    root@ck-plus:~# sudo letsencrypt certonly
    Saving debug log to /var/log/letsencrypt/letsencrypt.log

    How would you like to authenticate with the ACME CA?
    – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – –
    1: Spin up a temporary webserver (standalone)
    2: Place files in webroot directory (webroot)
    – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – – –
    Select the appropriate number [1-2] then [enter] (press ‘c’ to cancel): 1
    Plugins selected: Authenticator standalone, Installer None
    Please enter in your domain name(s) (comma and/or space separated) (Enter ‘c’
    to cancel): palka.ddns.net
    Obtaining a new certificate
    Performing the following challenges:
    http-01 challenge for palka.ddns.net
    Cleaning up challenges
    Problem binding to port 80: Could not bind to IPv4 or IPv6.

Leave a Reply

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