Fix It 'Til It's Broke

A poorly designed WP blog


Certificate Consolidation

*This tutorial is specific to Namecheap and certbot. There may be other providers that handle this similarly, but no guarantees. Turn away if you don’t think this applies to you.*

I have a certificate problem. I used to be ignorant. I still am… but I used to be, too.

I first started by buying a domain, and doing nothing with it for 4 months. Of course, namecheap really sold me on the idea that I would need to get a SSL certificate from them. So I did for a few dollars extra.

Then I discovered LetsEncrypt. And we all know how much I love FOSS, and people that just generally want to make the world (and internet) a better place by sharing their technology. So I threw my namecheap certificate in the trash, didn’t renew it, and have been using CertBot + LetsEncrypt ever since.

When I added my first subdomain I grabbed an additional certificate and added it to my NGINX config. Then christmas came, and I was self hosting a family christmas list that people wanted to be able to access from outside the house. So I grabbed another cert. Now I’m about to start hosting some more services (Joplin for note taking, among other things) and don’t want to have even more certificates and DNS records to manage. I also appreciate subdomains not being able to be accessed in public records, for bad actors to know exactly what subdomains to target from the beginning.

So it’s time to get a single wildcard certificate. This will require two acme challenges, and NameCheap doesn’t have this automated in any way. So it will be a multiple step process.

1: Turn off reverse proxy

Normally certbot can do autentication by spinning up an apache server, then shutting it down once complete. But my port 80 and 443 are already being used by NGINX. So I have to turn that off of the IP stack in the operating system will error out when trying to spin up apache. I don’t think this is actually necessary, but I do it out of habit.

*in Proxmox, the container running NGINX*

# service nginx stop

2: Begin certbot

A wildcard technically won’t work for the base domain without a sub tacked on the front. So you’ll have to add both “*.yourdomain.tld” and “yourdomain.tld” with -d flags to make it work.

* still in Proxmox*

# certbot certonly --server https://acme-v02.api.letsencrypt.org/directory --manual --preferred-challenges dns -d fitib.us -d *.fitib.us

Saving debug log to /var/log/letsencrypt/letsencrypt.log
Requesting a certificate for fitib.us and *.fitib.us
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Please deploy a DNS TXT record under the name:
_acme-challenge.fitib.us.
with the following value:
[some string of ascii characters]
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Press Enter to Continue

Don’t hit enter yet. We need to setup our challenge

3: Setup your acme challenge

Go to namecheap, login, and manage your domain. Go to the advanced DNS tab and create a TXT record.

Ignore the second TXT record for now, we’ll get to that later

Now we need to make sure that it takes before continuing with certbot. Open up a terminal somewhere else, and check for the TXT record manually. I’m forcing it to use google dns because I’m not sure my pihole instance will get updated as quickly, and I’m impatient. Also make sure to use the type (-t) flag and tell it it’s a TXT record specifically, or you’ll just get the other A/AAAA/etc. information that’s not helpful.

*on my debian based laptop*

$ dig @8.8.8.8 _acme-challenge.fitib.us -t TXT

; <<>> DiG 9.16.1-Ubuntu <<>> @8.8.8.8 _acme-challenge.fitib.us -t TXT
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 47714
;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 512
;; QUESTION SECTION:
;_acme-challenge.fitib.us.	IN	TXT
;; ANSWER SECTION:
_acme-challenge.fitib.us. 60	IN	TXT	[same ASCII characters I pasted in earlier]
;; Query time: 40 msec
;; SERVER: 8.8.8.8#53(8.8.8.8)
;; WHEN: Thu Jan 04 16:02:19 CST 2024
;; MSG SIZE  rcvd: 109

Or on windows. No output, because I didn’t need to run this.

nslookup -type=TXT _acme-challenge.mydomain.com

4: Back to certbot, repeat

Now go back into certbot and pick up where it left off. It should still be waiting for you.

*back in proxmox*

Press Enter to Continue
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Please deploy a DNS TXT record under the name:
_acme-challenge.fitib.us.
with the following value:
[another, different string of characters]
(This must be set up in addition to the previous challenges; do not remove,
replace, or undo the previous challenge tasks yet. Note that you might be
asked to create multiple distinct TXT records with the same name. This is
permitted by DNS standards.)
Before continuing, verify the TXT record has been deployed. Depending on the DNS
provider, this may take some time, from a few seconds to multiple minutes. You can
check if it has finished deploying with aid of online tools, such as the Google
Admin Toolbox: https://toolbox.googleapps.com/apps/dig/#TXT/_acme-challenge.fitib.us.
Look for one or more bolded line(s) below the line ';ANSWER'. It should show the
value(s) you've just added.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Press Enter to Continue

Did you read the instructions? Good. Do not replace the original TXT record with this string. Create an additional record. We should get 2 answers now once DNS updates.

*back on the laptop
dig @8.8.8.8 _acme-challenge.fitib.us -t TXT
; <<>> DiG 9.16.1-Ubuntu <<>> @8.8.8.8 _acme-challenge.fitib.us -t TXT
; (1 server found)
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 65151
;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 512
;; QUESTION SECTION:
;_acme-challenge.fitib.us.	IN	TXT
;; ANSWER SECTION:
_acme-challenge.fitib.us. 60	IN	TXT	"[second string, which matches]"
_acme-challenge.fitib.us. 60	IN	TXT	"[first string, still matches]"
;; Query time: 32 msec
;; SERVER: 8.8.8.8#53(8.8.8.8)
;; WHEN: Thu Jan 04 16:06:26 CST 2024
;; MSG SIZE  rcvd: 165

Good, there are now 2 TXT records returned, which is what the next check by certbot is looking for.

5: Closeout

Certbot should now finish successfully, and give you some instructions.

*back in proxmox*

Press Enter to Continue
Successfully received certificate.
Certificate is saved at: /[some path]/fullchain.pem
Key is saved at:         /[some path]/privkey.pem
This certificate expires on 2024-04-03.
These files will be updated when the certificate renews.
NEXT STEPS:
- This certificate will not be renewed automatically. Autorenewal of --manual certificates requires the use of an authentication hook script (--manual-auth-hook) but one was not provided. To renew this certificate, repeat this same certbot command before the certificate's expiry date.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
If you like Certbot, please consider supporting our work by:
 * Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
 * Donating to EFF:                    https://eff.org/donate-le
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

# service nginx start

Yes, that’s right. This process is not going to be handled with a cron job and auto-renew on its own like most things using certbot. It requires user input (creating the TXT records), and can’t be automated unless NameCheap creates some process/tool that’s compatible with certbot (which they have no financial incentive to do). Every 3 months I’ll have to go back in and do this myself. It’s not difficult, just a bit tedious. The good news is LetsEncrypt has my email (from generating the certificate) and ExpiryBot emails me a week or two before it expires.

Now we go back to the namecheap console and delete the TXT records that we no longer need. Then find your files and make sure the NGINX configuration points to them for each subdomain (I have a separate config file for each subdomain in my sites-available folder to turn them on/off easily). I’m not going to go into that here.