Wildcard-certificaat met Let's Encrypt via de DNS-controle

Gepubliceerd op 14 min leestijd

Een wildcard-certificaat laat zich niet via de webserver controleren, het gaat dwingend via een TXT-record in het DNS. Deze handleiding toont de handmatige en de automatische weg op Debian 13, Debian 12, Ubuntu 24.04 en Ubuntu 22.04, plus de fouten waarop het in de praktijk werkelijk stukloopt.

Een wildcard-certificaat dekt alle namen op één niveau af: shop.MijnDomein.nl, mail.MijnDomein.nl, klant-4711.MijnDomein.nl, ook namen die vandaag nog helemaal niet bestaan. Precies daarom werkt de vertrouwde weg via de webserver hier niet meer. Deze handleiding toont beide begaanbare wegen, de handmatige en de automatische, en gaat vooral in op de plekken waar het in de praktijk misgaat.

Alles wat hierna volgt is getest op Debian 13, Debian 12, Ubuntu 24.04 LTS en Ubuntu 22.04 LTS. De vier systemen verschillen bij Certbot duidelijker van elkaar dan de meeste handleidingen toegeven, daarom staat er verderop een tabel bij.

Waarom een wildcard-certificaat alleen via DNS kan

Let's Encrypt kent drie controlemethoden. Twee daarvan vallen voor wildcards af:

  • HTTP-01 plaatst een bestand onder http://name/.well-known/acme-challenge/token. Voor *.MijnDomein.nl bestaat er echter geen enkele naam waaronder dat bestand zou kunnen staan. De certificeringsinstantie zou oneindig veel hostnamen moeten opvragen.
  • TLS-ALPN-01 heeft hetzelfde probleem, ook die methode controleert een concrete host op poort 443.
  • DNS-01 controleert een TXT-record onder _acme-challenge.MijnDomein.nl. Wie dat record kan zetten, beheert de zone en daarmee ook elke willekeurige naam daaronder. Dat is het enige bewijs dat bij een sterretje past.

In de praktijk betekent dat: --apache, --nginx, --webroot en --standalone zijn voor wildcards onbruikbaar. Wie het toch probeert, krijgt deze melding:

Client with the currently selected authenticator does not support any
combination of challenges that will satisfy the CA. You may need to use an
authenticator plugin that can do challenges over DNS.

Dat is geen fout in uw configuratie, maar het juiste antwoord op een onmogelijk verzoek. Voor het normale geval met een handvol vaste namen blijft de weg via de webserver de juiste, en die beschrijven wij in het artikel over het gratis SSL-certificaat met Certbot.

Een tweede punt dat bijna alle handleidingen weglaten: een wildcard-certificaat dekt alleen de namen één niveau lager af. *.MijnDomein.nl geldt voor shop.MijnDomein.nl, maar noch voor MijnDomein.nl zelf, noch voor a.b.MijnDomein.nl. Het kale domein moet u er apart bij aanvragen, en dat heeft gevolgen voor het TXT-record, zie hieronder.

Vereisten en de pakketsituatie per distributie

U hebt roottoegang via SSH nodig, een domein waarvan u de zone beheert, en Certbot. Een draaiende webserver is voor het uitgeven niet nodig, poort 80 hoeft niet open te staan. Dat is een prettige bijkomstigheid: u kunt ook een certificaat uitgeven voor een dienst die helemaal niet aan het internet hangt.

Installeer Certbot en het DNS-gereedschap:

apt update
apt install -y certbot bind9-dnsutils

Het pakket bind9-dnsutils levert dig. De oude naam dnsutils is op alle vier de systemen nog slechts een overgangspakket dat naar bind9-dnsutils wijst. Controleer welke Certbot-versie u hebt gekregen:

certbot --version

De verschillen zijn aanzienlijk en ze bepalen welke weg er voor u eigenlijk openstaat:

