# Automate Let's Encrypt Certificates on BIG-IP

**URL:** <https://community.f5.com/t/automate-lets-encrypt-certificates-on-big-ip/69447>\
**Category:** F5 Technical Articles\
**Tags:** irules, big-ip, ssl, security, application-delivery\
**Created:** [March 30, 2022, 4:00pm UTC](https://community.f5.com/t/automate-lets-encrypt-certificates-on-big-ip/69447 "2022-03-30T16:00:00Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![JRahm](https://d1p9zq3aats0t8.cloudfront.net/user_avatar/community.f5.com/jrahm/32/3327_2.png) [@JRahm](https://community.f5.com/u/JRahm)\
**Post date:** [March 30, 2022, 4:00pm UTC](https://community.f5.com/t/automate-lets-encrypt-certificates-on-big-ip/69447/1 "2022-03-30T16:00:00Z")

</div>

To quote the evil emperor Zurg: “We meet again, for the last time!” It’s hard to believe it’s been six years since my first rodeo with Let’s Encrypt and BIG-IP, but (uncompromised) timestamps don’t lie. And maybe this won’t be my last look at Let’s Encrypt, but it will likely be the last time I do so as a standalone effort, which I’ll come back to at the end of this article. The first project was a compilation of shell scripts and python scripts and config files and well, this is no different. But it’s all updated to meet the acme protocol version requirements for Let’s Encrypt. Here’s a quick table to connect all the dots:

| **Description** | **What’s Out** | **What’s In** |
| --- | --- | --- |
| acme client | letsencrypt.sh | [dehydrated](https://github.com/dehydrated-io/dehydrated) |
| python library | [f5-common-python](https://github.com/f5networks/f5-common-python) | [bigrest](https://github.com/leonardobdes/BIGREST) |
| BIG-IP functionality | creating the SSL profile | utilizing an iRule for the HTTP challenge |

The f5-common-python library has not been maintained or enhanced for at least a year now, and I have an affinity for the good work Leo did with bigrest and I enjoy using it. I opted not to carry the SSL profile configuration forward because that functionality is more app-specific than the certificates themselves. And finally, whereas my initial project used the DNS challenge with the [name.com](http://name.com) API, in this proof of concept I chose to use an iRule on the BIG-IP to serve the challenge for Let’s Encrypt to perform validation against.

Whereas my solution is new, the way Let’s Encrypt works has not changed, so I’ve carried forward the process from my previous article that I’ve now archived. I’ll defer to their [how it works](https://letsencrypt.org/how-it-works/) page for details, but basically the steps are:

1. Define a list of domains you want to secure
2. Your client reaches out to the Let’s Encrypt servers to initiate a challenge for those domains.
3. The servers will issue an http or dns challenge based on your request
4. You need to place a file on your web server or a txt record in the dns zone file with that challenge information
5. The servers will validate your challenge information and notify you
6. You will clean up your challenge files or txt records
7. The servers will issue the certificate and certificate chain to you
8. You now have the key, cert, and chain, and can deploy to your web servers or in our case, to the BIG-IP

Before kicking off a validation and generation event, the client registers your account based on your settings in the config file. The files in this project are as follows:

```bash
/etc/dehydrated/config # Dehydrated configuration file
/etc/dehydrated/domains.txt # Domains to sign and generate certs for
/etc/dehydrated/dehydrated # acme client
/etc/dehydrated/challenge.irule # iRule configured and deployed to BIG-IP by the hook script
/etc/dehydrated/hook_script.py # Python script called by dehydrated for special steps in the cert generation process
# Environment Variables
export F5_HOST=x.x.x.x
export F5_USER=admin
export F5_PASS=admin

```

You add your domains to the domains.txt file (more work likely if signing a lot of domains, I tested the one I have access to). The dehydrated client, of course is required, and then the hook script that dehydrated interacts with to deploy challenges and certificates. I aptly named that hook\_script.py. For my hook, I’m deploying a challenge iRule to be applied only during the challenge; it is modified each time specific to the challenge supplied from the Let’s Encrypt service and is cleaned up after the challenge is tested. And finally, there are a few environment variables I set so the information is not in text files. You could also move these into a credential vault. So to recap, you first register your client, then you can kick off a challenge to generate and deploy certificates. On the client side, it looks like this:

```bash
./dehydrated --register --accept-terms
./dehydrated -c

```

Now, for testing, make sure you use the Let’s Encrypt staging service instead of production. And since I want to force action every request while testing, I run the second command a little differently:

```bash
./dehydrated -c --force --force-validation

```

Depicted graphically, here are the moving parts for the http challenge issued by Let’s Encrypt at the request of the dehydrated client, deployed to the F5 BIG-IP, and validated by the Let’s Encrypt servers. The Let’s Encrypt servers then generate and return certs to the dehydrated client, which then, via the hook script, deploys the certs and keys to the F5 BIG-IP to complete the process.

 ![Let's Encrypt.png](https://d20hrnpixdzcsd.cloudfront.net/original/3X/c/b/cb538ddd5b1d1ddc64dc4d5afa7c748f96caebfe.png)

And here’s the output of the dehydrated client and hook script in action from the CLI:

```bash
# ./dehydrated -c --force --force-validation
# INFO: Using main config file /etc/dehydrated/config
Processing example.com
 + Checking expire date of existing cert...
 + Valid till Jun 20 02:03:26 2022 GMT (Longer than 30 days). Ignoring because renew was forced!
 + Signing domains...
 + Generating private key...
 + Generating signing request...
 + Requesting new certificate order from CA...
 + Received 1 authorizations URLs from the CA
 + Handling authorization for example.com
 + A valid authorization has been found but will be ignored
 + 1 pending challenge(s)
 + Deploying challenge tokens...
 + (hook) Deploying Challenge
 + (hook) Challenge rule added to virtual.
 + Responding to challenge for example.com authorization...
 + Challenge is valid!
 + Cleaning challenge tokens...
 + (hook) Cleaning Challenge
 + (hook) Challenge rule removed from virtual.
 + Requesting certificate...
 + Checking certificate...
 + Done!
 + Creating fullchain.pem...
 + (hook) Deploying Certs
 + (hook) Existing Cert/Key updated in transaction.
 + Done!

```

This results in a deployed certificate/key pair on the F5 BIG-IP, and is modified in a transaction for future updates.

 ![le_certs_bigip.png](https://d20hrnpixdzcsd.cloudfront.net/original/2X/3/3a5300d44a537541862eeeb8c38cc3dad8ab5869.png)

 ![le_cert_details.png](https://d20hrnpixdzcsd.cloudfront.net/original/3X/2/c/2c2dd5d5eac78c05b5795ef880e6097637f53e65.png)

This [proof of concept](https://github.com/f5devcentral/lets-encrypt-python) is on github in the f5devcentral org if you’d like to take a look. Before closing, however, I’d like to mention a couple things:

1. This is an update to an existing solution from years ago. It works, but probably isn’t the best way to automate today if you’re just getting started and have already started pursuing a more modern approach to automation.
2. A better path would be something like Ansible. On that note, there are several solutions you can take a look at, posted below in resources.

### Resources

- [GitHub - EquateTechnologies/dehydrated-bigip-ansible: Ansible based hooks for dehydrated to enable ACME certificate automation for F5 BIG-IP systems · GitHub](https://github.com/EquateTechnologies/dehydrated-bigip-ansible)
- [GitHub - f5devcentral/ansible-bigip-letsencrypt-http01: Example implementation of handling ACME http-01 challenges through a BIG-IP and applying updated certificates to virtual servers · GitHub](https://github.com/f5devcentral/ansible-bigip-letsencrypt-http01)
- [GitHub - s-archer/acme-ansible-f5 · GitHub](https://github.com/s-archer/acme-ansible-f5)
- [https://github.com/s-archer/terraform-modular/tree/master/lets\_encrypt\_module](https://github.com/s-archer/terraform-modular/tree/master/lets_encrypt_module)&nbsp;(Terraform instead of Ansible)
- [https://community.f5.com/t5/technical-forum/let-s-encrypt-with-cloudflare-dns-and-f5-rest-api/m-p/292943](https://community.f5.com/post/292943)&nbsp;(Similar solution to mine, only slightly more robust with OCSP stapling, the DNS instead of HTTP challenge, and with bash instead of python)

---

<div class="post-metadata">

**Author:** ![nikzin](https://avatars.discourse-cdn.com/v4/letter/n/9e8a1a/32.png) [@nikzin](https://community.f5.com/u/nikzin)\
**Post date:** [April 1, 2022, 4:37am UTC](https://community.f5.com/t/automate-lets-encrypt-certificates-on-big-ip/69447/2 "2022-04-01T04:37:17Z")

</div>

Great article. Can i use this solution for other CAs than LE, if they support ACME ?

---

<div class="post-metadata">

**Author:** ![EM](https://avatars.discourse-cdn.com/v4/letter/e/ecc23a/32.png) [@EM](https://community.f5.com/u/EM)\
**Post date:** [April 1, 2022, 6:43am UTC](https://community.f5.com/t/automate-lets-encrypt-certificates-on-big-ip/69447/3 "2022-04-01T06:43:51Z")

</div>

Heya!

Nice article! Just dropping an info:

There is a pypi plugin for the certbot client as well.

> **[Client Challenge](https://pypi.org/project/certbot-bigip/)**

We maintain and use it for ourselfes and some of our customers use it as well.

---

<div class="post-metadata">

**Author:** ![JRahm](https://d1p9zq3aats0t8.cloudfront.net/user_avatar/community.f5.com/jrahm/32/3327_2.png) [@JRahm](https://community.f5.com/u/JRahm)\
**Post date:** [April 1, 2022, 6:26pm UTC](https://community.f5.com/t/automate-lets-encrypt-certificates-on-big-ip/69447/4 "2022-04-01T18:26:46Z")

</div>

@nikzin &nbsp;the F5 side should “just work” but would need to make sure source directory locations for certs/keys were updated if there’s a change. But yeah, this could be used as long as acme is supported elsewhere, no promises on it being ready to go, probably some tuning required.

@EM &nbsp;nice! I though about integrating it all into python instead of keeping the domains separate with bash, but decided on expediency.

---

<div class="post-metadata">

**Author:** ![PSFletchTheTek](https://avatars.discourse-cdn.com/v4/letter/p/53a042/32.png) [@PSFletchTheTek](https://community.f5.com/u/PSFletchTheTek)\
**Post date:** [June 21, 2022, 3:24pm UTC](https://community.f5.com/t/automate-lets-encrypt-certificates-on-big-ip/69447/5 "2022-06-21T15:24:50Z")

</div>

Hi&nbsp; @JRahm , i’m no expert with any of this stuff!  
I’m struggling to work out from the info above how to get this working.  
Do i need to run this from the f5 or a seperate machine?  
I like the idea of using a irule to do the HTTP Challenge as my f5 is the only thing allowed presented on the environment!  
Any implementation guides online anywhere?  
Ta Fletch

---

<div class="post-metadata">

**Author:** ![JRahm](https://d1p9zq3aats0t8.cloudfront.net/user_avatar/community.f5.com/jrahm/32/3327_2.png) [@JRahm](https://community.f5.com/u/JRahm)\
**Post date:** [June 21, 2022, 5:56pm UTC](https://community.f5.com/t/automate-lets-encrypt-certificates-on-big-ip/69447/6 "2022-06-21T17:56:01Z")

</div>

Hi&nbsp; @PSFletchTheTek ,&nbsp;definitely from a separate machine. This isn’t an officially supported solution, so the only implementation guide is what I wrote above. Feel free to post any attempt details as a question in the forum and tag me and I’ll take a look to see if I can help you get un-stuck.

---

<div class="post-metadata">

**Author:** ![PSFletchTheTek](https://avatars.discourse-cdn.com/v4/letter/p/53a042/32.png) [@PSFletchTheTek](https://community.f5.com/u/PSFletchTheTek)\
**Post date:** [June 22, 2022, 8:38am UTC](https://community.f5.com/t/automate-lets-encrypt-certificates-on-big-ip/69447/7 "2022-06-22T08:38:41Z")

</div>

Thanks&nbsp; @JRahm &nbsp;,

That makes a little more sense now, espically why you where suggesting things like ansible to do the work instead!  
I’ve got a ansible server with the f5 on the to do list, i’ll add cert management to that to do list.

As i said i really like the irule approach for the response code, i just need to work out how it works now!  
I need to work out where&nbsp;TOKENVALUE is defined.  
Then how i can reproduce that in a ansible script.

---

<div class="post-metadata">

**Author:** ![JRahm](https://d1p9zq3aats0t8.cloudfront.net/user_avatar/community.f5.com/jrahm/32/3327_2.png) [@JRahm](https://community.f5.com/u/JRahm)\
**Post date:** [June 22, 2022, 3:49pm UTC](https://community.f5.com/t/automate-lets-encrypt-certificates-on-big-ip/69447/8 "2022-06-22T15:49:37Z")

</div>

Ah..the TOKENVALUE is a placeholder in the iRule that the python hook script replaces from the challenge received from Let’s Encrypt. This is done automatically for you each time, as the iRule template is modified, uploaded, challenged, and then removed.

---

<div class="post-metadata">

**Author:** ![kgaigl](https://avatars.discourse-cdn.com/v4/letter/k/c67d28/32.png) [@kgaigl](https://community.f5.com/u/kgaigl)\
**Post date:** [August 17, 2022, 6:27am UTC](https://community.f5.com/t/automate-lets-encrypt-certificates-on-big-ip/69447/9 "2022-08-17T06:27:13Z")

</div>

Hello,

I see I can enroll Certs with this scripts (although I’ve got problems to understand), but how can I put this certs in a SSL-Profile?

We’ve got a VS for our Homepage with a SSL-Client Profile and there are about 25 URL’s pointing to this VS. One SSL-Profile is the default SNI Profile and we’ve got 25 other SSL-Profiles with the corresponding Certs (I know this is not very smart, we could put all Common Names as SAN’s to one Cert, but I’m not sure how this works with SNI)

So I think we can’t automate the complete process to renew the Certs, right?

Is there any other way?

Our webteam is asking if they can switch to lets enrypt and I can’t see a way to automate this

The Certs are all from Digicert

---

<div class="post-metadata">

**Author:** ![JRahm](https://d1p9zq3aats0t8.cloudfront.net/user_avatar/community.f5.com/jrahm/32/3327_2.png) [@JRahm](https://community.f5.com/u/JRahm)\
**Post date:** [August 17, 2022, 5:01pm UTC](https://community.f5.com/t/automate-lets-encrypt-certificates-on-big-ip/69447/10 "2022-08-17T17:01:03Z")

</div>

Hi&nbsp; @kgaigl …yes, you can do that. Because this script updates the certs/keys in a transaction, it will not complain that they are attached to an existing ssl-profile.

Creating/updating ssl-profiles can also be automated, but I would probably segment that logic out into a different script (and even this script might need some massaging for your needs), maybe one that manages the needs for which domains are secured, and then calls the lets-encrypt action to create/upload certs, makes sure they are there, then acts on adding/updating/removing the appropriate key/value pairs in the profile as necessary.

---

<div class="post-metadata">

**Author:** ![kgaigl](https://avatars.discourse-cdn.com/v4/letter/k/c67d28/32.png) [@kgaigl](https://community.f5.com/u/kgaigl)\
**Post date:** [August 18, 2022, 6:09am UTC](https://community.f5.com/t/automate-lets-encrypt-certificates-on-big-ip/69447/11 "2022-08-18T06:09:48Z")

</div>

Hi JRahm,

thank you but I guess this is beyond my skills.

Maybe I’ll ask our Support Partner, but I’m afraid of finding solutions if something goes wrong

---

<div class="post-metadata">

**Author:** ![JRahm](https://d1p9zq3aats0t8.cloudfront.net/user_avatar/community.f5.com/jrahm/32/3327_2.png) [@JRahm](https://community.f5.com/u/JRahm)\
**Post date:** [August 18, 2022, 3:24pm UTC](https://community.f5.com/t/automate-lets-encrypt-certificates-on-big-ip/69447/12 "2022-08-18T15:24:27Z")

</div>

Maybe a project you could take on to build those skills! Happy to provide pointers/feedback along the way.

---

<div class="post-metadata">

**Author:** ![TimRiker](https://d1p9zq3aats0t8.cloudfront.net/user_avatar/community.f5.com/timriker/32/7820_2.png) [@TimRiker](https://community.f5.com/u/TimRiker)\
**Post date:** [September 8, 2022, 11:48pm UTC](https://community.f5.com/t/automate-lets-encrypt-certificates-on-big-ip/69447/13 "2022-09-08T23:48:09Z")

</div>

I tried this to create SAN certificates and got failures as the added irule can only handle one challenge at a time.

I moved the challenge to a datagroup. Changed the setup to run as non-root, and created a pull request:

> **[GitHub - timriker/lets-encrypt-python](https://github.com/timriker/lets-encrypt-python)**
>
> Contribute to timriker/lets-encrypt-python development by creating an account on GitHub.

> <https://github.com/f5devcentral/lets-encrypt-python/pull/8>
>
> Moved to non-root, so create accounts, certs, and wellknown under working dir.
> …removed CONTACT\_EMAIL, which user will need to add anyway
> create client-ssl cert
> updated docs, added Getting Started, etc.

---

<div class="post-metadata">

**Author:** ![JRahm](https://d1p9zq3aats0t8.cloudfront.net/user_avatar/community.f5.com/jrahm/32/3327_2.png) [@JRahm](https://community.f5.com/u/JRahm)\
**Post date:** [September 9, 2022, 4:37pm UTC](https://community.f5.com/t/automate-lets-encrypt-certificates-on-big-ip/69447/14 "2022-09-09T16:37:27Z")

</div>

Hi&nbsp; @TimRiker ,&nbsp;I’ll take a look, thanks for the PR!

---

<div class="post-metadata">

**Author:** ![ViktorBidnenko](https://d1p9zq3aats0t8.cloudfront.net/user_avatar/community.f5.com/viktorbidnenko/32/4812_2.png) [@ViktorBidnenko](https://community.f5.com/u/ViktorBidnenko)\
**Post date:** [April 24, 2023, 8:28pm UTC](https://community.f5.com/t/automate-lets-encrypt-certificates-on-big-ip/69447/15 "2023-04-24T20:28:39Z")

</div>

Hi, do I need to have a valid cert on managment ip of big-ip? I think using self-signed does not work.

After runnig the script got ssl.SSLCertVerificationError: [SSL: CERTIFICATE\_VERIFY\_FAILED] certificate verify failed: self signed certificate (\_ssl.c:1123). But is it possible to find some workaround for this? I have python3.9, maybe this is the issue?

 ![Error](https://d20hrnpixdzcsd.cloudfront.net/original/2X/7/7ea651cc6dd2d5566b167ed2a3b9f5671dc5b0f8.jpeg)  
Error

---

<div class="post-metadata">

**Author:** ![JRahm](https://d1p9zq3aats0t8.cloudfront.net/user_avatar/community.f5.com/jrahm/32/3327_2.png) [@JRahm](https://community.f5.com/u/JRahm)\
**Post date:** [April 24, 2023, 8:51pm UTC](https://community.f5.com/t/automate-lets-encrypt-certificates-on-big-ip/69447/16 "2023-04-24T20:51:25Z")

</div>

you’ll need to set&nbsp; **session\_verify** &nbsp;to False when you instantiate BIG-IP if you are using self-signed certs on BIG-IP for management purposes.

---

<div class="post-metadata">

**Author:** ![ViktorBidnenko](https://d1p9zq3aats0t8.cloudfront.net/user_avatar/community.f5.com/viktorbidnenko/32/4812_2.png) [@ViktorBidnenko](https://community.f5.com/u/ViktorBidnenko)\
**Post date:** [April 24, 2023, 9:08pm UTC](https://community.f5.com/t/automate-lets-encrypt-certificates-on-big-ip/69447/17 "2023-04-24T21:08:18Z")

</div>

Thanks, did not find it before but now I see:

> **[BIGIP — BIGREST 1.7.3 documentation](https://bigrest.readthedocs.io/bigip.html)**

---

<div class="post-metadata">

**Author:** ![TimRiker](https://d1p9zq3aats0t8.cloudfront.net/user_avatar/community.f5.com/timriker/32/7820_2.png) [@TimRiker](https://community.f5.com/u/TimRiker)\
**Post date:** [April 25, 2023, 2:31am UTC](https://community.f5.com/t/automate-lets-encrypt-certificates-on-big-ip/69447/18 "2023-04-25T02:31:19Z")

</div>

@ViktorBidnenko &nbsp;  
On our site we already order globally signed wildcard certs. We use these same certs with internal hostnames on the same domain as the wildcard cert. Thus the device certs are also globally verified.

---

<div class="post-metadata">

**Author:** ![ScottE](https://d1p9zq3aats0t8.cloudfront.net/user_avatar/community.f5.com/scotte/32/11894_2.png) [@ScottE](https://community.f5.com/u/ScottE)\
**Post date:** [September 13, 2023, 9:23pm UTC](https://community.f5.com/t/automate-lets-encrypt-certificates-on-big-ip/69447/19 "2023-09-13T21:23:10Z")

</div>

I moved the environmental variables to configuration files so that it can process multiple certificates, both single and SAN.&nbsp; Also added a “virtual\_servers” file which provides the cross references required to map the certificate name(s) to the virtual host on the LB on which to apply the irule and ssl profile to.

There was also a bug I fixed and added a variable in the hook\_script.py file for the parent ssl profile that Tim Riker had added.&nbsp; I’ve created a pull request:

> **[GitHub - ScottECampbell/lets-encrypt-python](https://github.com/ScottECampbell/lets-encrypt-python)**
>
> Contribute to ScottECampbell/lets-encrypt-python development by creating an account on GitHub.

> <https://github.com/f5devcentral/lets-encrypt-python/pull/9>
>
> Hello Jason,
> 
> I've cleaned up my edits. Two new files, one required, one exam…ple only.
> 
> I've moved all the env variables to files and added the ability to have it run for a multiple number of domains/certificates. Currently it runs nightly and has a mixture of single domain certificates and SAN certificates without issues.
> 
> In my environment I needed a python virtual environment so I've provided an example cron\_wrapper file.
> 
> Please let me know if anything is unclear or needs more explanation.
> 
> Scott Campbell

Hopefully I’ve done this all correctly.&nbsp; I’ve been running it via cron for over a month with multiple certificates in the config files without any problems.

---

<div class="post-metadata">

**Author:** ![TimRiker](https://d1p9zq3aats0t8.cloudfront.net/user_avatar/community.f5.com/timriker/32/7820_2.png) [@TimRiker](https://community.f5.com/u/TimRiker)\
**Post date:** [November 25, 2025, 10:02pm UTC](https://community.f5.com/t/automate-lets-encrypt-certificates-on-big-ip/69447/20 "2025-11-25T22:02:06Z")

</div>

I needed wildcard certs, so I created scripts that do DNS orders and then deploy over REST.

[ACME DNS RFC-2136 Let's Encrypt certs | DevCentral](https://community.f5.com/topic/344541)
