Use Let’s Encrypt Certificates with FreeRADIUS


Let’s Encrypt is a certificate authority that generates TLS certificates automatically, and for free. It’s been great for web server administrators because it allows them to automate the process of requesting, receiving, installing, and renewing TLS certificates, taking the administrative overhead out of setting up a secure website. And did I mention it’s free and supported by all the major web browsers now?

Getting all of that to work with a RADIUS server is challenging however, mostly because of the way Let’s Encrypt works. The Let’s Encrypt client runs on a web server with a public domain name. The client requests a TLS cert from Let’s Encrypt and before Let’s Encrypt issues the cert, it verifies that the client is connecting from the same domain name that it is requesting a cert for, and that the client can put some hidden files on the server’s website. Do you see the problem? Unless you run a public-facing web server on your RADIUS server (unlikely), Let’s Encrypt will not issue certs to your server. It needs a web server it can interact with in order to validate the domain name of the client’s request.

Why use a certificate from a public CA like Let’s Encrypt for 802.1X/PEAP authentication? While a private CA offers more security, a public CA has the advantage of having a pre-installed root certificate on virtually all RADIUS supplicants, including BYOD clients that are unmanaged. If you don’t have an MDM or BYOD onboarding solution, you can’t get your private root cert onto BYOD clients very easily.

Unmanaged clients are a security risk, however, because the end-user can easily override security warnings that occur when connecting to an evil twin network with a bogus cert. A good MDM solution will allow network admins configure BYOD clients properly so that TLS failures cannot be bypassed.

A few considerations before you get too excited:

  • Again, a better, more secure solution is to use a private CA and distribute the RADIUS server cert to clients using an MDM solution and/or BYOD onboarding solution.
  • Let’s Encrypt certs are only good for three months at a time, and some supplicants will prompt users to accept the new certificate when it is renewed.
  • Build in some error handling, logging, and notification. E.g. an email from the web server when the cert renewal routine runs, including its output, and an email from the RADIUS server when it copies the new certs and reloads FreeRADIUS.
  • It works as root, but there’s probably a way to accomplish this without using root. Do it that way.
  • You can accomplish the same thing with Windows servers and Powershell.
  • You broke it, not me.

To get this working, we need a public web server with the same domain same as you’d use in your RADIUS server’s cert common name. This means internal domain names with a .local TLD won’t work.

I setup two Ubuntu servers, one running the nginx web server with a public IP, and another on my local network running FreeRADIUS. The web server will run the Let’s Encrypt client and create and renew the certs. The RADIUS server will copy those certs from the web server and use them for PEAP authentication. Once setup, the process of renewing and installing the certs on the RADIUS server happens automatically, just like it would on a web server.

First, a public DNS A record needs to be setup with the domain name which will be used on the TLS cert common name, we’ll use, and point it to the IP address of the web server.

Once that is done, you can install and run the Let’s Encrypt client on the web server. It works with Apache too, but if you prefer nginx like me, follow these directions to get it setup with Ubuntu 14.04 or Ubuntu 16.04. Don’t skip over the part about using cron to run the renewal routine.

Now that we have the certs on the web server, we’ll turn our attention to the RADIUS server. The first thing we need to do is setup ssh public key authentication between the two servers. I used the root account on both servers to do this, so that I would have permissions everywhere I needed it. With public key authentication in place securely copying the certs in the future can happen automatically, without getting stopped by a password request. Here are instructions to get that working.

Now we’ll start configuring FreeRADIUS on the RADIUS server. I’m assuming you already have a working FreeRADIUS server. I’m using FreeRADIUS 3, and you should be too. I like to use a separate directory for the Let’s Encrypt certs.

root@freeradius:~# mkdir /etc/freeradius/certs/letsencrypt/

Now let’s try copying the certs from the web server to this directory on the RADIUS server. If public key authentication is working, you should not be prompted for a password.

root@freeradius:~# scp /etc/freeradius/certs/letsencrypt/
root@freeradius:~# scp /etc/freeradius/certs/letsencrypt/

Did it work? If so, you should see the certs in the new folder we created.

root@freeradius:~# ls /etc/freeradius/certs/letsencrypt/
fullchain.pem  privkey.pem

Now we need to configure FreeRADIUS to use the Let’s Encrypt certs for PEAP authentication. I have a previous blog about using different CA’s for PEAP and EAP-TLS on FreeRADIUS that should come in handy here. If you are using EAP-TLS too, be sure not to change that CA from your private CA! All we need to do now is modify /etc/freeradius/mods-enabled/eap with our new certs in the TLS section used for PEAP.

root@freeradius:~# nano /etc/freeradius/mods-enabled/eap

tls-config tls-peap should be changed to:

