Getting Started with `lego` (HTTP01)

Getting Started

In this tutorial, we will get certificates signed by Let’s Encrypt for the tls-certificates-require charm using the lego-operator and the HTTP01 challenge. The certificate requirer in this case is tls-certificates-require and HAProxy is only used to facilitate the ACME HTTP01 challenge.

Pre-requisites

  • Ubuntu 22.04
  • A Juju controller
  • A valid domain name
  • Access to DNS configuration
  • A requirer charm using the tls-certificates-interface

1. Add the Juju model

juju add-model lego 

2. Deploy and Configure the Requirer Charm

In this tutorial we are going to use the tls-certificates-requirer charm, and we will configure it to use our valid domain name.

juju deploy tls-certificates-requirer --config common_name="haproxy.techtutorial.org" --config sans_dns="haproxy.techtutorial.org"

3. Deploy and Configure lego

juju deploy lego --channel 4/stable

Configure the Let's Encrypt server. In this tutorial we will use the staging server of Let’s Encrypt

juju config lego server="https://acme-staging-v02.api.letsencrypt.org/directory"

Configure your email

juju config lego email=<your email address>

Configure the plugin to http

juju config lego plugin="http"

4. Deploy HAProxy and Ingress-configurator charms

juju deploy haproxy --channel 2.8/edge
juju deploy ingress-configurator --channel latest/edge --to <machine>

Where <machine> refers to the machine id on which HAProxy got deployed.

5. Deploy a Certificate Provider Charm that will serve the certificate of HAProxy

juju deploy self-signed-certificates --channel 1/stable

6. Configure HAProxy and Ingress-configurator charms

  • Make sure your domain’s DNS resolves to a public IP that actually reaches the haproxy unit
  • configure the hostname in the configurator charm
juju config ingress-configurator hostname="haproxy.techtutorial.org"
juju config ingress-configurator paths="/.well-known/acme-challenge" allow-http=true

7. Integrate the charms

juju integrate ingress-configurator:haproxy-route haproxy
juju integrate self-signed-certificates:certificates haproxy:certificates
juju integrate lego:ingress ingress-configurator
juju integrate lego tls-certificates-requirer

8. Validate Certificate

Wait until the charms go into the following states:

Model  Controller  Cloud/Region         Version  SLA          Timestamp
lego   machine     localhost/localhost  3.6.5    unsupported  15:30:23Z

App                        Version  Status  Scale  Charm                      Channel        Rev  Exposed  Message
haproxy                             active      1  haproxy                    2.8/edge       250  yes
ingress-configurator                active      1  ingress-configurator       latest/edge     27  no
lego                                active      1  lego                                        2  no       1/1 certificate requests are fulfilled
self-signed-certificates            active      1  self-signed-certificates   1/stable       317  no
tls-certificates-requirer           active      1  tls-certificates-requirer  latest/stable  143  no       Waiting for certificates relation

We can validate that the certificate was actually signed by Let's Encrypt

juju run tls-certificates-requirer/leader get-certificate \
  | yq -r '.certificates | fromjson | .[0].certificate' \
  | openssl x509 -text -noout

The output shows that the certificate was signed by Let's Encrypt for haproxy.techtutorial.org

Running operation 5 with 1 task
  - task 6 on unit-two-0

Waiting for task 6...
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            2c:da:10:cb:59:94:31:84:08:bf:18:b9:44:51:5e:e2:b5:3c
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: C = US, O = (STAGING) Let's Encrypt, CN = (STAGING) Tenuous Tomato R13
        Validity
            Not Before: Nov 18 13:13:27 2025 GMT
            Not After : Feb 16 13:13:26 2026 GMT
        Subject: CN = haproxy.techtutorial.org
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                Public-Key: (2048 bit)
                Modulus:
                    00:96:9f:20:1b:96:05:a9:0b:81:73:96:9e:03:b1:
                    b1:82:1c:4b:56:80:70:de:cc:83:61:23:4a:3b:89:
                    f7:83:da:9c:d4:d8:31:f4:b6:ba:a4:d6:32:50:23:
                    e4:2f:44:13:81:97:31:15:32:29:61:82:05:c5:a8:
                    2a:1b:23:cd:d8:62:f9:ba:14:36:dc:71:a0:43:f6:
                    ab:95:ca:6c:9f:39:a7:85:c5:97:06:54:7b:d9:c9:
                    ef:35:2c:e6:cd:0c:a2:7c:ee:1d:0f:a3:20:f5:3d:
                    3e:30:5b:98:00:5a:52:b0:25:f5:84:53:d7:ea:b1:
                    78:70:e0:72:19:6a:b0:ce:f5:7b:5e:73:77:b3:0b:
                    67:45:8b:51:c6:f2:18:4f:58:18:2e:06:09:d7:1c:
                    e7:69:c2:17:f2:e9:52:88:68:97:2c:ad:ce:fe:64:
                    ab:00:aa:90:0e:62:b7:e6:0e:67:70:1f:78:a1:39:
                    e5:58:a7:2d:c6:20:68:68:83:66:96:e1:ca:93:f1:
                    86:5d:11:a7:3e:fd:15:b3:88:66:88:78:6a:69:f3:
                    c5:f7:d2:0c:3f:67:b3:f3:46:59:35:a5:a0:c6:b1:
                    95:d0:a8:b1:f8:45:85:c2:63:d8:3d:42:49:5e:9d:
                    ee:9f:b6:15:cc:4f:83:ed:fe:7f:6e:c9:cb:92:ca:
                    7d:0b
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Key Usage: critical
                Digital Signature, Key Encipherment
            X509v3 Extended Key Usage:
                TLS Web Server Authentication, TLS Web Client Authentication
            X509v3 Basic Constraints: critical
                CA:FALSE
            X509v3 Subject Key Identifier:
                3A:0F:E7:E2:8A:E0:C0:47:DC:0F:91:5D:29:74:D6:3C:60:B1:33:D0
            X509v3 Authority Key Identifier:
                3E:34:8F:CA:29:73:2B:10:58:19:A1:EB:F8:BB:A9:C4:5E:64:E6:D1
            Authority Information Access:
                CA Issuers - URI:http://stg-r13.i.lencr.org/
            X509v3 Subject Alternative Name:
                DNS:haproxy.techtutorial.org
            X509v3 Certificate Policies:
                Policy: 2.23.140.1.2.1
            X509v3 CRL Distribution Points:
                Full Name:
                  URI:http://stg-r13.c.lencr.org/61.crl
            CT Precertificate SCTs:
                Signed Certificate Timestamp:
                    Version   : v1 (0x0)
                    Log ID    : DD:99:34:FC:A5:E7:24:80:C9:56:68:7D:81:34:99:08:
                                49:B2:49:F7:B5:69:D8:C7:BC:AB:3F:5C:C1:F3:6E:64
                    Timestamp : Nov 18 14:11:57.482 2025 GMT
                    Extensions: none
                    Signature : ecdsa-with-SHA256
                                30:44:02:20:64:E7:11:60:FD:83:4B:9B:E9:9D:59:9E:
                                80:53:F7:9F:38:48:7A:2D:78:5D:C3:6E:FC:61:44:2D:
                                3D:38:F1:4E:02:20:53:6E:F4:3D:58:FB:8C:AE:D5:A2:
                                30:9C:5B:39:1E:13:EE:B6:A0:6C:A2:48:A6:7F:63:5E:
                                D3:28:34:42:11:EA
                Signed Certificate Timestamp:
                    Version   : v1 (0x0)
                    Log ID    : 82:CD:CD:47:9E:77:E4:5D:14:AD:69:03:88:2C:41:13:
                                FC:81:C2:12:13:BE:C2:B3:D9:4E:9D:C7:CD:80:CD:FE
                    Timestamp : Nov 18 14:11:59.516 2025 GMT
                    Extensions: 00:00:05:00:05:27:7B:78
                    Signature : ecdsa-with-SHA256
                                30:46:02:21:00:A7:7D:7C:37:3D:33:7A:DE:83:3C:D8:
                                7E:E8:A8:F4:5D:B9:07:DA:EC:19:80:97:7E:CB:18:21:
                                66:97:86:FE:E3:02:21:00:DA:EC:6C:8C:90:86:A8:64:
                                5E:48:6D:87:54:69:1B:61:75:E5:CF:2D:CF:0E:15:04:
                                BB:30:56:76:90:53:89:0D
    Signature Algorithm: sha256WithRSAEncryption
    Signature Value:
        6e:2b:ad:a6:43:6b:7e:cf:84:24:16:a5:4e:df:c3:93:c1:22:
        4f:4f:2f:ff:c7:c7:9e:68:6b:bf:08:6f:78:7e:58:e7:d8:1e:
        b4:c8:86:a6:ba:ed:a5:c9:34:0f:f9:fa:59:dd:6a:98:2c:09:
        e8:e8:4b:55:13:59:98:ca:a1:92:98:2f:8b:1a:f9:13:c6:15:
        1d:77:c5:66:c3:5d:52:db:a4:c3:c7:c9:3c:09:e6:b2:70:82:
        39:6d:61:b8:cf:3f:e9:c8:25:ed:a3:bc:6c:c7:e0:70:f5:6b:
        b2:ee:26:6e:f6:6c:f9:65:4a:29:ac:e5:ec:d8:af:90:f0:da:
        13:11:8f:de:41:49:92:e0:3f:50:fc:98:8a:6f:bb:f8:a4:19:
        b0:79:17:7f:25:be:d0:ca:56:82:70:c9:80:ce:af:64:7c:89:
        d4:cc:4e:cf:49:9f:93:b1:cf:9a:01:a8:65:ce:0e:69:8f:30:
        d7:67:94:64:6e:38:60:b8:2a:60:eb:09:41:14:ae:30:24:ff:
        92:c2:95:9d:8f:86:7d:8d:4f:44:5c:0e:17:5a:ba:36:c7:b3:
        39:c3:8f:3c:16:b3:cd:ff:9b:24:80:27:c5:17:82:10:b2:fc:
        6c:b3:8b:12:dd:0b:74:b6:18:4b:5d:5d:2b:72:7a:1c:ee:b5:
        ad:77:b3:8b

We are successfully using LEGO for our COS stack in AWS which works great.

However, can you confirm that it doesn’t support having the DNS for .acme challenges being in a different domain than the root domain?

E.g. if the certificate is for foo.example.com, then, the acme DNS txt challenge record can’t be in foo.example.net ?

Why this matters:

It is a high security practice to assign API access to manipulate (add, remove, change etc.) records by automation frameworks, without having to hand that out to the actual domain that the SSL certs should end up in. Therefore, what you do, is that you create a domain who holds those DNS challenges where you let certbot/lego etc. manipulate without risking a hacker that gets hold of the API credentials to gain control over your real domain. This is strong security which we like to use. Therefore, it would be great if LEGO had this support (using an alternate domain) for dns01 challenges.

Let me know what you think.

Hi @erik-lonroth !

In fact Lego supports CNAME following with the DNS01 challenge (we are on the http01 challenge doc here.), so you can do what you described if you manually create the right CNAME record manually in the original root. I haven’t manually tested this tho, I can if you want me to. Recently we added a config option to the charm to allow disabling this.

I’m not sure it accepts cnames in a different domain?

Consider the use-case I mentioned.

Or perhaps I don’t understand you correctly.

@erik-lonroth

To get a cert for example.com (managed at Namecheap, in my test) without ever giving lego-operator your Namecheap API key, add a CNAME at Namecheap: _acme-challenge.example.com_acme-challenge.different-domain.com , where different-domain.com is a zone you host at Hetzner (the other provider I tested with). Configure the charm with plugin=hetzner and a secret containing only your Hetzner API token (hetzner-api-token=... ) Namecheap credentials never touch the charm.

When issuing the cert, LEGO follows the CNAME (on by default) and creates the TXT challenge record at _acme-challenge.different-domain.com via the Hetzner API, Let’s Encrypt then validates by querying _acme-challenge.example.com , which resolves through the same CNAME to find that TXT record, and issues the certificate for example.com anyway. Net result: you delegate proof-of-control to a provider whose API you’re willing to expose (Hetzner here), while keeping the registrar of the actual domain (Namecheap here) completely out of the loop, the same pattern works with any registrar/provider pair.

This should match your use case if I understood it correctly.