SysteemCertbotPlug-ins uit de pakketbronnen
Debian 134.0.0cloudflare, desec, google, infomaniak, rfc2136, route53
Debian 122.1.0cloudflare, digitalocean, dnsimple, gandi, gehirn, google, linode, ovh, rfc2136, route53, sakuracloud
Ubuntu 24.042.9.0cloudflare, digitalocean, dnsimple, gandi, gehirn, google, infomaniak, linode, ovh, rfc2136, route53, sakuracloud
Ubuntu 22.041.21.0cloudflare, digitalocean, dnsimple, gandi, gehirn, google, linode, ovh, rfc2136, route53, sakuracloud

En dat is de verrassing: Debian 13 heeft de meeste DNS-plug-ins uit het archief gegooid. Wie onder Debian 12 met python3-certbot-dns-ovh of python3-certbot-dns-linode werkte en naar Debian 13 bijwerkt, vindt het pakket daar eenvoudigweg niet meer. Een apt upgrade over de distributiegrens heen verwijdert het, en de verlenging breekt af zonder dat iemand ernaar kijkt. Controleer dat vóór een distributiewissel.

Welke plug-ins op uw systeem werkelijk geladen worden, laat dit commando zien:

certbot plugins

De handmatige weg met een TXT-record

De handmatige weg heeft geen API-toegang nodig en werkt bij elke DNS-provider. Hij is geschikt om mee te experimenteren en voor zones die u toch al zelden aanraakt. Hij heeft één ernstig nadeel, waar wij zo op terugkomen.

Zet de TTL van het toekomstige TXT-record in uw zone vooraf laag, 60 tot 300 seconden. Dat kost niets en scheelt u later wachttijd. Daarna:

certbot certonly --manual --preferred-challenges dns \
  --cert-name mijndomein.nl \
  -d "*.MijnDomein.nl" -d MijnDomein.nl

De aanhalingstekens om "*.MijnDomein.nl" zijn verplicht. Zonder die tekens vervangt de shell het sterretje door bestandsnamen uit de huidige map, en vraagt Certbot certificaten aan voor uw bestanden. --cert-name is eveneens dringend aan te raden: anders leidt Certbot de naam van de certificaatmap af uit de eerste naam, en niemand wil hoeven zoeken naar een map met een sterretje erin.

Certbot pauzeert en toont zoiets als dit:

Please deploy a DNS TXT record under the name:
_acme-challenge.MijnDomein.nl.

with the following value:

gfj9Xq...Rg85nM

Nu komt het punt waarop de meeste pogingen stranden. U hebt twee namen aangevraagd, het sterretje en het kale domein. Dat zijn twee gescheiden controles, en beide komen uit op dezelfde recordnaam _acme-challenge.MijnDomein.nl, met twee verschillende waarden. Certbot zegt dat ook:

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.

Veel DNS-beheerschermen bieden voor dezelfde naam echter maar één invoerveld en vervangen de eerste waarde door de tweede. Dan staat er uiteindelijk maar één waarde in de zone, mislukt een van de twee controles, en noemt de foutmelding toch maar één naam. Laat uw beheerscherm geen twee TXT-records met dezelfde naam toe, dan valt de handmatige weg af. Gebruik dan de delegatie via CNAME, verderop.

Eerst controleren, dan pas Enter

Certbot wacht op uw bevestiging. Druk niet meteen op Enter. Open een tweede SSH-sessie en controleer eerst bij de autoritatieve nameserver, niet bij de lokale resolver:

dig +short NS MijnDomein.nl
dig +short TXT _acme-challenge.MijnDomein.nl @ns1.provider.example
dig +short TXT _acme-challenge.MijnDomein.nl @1.1.1.1
dig +short TXT _acme-challenge.MijnDomein.nl @8.8.8.8

Pas wanneer beide waarden in één opvraging verschijnen, en dat bij meerdere van elkaar onafhankelijke resolvers, drukt u op Enter. Een lege uitvoer betekent: het record is er nog niet. Zo ziet dat eruit bij een domein zonder record, de uitvoer blijft leeg:

dig +short TXT _acme-challenge.example.com @1.1.1.1

Waarom die omweg via de autoritatieve server? Vraagt u _acme-challenge op voordat u het record hebt aangemaakt, dan onthoudt uw resolver het niet-bestaan voor de duur van de negatieve TTL uit het SOA-record, vaak een uur. U ziet dan nog lang niets, hoewel het record er allang staat, en zoekt de fout op de verkeerde plek. De autoritatieve server heeft die cache niet.

Het nadeel van de handmatige weg

Een certificaat dat met --manual en zonder script is uitgegeven, verlengt zichzelf nooit. Bij de volgende automatische ronde staat er in het logbestand:

An authentication script must be provided with --manual-auth-hook when using
the manual plugin non-interactively.

Certbot slaat dit certificaat over en gaat met de andere verder, de exitcode blijft daarbij vaak onopvallend. U merkt het pas wanneer de browser klaagt. Reken er dus vast op dat u elke 60 tot 90 dagen met de hand dezelfde procedure herhaalt, of stap over op een van de automatische wegen.

De automatische weg via een plug-in van uw provider

Heeft uw DNS-provider een API en bestaat daarvoor een Certbot-plug-in, dan zet Certbot het TXT-record zelf, wacht, laat controleren en ruimt het daarna weer op. Dat is de weg die u voor productiesystemen wilt. Als voorbeeld nemen wij Cloudflare:

apt install -y python3-certbot-dns-cloudflare

Bewaar de inloggegevens buiten de webmap en bescherm ze meteen:

mkdir -p /root/.secrets/certbot
chmod 700 /root/.secrets/certbot

In /root/.secrets/certbot/cloudflare.ini hoort precies één regel:

dns_cloudflare_api_token = UwTokenHier

Daarna is dit verplicht:

chmod 600 /root/.secrets/certbot/cloudflare.ini

Anders waarschuwt Certbot bij elke ronde voor te ruime rechten. Gebruik een beperkt token met schrijfrecht op DNS-records, niet de globale accountsleutel. De globale sleutel werkt weliswaar nog, maar kan alles in uw account, en hij staat daarna in leesbare vorm op de server. Een token laat zich tot één enkele zone beperken en bij een incident afzonderlijk intrekken.

Het uitgeven:

certbot certonly --dns-cloudflare \
  --dns-cloudflare-credentials /root/.secrets/certbot/cloudflare.ini \
  --dns-cloudflare-propagation-seconds 60 \
  --cert-name mijndomein.nl \
  -d "*.MijnDomein.nl" -d MijnDomein.nl

De waarde voor --propagation-seconds is de wachttijd tussen het zetten van het record en de controlevraag. De standaardinstelling ligt op 10 seconden en is voor veel zones te krap. 60 seconden is een goede startwaarde, bij trage providers ook 120. Dit ene getal verklaart een groot deel van de sporadisch mislukkende verlengingen die verder niemand kan reproduceren.

Bij andere providers heet het pakket python3-certbot-dns-<provider>, de parameters volgen hetzelfde patroon. Kijk in de tabel hierboven of uw provider op uw distributie überhaupt aanwezig is.

Als het passende pakket ontbreekt

De voor de hand liggende reflex is pip install certbot-dns-watdanook. Op Debian 12, Debian 13 en Ubuntu 24.04 eindigt dat zo:

error: externally-managed-environment

× This environment is externally managed

Dat is opzet, geen defect. Op Ubuntu 22.04 loopt hetzelfde commando nog wel door, maar dan komen er pip-pakketten tussen de systeempakketten terecht, en bij de volgende apt upgrade passen de versies van Certbot en plug-in niet meer bij elkaar. Forceer dat niet met --break-system-packages.

De nette uitweg is de snap-uitvoering van Certbot, die de plug-ins meebrengt en zichzelf actueel houdt. Verwijder vooraf het distributiepakket, zodat er niet twee Certbots dezelfde map beheren:

apt remove -y certbot
snap install --classic certbot
ln -s /snap/bin/certbot /usr/bin/certbot
snap set certbot trust-plugin-with-root=ok
snap install certbot-dns-cloudflare

Belangrijk: de bestaande gegevens onder /etc/letsencrypt/ blijven behouden, de snap neemt ze over. Maar de systemd-timer heet daarna snap.certbot.renew.timer en niet meer certbot.timer. Wie dat over het hoofd ziet, heeft twee timers of helemaal geen.

Zonder plug-in van de provider: rfc2136 en CNAME-delegatie

Twee methoden werken onafhankelijk van de vraag wie uw DNS-provider is.

rfc2136 is de standaardweg voor dynamische DNS-actualisering met een TSIG-sleutel. Hij werkt met BIND, Knot en PowerDNS en zit op alle vier de systemen als pakket in de bronnen:

apt install -y python3-certbot-dns-rfc2136

Draait u uw DNS zelf, dan is dat de robuustste oplossing, omdat er geen externe dienst en geen HTTP-API aan te pas komt.

De CNAME-delegatie is het elegantere antwoord op twee problemen tegelijk. U legt in uw hoofdzone eenmalig een onveranderlijk record aan:

_acme-challenge.MijnDomein.nl.  CNAME  MijnDomein.nl.acme.andere-zone.nl.

Let's Encrypt volgt CNAME-ketens bij het zoeken naar het TXT-record. Het TXT-record ontstaat dus in de doelzone, en alleen daar heeft de server schrijfrechten nodig. Dat lost meteen meerdere punten op:

  • De inloggegevens op de webserver kunnen uw hoofdzone niet wijzigen. Een gekraakte webserver kan geen MX-records omleggen.
  • De doelzone mag meerdere TXT-waarden tegelijk voeren, ook als het beheerscherm van uw hoofdprovider dat niet aankan.
  • De doelzone kan een heel lage TTL hebben, zonder dat de hoofdzone daaronder lijdt.

De CNAME zelf wordt nooit meer aangeraakt, hij mag een hoge TTL hebben. Controleren kunt u hem met:

dig +short CNAME _acme-challenge.MijnDomein.nl @1.1.1.1

De verlenging automatiseren

Certbot brengt vanuit het pakket al een timer mee die twee keer per dag loopt. Een certificaat wordt daarbij alleen verlengd wanneer dat nodig is:

systemctl list-timers certbot.timer

Als hij niet draait:

systemctl enable --now certbot.timer

Het pakket levert op alle vier de systemen twee triggers mee: /lib/systemd/system/certbot.timer en daarnaast /etc/cron.d/certbot. Het cron-bestand controleert aan het begin zelf of de timer actief is en doet dan niets, er wordt dus niet dubbel verlengd. Een eigen renew-cronjob hebt u daarom niet nodig, dat zou de derde trigger voor dezelfde taak zijn.

Hier zit een verschil tussen de distributies dat u moet kennen. Certbot tot en met versie 3 verlengt zodra er minder dan 30 dagen resterende looptijd over is. Certbot 4.0, dus de uitvoering in Debian 13, verlengt in plaats daarvan zodra er nog een derde van de looptijd over is. Bij de tegenwoordig gebruikelijke 90 dagen levert dat hetzelfde moment op. Zodra Let's Encrypt korter lopende certificaten uitgeeft, gedragen de twee uitvoeringen zich verschillend, en alleen de nieuwe past zich automatisch aan.

Test het verloop in een droogloop. Daarbij wordt er niets uitgegeven en gaat er niets van uw limiet af:

certbot renew --dry-run

Een wildcard-certificaat wordt door Certbot niet in de webserverconfiguratie opgenomen, dat moet u eenmalig zelf doen. Leg een deploy-hook aan, zodat de webserver na elke verlenging het nieuwe certificaat ook werkelijk laadt. Schrijf die als bestand, niet als parameter:

mkdir -p /etc/letsencrypt/renewal-hooks/deploy

De inhoud van /etc/letsencrypt/renewal-hooks/deploy/reload-webserver.sh:

#!/bin/sh
systemctl reload nginx

Daarna uitvoerbaar maken:

chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-webserver.sh

Scripts in deze map lopen voor elk verlengd certificaat. De parameter --deploy-hook daarentegen wordt alleen weggeschreven in het verlengingsbestand van precies die certificaten die op dat moment behandeld werden. Certificaten die er later bijkomen hebben hem dan niet, en dat valt pas maanden later op.

Nog een waarschuwing over de API-inloggegevens: wordt het token bij de provider ingetrokken of verloopt het, dan mislukt de verlenging zonder dat er iets zichtbaar kapot is. Het certificaat geldt immers nog. Pas 30 dagen later staat de dienst stil. Controleer daarom af en toe of de certificaten werkelijk vernieuwd worden, en vertrouw niet op vervalwaarschuwingen per e-mail.

Waaraan u ziet dat het echt gelukt is

Dat het commando zonder fout doorliep, is geen bewijs. Deze drie controles zijn dat wel. Ten eerste het overzicht:

certbot certificates

Onder Domains moeten beide namen staan, *.MijnDomein.nl en MijnDomein.nl. Ontbreekt het sterretje, dan hebt u een gewoon certificaat gekregen en het alleen niet gemerkt.

Ten tweede de blik in het bestand zelf:

openssl x509 -noout -text -in /etc/letsencrypt/live/mijndomein.nl/fullchain.pem | grep -A1 "Subject Alternative Name"

Daar moet DNS:*.MijnDomein.nl opduiken. Het veld Subject Alternative Name is doorslaggevend, niet de Common Name, die moderne browsers helemaal niet meer uitlezen.

Ten derde, en dat is het eigenlijke bewijs, vraagt u een naam op die u zojuist zelf hebt verzonnen:

echo | openssl s_client -servername test-1234.MijnDomein.nl -connect MijnDomein.nl:443 2>/dev/null | openssl x509 -noout -subject -dates

Levert dat een geldig certificaat op en geen waarschuwing, dan werkt de wildcard werkelijk. Pas dan bent u klaar.

Veelvoorkomende fouten, letterlijk

  • "DNS problem: NXDOMAIN looking up TXT for _acme-challenge.MijnDomein.nl": het record bestaat niet, is nog niet verspreid, of u hebt het bij de verkeerde provider aangemaakt. Het meest voorkomende geval: het domein staat weliswaar bij provider A geregistreerd, maar de nameservers wijzen naar provider B, en het record ligt bij A. Doorslaggevend is uitsluitend wat dig +short NS MijnDomein.nl uitgeeft.
  • "Incorrect TXT record ... found at _acme-challenge.MijnDomein.nl": er staat wel een waarde, maar de verkeerde. Typisch na een afgebroken poging, wanneer de oude waarde nog in de zone staat, of wanneer het beheerscherm de tweede waarde over de eerste heen heeft geschreven. Oude _acme-challenge-records verwijderen en opnieuw beginnen.
  • "DNS problem: SERVFAIL looking up TXT ... the domain's nameservers may be malfunctioning": bijna altijd een kapotte DNSSEC-handtekening, bijvoorbeeld na een providerwissel waarbij het oude DS-record bij de registry is blijven staan. Eerst repareren, anders mislukt elke uitgifte.
  • "CAA record for MijnDomein.nl prevents issuance": de vaak over het hoofd geziene valkuil. Voor wildcards leest de certificeringsinstantie eerst issuewild uit. Wie issue "letsencrypt.org" heeft gezet, maar daarnaast een issuewild ";" heeft staan, krijgt wel gewone certificaten en geen wildcards. Controleren met dig +short CAA MijnDomein.nl.
  • "too many certificates (5) already issued for this exact set of identifiers": voor dezelfde namencombinatie zijn vijf certificaten in zeven dagen toegestaan. Test daarom met --dry-run of tegen de testomgeving via --test-cert. De blokkade loopt vanzelf af, opheffen kan niet.
  • Het beheerscherm toont het TXT-record, dig echter niet: sommige providers eisen dat wijzigingen aan de zone uitdrukkelijk gepubliceerd worden. Een opgeslagen record is niet vanzelf een actief record.
  • De recordnaam wordt dubbel: sommige beheerschermen hangen het domein automatisch aan. Vul daar alleen _acme-challenge in, anders ontstaat _acme-challenge.MijnDomein.nl.MijnDomein.nl. Een dig op de volledige naam legt dat meteen bloot.

Vooruitblik: DNS-PERSIST-01

Let's Encrypt werkt aan een nieuwe controlemethode met de naam DNS-PERSIST-01. In plaats van bij elke verlenging een vers token te publiceren, legt u eenmalig een blijvend record vast dat een bepaald ACME-account tot uitgifte machtigt. Daarna heeft de server voor de verlenging helemaal geen schrijftoegang meer nodig op het DNS. Voor wildcards zou dat een duidelijke winst aan veiligheid betekenen.

Volgens de planning van Let's Encrypt was de testomgeving voorzien voor eind eerste kwartaal 2026, de productieomgeving voor het tweede kwartaal. Of Certbot de methode ondersteunt, is op dit moment niet aangekondigd. Reken er dus nog niet op, maar houd het in de gaten wanneer u net bezig bent met de opbouw van een nieuw certificaatbeheer.

Samenvatting

Een wildcard-certificaat gaat uitsluitend via de DNS-controle, omdat een sterretje zich niet via één enkele webserver laat bewijzen. De handmatige weg via een TXT-record werkt overal, maar verlengt zichzelf nooit. De weg via een plug-in van de provider is de enige die u onbeheerd kunt laten lopen, en hij strandt in de praktijk meestal op een te korte wachttijd of op inloggegevens die stilletjes ongeldig zijn geworden. Als u maar één ding onthoudt: controleer het TXT-record bij de autoritatieve nameserver voordat u Certbot verder laat lopen, en reken bij sterretje plus kaal domein vast op twee records onder dezelfde naam.

Veelgestelde vragen

Waarom kan ik een wildcard-certificaat niet via Apache of nginx uitgeven?
Omdat de controle via de webserver een bestand onder een concrete hostnaam opvraagt. Een wildcard dekt echter willekeurig veel namen af, ook namen die nog helemaal niet bestaan. Let's Encrypt accepteert voor wildcards daarom uitsluitend de DNS-controle. Certbot meldt anders: Client with the currently selected authenticator does not support any combination of challenges that will satisfy the CA.
Dekt *.MijnDomein.nl ook MijnDomein.nl zelf af?
Nee. Een wildcard geldt alleen voor de namen precies één niveau daaronder. Het kale domein moet u er met een extra -d bij aanvragen. Evenmin geldt het twee niveaus lager, dus niet voor a.b.MijnDomein.nl.
Waarom heb ik twee TXT-records met dezelfde naam nodig?
Omdat het sterretje en het kale domein twee gescheiden controles zijn, die allebei uitkomen op _acme-challenge.MijnDomein.nl, met verschillende waarden. Beide moeten tegelijk in de zone staan. Volgens de DNS-standaard mag dat, maar veel beheerschermen van providers vervangen de eerste waarde door de tweede.
Hoe controleer ik of het TXT-record al verspreid is?
Met dig tegen de autoritatieve nameserver en daarnaast tegen minstens twee openbare resolvers, bijvoorbeeld dig +short TXT _acme-challenge.MijnDomein.nl @1.1.1.1. Vraag de naam niet op voordat u het record hebt aangemaakt, anders onthoudt uw resolver het niet-bestaan voor de duur van de negatieve TTL.
Verlengt een handmatig uitgegeven wildcard-certificaat zichzelf automatisch?
Nee. Zonder script meldt Certbot bij de automatische ronde: An authentication script must be provided with --manual-auth-hook when using the manual plugin non-interactively, en slaat het certificaat over. Voor onbeheerd gebruik hebt u een DNS-plug-in van uw provider of rfc2136 nodig.
Welke Certbot-DNS-plug-ins zijn er op Debian 13?
Debian 13 levert alleen nog cloudflare, desec, google, infomaniak, rfc2136 en route53. Pakketten als python3-certbot-dns-ovh, dns-linode of dns-digitalocean, die er onder Debian 12 en Ubuntu nog wel zijn, zitten daar niet meer bij. Controleer dat vóór een distributiewissel, anders breekt de verlenging ongemerkt af.
Wat levert een CNAME-delegatie van _acme-challenge op?
Die verplaatst het TXT-record naar een aparte zone. De server heeft dan geen schrijfrechten op uw hoofdzone nodig, een gekraakte webserver kan dus geen MX- of A-records wijzigen. Bovendien lost het het probleem op van beheerschermen die geen twee TXT-waarden onder dezelfde naam toelaten.

Lets-Encrypt Certbot Wildcard SSL-certificaat DNS DNS-01 Debian Ubuntu