tls-config tls-peap {
 private_key_file = ${certdir}/letsencrypt/privkey.pem
 certificate_file = ${certdir}/letsencrypt/fullchain.pem

If you aren’t using multiple TLS configurations, this section is named tls-config tls-common. You can leave it like that.

Reload FreeRADIUS for the change to take effect.

root@freeradius:~# service freeradius reload
 * Checking FreeRADIUS daemon configuration...               [ OK ] 
 * FreeRADIUS daemon is running
 * Reloading FreeRADIUS daemon freeradius                    [ OK ]

Now when connecting to the WLAN that is configured to use this RADIUS server for 802.1X/PEAP  authentication, the client is presented with a valid Let’s Encrypt server certificate.


OK, we have a working FreeRADIUS server using Let’s Encrypt certs for 802.1X/PEAP authentication. Now let’s automate the process of getting renewed certs from the web server to the RADIUS server. We’ll use scp and cron to get this done.

On the RADIUS server, add these commands to root’s crontab, with the appropriate domain names.

root@freeradius:~# crontab -e
# m h dom mon dow command
0 3 * * 1 scp /etc/freeradius/certs/letsencrypt/
0 3 * * 1 scp /etc/freeradius/certs/letsencrypt/
5 3 * * 1 service freeradius reload

At 3:00 AM every Monday, cron will run copy the TLS certs from the web server the reload FreeRADIUS at 3:05 AM to put them into production. Now the Let’s Encrypt certs are automatically installed on the RADIUS server a few minutes after they are renewed on the web server. The certs are good for three months at a time and renewable one month in advance, so you’ll get renewed certs automatically installed every two months.

Presto! You now have Let’s Encrypt certs automatically renewed and installed on your RADIUS server. While a private CA is a better solution for 802.1X authentication, this isn’t bad for a $0 software stack.

12 thoughts on “Use Let’s Encrypt Certificates with FreeRADIUS

  1. Thanks for the guide! I tried this for my home setup, just to have means to present trusted certificates to the clients, which “looks better” and does not educate the kids to accept untrusted certs. Since I have letsencrypt certs anyhow, it was quite easy. However, even after adding the subdomain of the machine running freeradius to the letsencrypt cert, the iOS devices here here still call the presented cert “untrusted”. Any way to get this “fixed”? If not, probably better to go with the more secure, self signed option.


    1. I’m afraid not. I believe the default behavior of iOS is to always present the server cert to the user and require them to accept it, even if it is a valid cert (unless it has been preinstalled in an MDM configuration profile). This is a downside to using LE with a heavy iOS environment as each time the cert is renewed, clients have to accept it again.


      1. Thanks. I could live with the renewal periods and repeated confirmation. The point I was surprised about is that iOS calls the certificated “not trusted”, in red at the top. That’s more or less identical to how the private, self-signed certificates were presented…


  2. Thanks for the great tutorial.

    We also use Let’s Encrypt in our environment.
    Do you know what happens with Windows users when the certificate is renewed?
    Is only the “Accept certificate” prompt appearing or does the user have to reinput their credentials?


    1. I believe the Windows supplicant verifies that the server cert was issued by a trusted CA and optionally verifies the CN or SAN domain name. I’m not 100% on that though, so lab it up to be sure.


  3. Same problem on Windows 7,8 and 10, I believe It doesn’t matter if you have a trusted certificate or not, if (on Windows) you don’t create the wifi profile in advance, although by default it has the option “Verify server certificate”, it doesn’t have any specified Root CA certificate checked… and you will get a certificate error even with a valid cert. To correct this you have to manually specify which CA to trust for this specific SSID. My point is if you want to deploy it for BYOD using PEAP-MSCHAPv2 you’re better off with self signed certs. Using public certificates on Radius servers is not a good idea because it creates opportunities for man-in-the-middle attacks if they have a cert issued by the same public CA as you do.


  4. Hey mate! Excuse me for stupid questions but anyway: if I have freeradius server in my local network with private IP-address like and I use it for connecting devices to office Wi-Fi via login/password, so the question is:
    I have a few Iphones which has troubles to connect to Wi-Fi because they doesn’t trust default radius certificate. Here I found common issue with this workaround:
    How can I resolve such situation? Can I use public web server with the domain name which has no relation to my radius server just to get Let’s encrypt certificate and use it for my radius server?


    1. Hi George! I think using Let’s Encrypt will just complicate the matter. Instead, I suggest creating a new private cert for the radius server with openssl that meets all of Apple’s requirements. Then you can add the cert to the clients in advance so trust is per-established.


      1. Could you please help to create such cert? Because the main reason IOS devices doesn’t trust default radius certificate is they doesnt’t know CA. If I take command from here:
        command like that: openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365
        will my IOS deviices start to trust such cert?
        Maybe you have link for some guide or something like that?


  5. Unfortunately, as I understood from docs and google there is no way to fix it on IOS-devices with self-signed certs. But anyway thanks for your help:)


  6. Hi Jim!

    I am trying to get freeradius to work with certificates since a few weeks now.

    After spending hours and hours with creating certificates and ending up with freeradius or android not accepting the CA because it is self signed, I wanted to give Lets Encrypt a shot.

    So i just created the Certificate for my radius subdomain on my opnsense firewall using the lets encrypt certificate.

    On my freeradius server i created a cronjob which pulls the certificates every hour.

    But certificates get re-issued every 60 days on opnsense anyway.

    Sadly I does not work at all.

    I always get an error that the certificate would be expired. Even after I just issued it.

    Mon Nov 8 19:39:16 2021 : ERROR: (13) eap_peap: ERROR: TLS Alert read:fatal:certificate expired
    Mon Nov 8 19:39:16 2021 : Auth: (13) Login incorrect (eap_peap: TLS Alert read:fatal:certificate expired): [Pascal3366/] (from client homebase-AP1 port 0 cli 5C-17-CF-1C-A0-40)

    Is there any way to fix this issue ?

    Thanks in advance !


Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: