SSD Nodes Learn Hosting plans →
Gidsen Matt ConnorDoor Matt Connor

Certbot installeren op Ubuntu 24.04 voor Nginx

Installeer Certbot via apt of snap op Ubuntu 24.04. Gebruik sudo apt install certbot python3-certbot-nginx en certbot --nginx. Voorkom timeoutfouten en dubbele timers.

Certbot installeren: apt of snap

Op Ubuntu 24.04 levert sudo apt install certbot python3-certbot-nginx een werkende Certbot die echte, publiekelijk vertrouwde Let's Encrypt-certificaten uitgeeft. De officiële documentatie van Certbot adviseert het gebruik van de snap; het verschil is klein, de snap volgt de upstream-releases, terwijl het archiefpakket de versie volgt die bij de LTS is geleverd en beveiligingsupdates ontvangt.

Kies er één. Twee kopieën van Certbot betekenen twee vernieuwingstimers die op dezelfde /etc/letsencrypt-boom zijn gericht, en de versie die u vergeet, is degene die voor verrassingen zorgt.

Het apt-pad:

sudo apt update
sudo apt install certbot python3-certbot-nginx

Dit installeert /usr/bin/certbot, de nginx-plugin, een certbot.service + certbot.timer-paar en een /etc/cron.d/certbot-item dat onder systemd niets uitvoert.

Het snap-pad:

sudo apt remove certbot python3-certbot-nginx
sudo snap install core && sudo snap refresh core
sudo snap install --classic certbot
sudo ln -s /snap/bin/certbot /usr/bin/certbot

De snap bevat een eigen timer, snap.certbot.renew.timer. Verwijder het apt-pakket voordat u de snap installeert.

Beide installaties gedragen zich daarna hetzelfde, afgezien van het exacte moment waarop ze besluiten te vernieuwen (behandeld onder Vernieuwing hieronder). Certbot 2.x gebruikt standaard ECDSA (P-256)-sleutels; geef --key-type rsa alleen door voor een client die geen ECDSA ondersteunt. Alle statusinformatie bevindt zich onder /etc/letsencrypt: archive/ bevat de echte sleutel- en certificaatbestanden, live/ bevat symbolische koppelingen naar de huidige bestanden, renewal/ bevat één configuratiebestand per certificaat en accounts/ bevat uw ACME-accountsleutel.

Wat HTTP-01 feitelijk doet en waarom poort 80 niet optioneel is

De HTTP-01 challenge is een callback. U vraagt Let's Encrypt om een certificaat voor example.com; zij lossen de naam op in de publieke DNS, openen een verbinding met poort 80 op het gevonden adres en vragen om http://example.com/.well-known/acme-challenge/<token>. Uw server antwoordt met de exacte token-inhoud die Certbot zojuist naar de schijf heeft geschreven. Dat is het volledige mechanisme. Hieruit vloeien drie consequenties voort, die de oorzaak zijn van de meeste mislukte uitgiftes.

  • Poort 80 moet bereikbaar zijn vanaf het publieke internet, niet alleen vanaf uw laptop. Een ufw-regel, een security group van een cloudprovider of een firewall in een VPS-console die alleen poort 443 openzet, blokkeert de uitgifte en elke toekomstige vernieuwing.
  • DNS moet al naar deze server wijzen. De validatieserver voert zijn eigen opzoeking uit vanaf het internet; uw /etc/hosts-vermeldingen en browsercache betekenen niets voor hen.
  • Als u een AAAA-record publiceert, wordt IPv6 als eerste geprobeerd. Let's Encrypt probeert het pas opnieuw via IPv4 als de IPv6-verbinding een time-out geeft. Een geweigerde IPv6-verbinding wordt niet opnieuw geprobeerd, en een verouderd AAAA-record dat wijst naar een host die de verbinding accepteert en iets anders serveert, leidt tot een harde foutmelding.

Redirects zijn toegestaan: de validatie volgt een HTTP-redirect naar HTTPS en het maakt niet uit of het certificaat aan de andere kant ontbreekt, verlopen is of self-signed is. Wat de server echter niet doet, is ergens anders beginnen dan op poort 80. Certbot heeft geen TLS-ALPN-01-implementatie, dus "gebruik gewoon 443" is geen uitweg.

Een authenticator kiezen: --nginx, --webroot, --standalone

--nginx is de juiste standaardkeuze wanneer Nginx al draait en het domein reeds bedient. Certbot analyseert uw configuratie, voegt tijdelijk een challenge-locatie toe, herlaadt Nginx, valideert en schrijft vervolgens de TLS-richtlijnen naar uw server block. Er is geen downtime.

sudo certbot --nginx -d example.com -d www.example.com

Gescript, voor een nieuwe server:

sudo certbot --nginx \
  -d example.com -d www.example.com \
  --agree-tos -m ops@example.com --no-eff-email \
  --redirect --non-interactive

--webroot is de juiste keuze wanneer u niet wilt dat Certbot uw Nginx-configuratie aanpast; bijvoorbeeld wanneer u deze vanuit een template genereert, in git beheert of pusht met Ansible. Certbot schrijft enkel het challenge-bestand naar een map die u al serveert.

sudo certbot certonly --webroot -w /var/www/example.com \
  -d example.com -d www.example.com \
  --deploy-hook "systemctl reload nginx"

--standalone is de juiste keuze wanneer er niets op poort 80 luistert: een mailserver, een API die enkel via 443 communiceert, of een first-boot script dat draait voordat Nginx bestaat. Certbot bindt poort 80 zelf voor enkele seconden. Als Nginx wel draait, mislukt dit; stop Nginx daarom rondom de uitvoering:

sudo certbot certonly --standalone -d mail.example.com \
  --pre-hook "systemctl stop nginx" \
  --post-hook "systemctl start nginx"

Deze hooks worden vastgelegd in de vernieuwingsconfiguratie van het certificaat, zodat hetzelfde stop/start-proces automatisch plaatsvindt bij vernieuwing.

Een serverblok dat werkt voor en na het bestaan van het certificaat

Het kip-en-ei-probleem: nginx weigert te starten met ssl_certificate wanneer dit verwijst naar een bestand dat niet bestaat, en Certbot kan niet valideren terwijl nginx offline is. Breng de site eerst online op poort 80.

server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;

    root /var/www/example.com;
    index index.html;

    location ^~ /.well-known/acme-challenge/ {
        root /var/www/example.com;
        default_type "text/plain";
        try_files $uri =404;
    }

    location / {
        try_files $uri $uri/ =404;
    }
}

Voer sudo nginx -t && sudo systemctl reload nginx uit, bevestig dat curl -I http://example.com/ antwoordt van buiten de server, en vraag daarna het certificaat aan. Vervolgens:

server {
    listen 80;
    listen [::]:80;
    server_name example.com www.example.com;

    location ^~ /.well-known/acme-challenge/ {
        root /var/www/example.com;
        default_type "text/plain";
    }

    location / {
        return 301 https://$host$request_uri;
    }
}

server {
    listen 443 ssl;
    listen [::]:443 ssl;
    server_name example.com www.example.com;

    ssl_certificate     /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
    include /etc/letsencrypt/options-ssl-nginx.conf;
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

    root /var/www/example.com;
    index index.html;

    location / {
        try_files $uri $uri/ =404;
    }
}

Het langere ACME-voorvoegsel wint het al van location /, waardoor de return 301 nooit een challenge-verzoek ziet. De ^~-modifier bewijst zijn nut elders: zodra dat voorvoegsel overeenkomt, slaat nginx regex-locaties volledig over, zodat een later toegevoegde regel zoals location ~ /\. { deny all; } de challenge niet kan blokkeren. Door die locatie op poort 80 te behouden, blijven vernieuwingen werken nadat de rest van de site is overgezet naar HTTPS-only.

Beide bovenstaande blokken serveren bestanden vanaf de schijf; als nginx in plaats daarvan fungeert als front-end voor een applicatie, wordt location / een proxy_pass-blok en behandelt het reverse proxy serverblok, regel voor regel de headers die de applicatie nodig heeft, terwijl de ACME-locatie en de TLS-richtlijnen exact hetzelfde blijven.

HTTP/2-syntaxis hangt af van uw nginx-versie. Ubuntu 24.04 levert nginx 1.24, dat de instelling inline vereist, listen 443 ssl http2;, en start niet als u de afzonderlijke http2 on;-richtlijn gebruikt. Debian 13 levert nginx 1.26, dat http2 on; vereist en enkel een waarschuwing geeft dat de inline-vorm verouderd is. Controleer eerst nginx -v.

Verwijs nginx naar live/, nooit naar archive/. De live/-symlinks worden bij elke vernieuwing opnieuw ingesteld; een hard pad naar archive/ zorgt ervoor dat u vastzit aan een certificaat dat verloopt terwijl u het gebruikt.

Wildcards betekenen DNS-01, en DNS-01 betekent een plugin

Een wildcard-certificaat (*.example.com) kan niet worden gevalideerd via HTTP-01; er is immers geen enkele hostnaam waarvan een bestand kan worden opgehaald. DNS-01 is de enige route: u bewijst het beheer door een _acme-challenge.example.com TXT-record te publiceren. Certbot heeft API-inloggegevens van uw DNS-provider nodig om dit onbeheerd uit te voeren; daarvoor zijn de provider-plugins bedoeld. De volledige handleiding voor wildcard-certificaten behandelt de werking van het TXT-record en de valkuil bij vernieuwing in de handmatige modus; hieronder volgt de korte versie voor Cloudflare.

sudo snap set certbot trust-plugin-with-root=ok
sudo snap install certbot-dns-cloudflare

Op het apt-pad is dat in plaats daarvan sudo apt install python3-certbot-dns-cloudflare. Inloggegevens plaatst u in een bestand dat alleen toegankelijk is voor root:

# /root/.secrets/cloudflare.ini
# then: sudo chmod 600 /root/.secrets/cloudflare.ini
dns_cloudflare_api_token = your_scoped_token_here

Beperk de scope van het token tot DNS-bewerkingsrechten voor die specifieke zone. Het is een sleutel tot uw DNS; ga er ook zo mee om.

sudo certbot certonly \
  --dns-cloudflare \
  --dns-cloudflare-credentials /root/.secrets/cloudflare.ini \
  -d example.com -d '*.example.com'

Plaats aanhalingstekens rondom de wildcard zodat uw shell deze niet als glob interpreteert. DNS-01 lost ook op wat HTTP-01 niet kan: certificaten voor hosts zonder publieke poort 80, een interne service, een machine die alleen bereikbaar is via een zelfgehoste WireGuard VPN op een VPS, of een beheerpaneel op een privé-interface.

Vernieuwing: de 90 dagen, de timer, de deploy hook

Let's Encrypt-certificaten zijn 90 dagen geldig. Het apt-pakket (Certbot 2.9.0) vernieuwt wanneer er minder dan 30 dagen resterend zijn. De snap volgt de huidige Certbot-versie, die sinds versie 4.1 ook via ACME Renewal Info (ARI) een aanbevolen vernieuwingstijd opvraagt bij Let's Encrypt en mogelijk eerder vernieuwt; voor een Let's Encrypt-certificaat betekent dit doorgaans ongeveer tweederde van de levensduur. Hoe dan ook krijgt u een venster van ongeveer 30 dagen waarin een mislukte vernieuwing een oplosbaar ongemak is in plaats van een uitval. Verwacht dat vernieuwingen vanaf volgend jaar vaker voorkomen: het standaardprofiel van Let's Encrypt stapt op 10 februari 2027 over op certificaten van 64 dagen, en op 16 februari 2028 op certificaten van 45 dagen. Let's Encrypt verstuurt geen e-mails meer met waarschuwingen over het verlopen van certificaten; niemand zal u hierop attenderen, dus monitoring is nu uw eigen verantwoordelijkheid.

Controleer de timer die bij uw installatie is meegeleverd:

systemctl list-timers 'certbot*' 'snap.certbot*'
sudo certbot certificates

certbot renew doorloopt elke configuratie in /etc/letsencrypt/renewal/, slaat alles over dat nog niet vernieuwd hoeft te worden, en vernieuwt de rest met de opties die in die configuratie zijn opgeslagen. Dit zijn de flags van de oorspronkelijke uitvoering, tenzij u deze sindsdien heeft gewijzigd met certbot reconfigure. Daarom is de eerste uitvoering van belang: deze wordt vastgelegd.

Het vernieuwen van het bestand op de schijf verandert op zichzelf niets; nginx blijft het oude certificaat vanuit het geheugen serveren totdat het een signaal krijgt om te herladen. Configureer eenmalig een deploy hook:

sudo tee /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh >/dev/null <<'EOF'
#!/bin/sh
set -e
nginx -t && systemctl reload nginx
EOF
sudo chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh

Alles wat uitvoerbaar is in renewal-hooks/deploy/ wordt uitgevoerd na elke succesvolle vernieuwing. De flag --deploy-hook doet hetzelfde voor één certificaat en slaat renew_hook = ... op in de bijbehorende vernieuwingsconfiguratie. certbot --nginx herlaadt voor u; --webroot- en --standalone-opstellingen doen dit niet. Een ontbrekende hook is precies de reden waarom een site een verlopen certificaat serveert terwijl certbot certificates vrolijk een vers certificaat rapporteert. Elke andere service die het certificaat bij het opstarten inleest, vereist dezelfde hook; een gecontaineriseerde applicatie zoals een Nextcloud VPS-installatie met Docker, TLS en backups heeft hier ook een eigen herstart- of herlaadstap nodig.

Testen van de vernieuwing in de praktijk

sudo certbot renew --dry-run

Hiermee voert u de volledige challenge uit tegen de staging-omgeving van Let's Encrypt: hetzelfde codepad, dezelfde firewall, dezelfde DNS, geen verbruik van uw limieten voor productie (staging heeft eigen, veel ruimere limieten) en er worden geen certificaten op de schijf opgeslagen. Als de test vandaag slaagt, zal de automatische vernieuwing over 60 dagen ook slagen, mits de configuratie van de server ongewijzigd blijft.

Een dry run bewijst niet dat uw reload-hook wordt uitgevoerd; dit gedrag verschilt per versie van Certbot. Test dit gedeelte handmatig: voer het hook-script direct uit, bevestig dat systemctl reload nginx slaagt en controleer sudo grep renew_hook /etc/letsencrypt/renewal/example.com.conf.

De fouten die u daadwerkelijk zult tegenkomen

Could not bind to IPv4 or IPv6., --standalone terwijl nginx al poort 80 bezet houdt. Gebruik --nginx of --webroot, of stop nginx tijdens de uitvoering. Bevestig wie de poort bezet houdt met sudo ss -lntp | grep ':80'.

Timeout during connect (likely firewall problem), Let's Encrypt kon poort 80 niet bereiken. Werk van buiten naar binnen: sudo ufw status (open deze met sudo ufw allow 'Nginx Full'), daarna de firewall van de VPS-provider zelf, en vervolgens DNS. Test vanaf een locatie die niet uw server is: curl -sSv http://example.com/.well-known/acme-challenge/test. Een verouderd AAAA-record veroorzaakt dezelfde melding.

unauthorized :: Invalid response from http://example.com/.well-known/acme-challenge/xyz: 404, poort 80 is bereikbaar, maar het token wordt niet geserveerd. Het verzoek kwam terecht in een ander server-blok (controleer welke eigenaar is van default_server), of de map die is doorgegeven aan -w is niet de map die nginx serveert. Plaats een bestand op /var/www/example.com/.well-known/acme-challenge/test en haal dit extern op; als dit een 404 geeft, was het certificaat nooit het probleem.

DNS problem: NXDOMAIN looking up A for example.com, de naam wordt niet publiek omgezet. Nieuwe records die nog niet zijn gepropageerd, of een record in een zone die uw registrar niet bedient.

too many certificates (5) already issued ..., een snelheidslimiet (rate limit), en de fout die mensen tegenkomen tijdens het debuggen in een lus. De melding benoemt de limiet en eindigt met een retry after tijdstip. Let's Encrypt beperkt dubbele certificaten, exact dezelfde set namen, tot vijf per week en biedt hiervoor geen omzeiling; alleen tijd heft deze blokkade op. Daarnaast staat het 50 nieuwe certificaten per geregistreerd domein per week toe; die limiet kan worden verhoogd door een verzoek tot verhoging in te dienen. Een verzoek voor exact dezelfde set namen als een bestaand certificaat telt als een verlenging en is hiervan vrijgesteld. Verlengingen die via ARI worden gecoördineerd, zijn vrijgesteld van elke limiet. Debug tegen de staging-omgeving met --dry-run.

nginx: [emerg] cannot load certificate "/etc/letsencrypt/live/example.com/fullchain.pem": No such file or directory, nginx is geconfigureerd voor een certificaat dat nooit is uitgegeven, of een certificaat dat is verwijderd met certbot delete. Zet het TLS-server-blok in commentaar, start nginx, geef het certificaat uit en herstel het blok.

open() "/etc/letsencrypt/options-ssl-nginx.conf" failed, dat bestand wordt meegeleverd met het nginx-pluginpakket. Op een certonly-systeem zonder python3-certbot-nginx, voegt u de plugin toe of vervangt u de include-regel door uw eigen ssl_protocols- en ssl_ciphers-instellingen.

Beheer op schaal

Eén certificaat kan maximaal 100 namen bevatten, en een enkele certbot --nginx -d a.example.com -d b.example.com ... is verleidelijk, totdat een verouderd DNS-record de validatie blokkeert en daarmee alle andere namen op dat certificaat onbereikbaar maakt. Afzonderlijke certificaten per site falen onafhankelijk van elkaar; dit is de gewenste situatie voor een server die meer dan een paar services host. Bij een groter aantal sites verdient een ACME-bewuste reverse proxy zichzelf terug: een Traefik reverse proxy die meerdere apps onder Docker Compose draait vraagt de certificaten zelf aan en vernieuwt deze, waardoor Certbot volledig overbodig wordt. Welke proxy u kiest voor deze taak is een eigen afweging, en het vergelijken van Nginx met Caddy en Traefik hangt grotendeels af van hoeveel configuratiewerk voor certificaten en per-app instellingen u door de proxy wilt laten afhandelen.

Maak een volledige back-up van /etc/letsencrypt, inclusief sudo tar -czf letsencrypt-$(date +%F).tar.gz -C /etc letsencrypt, met behoud van symlinks. Deze map bevat accounts/, uw ACME-account-key, die u niet identiek kunt regenereren. Verhuizen naar een nieuwe VPS komt dan neer op: rsync de map met -a, installeer Certbot, wijzig de DNS-records en voer certbot renew --dry-run uit voordat u de overstap definitief maakt.

Bij het opnieuw opbouwen van de server of een overstap naar een nieuwe LTS-versie wordt de renewal-timer niet automatisch overgenomen. Voer na elke migratie, het terugzetten van een snapshot of een distributie-upgrade systemctl list-timers 'certbot*' en een --dry-run uit. Als u dit overslaat, zal een site 89 dagen later op zwart gaan, om 3 uur 's nachts, met een certificaat waarvan iedereen aannam dat het zichzelf vernieuwde.

De bovenstaande HTTP-01-routes gaan uit van een machine die u beheert, met een publiek IP-adres en poort 80 open naar het internet; oftewel een VPS. DNS-01 vereist dit niet. De werking is voor beide methoden identiek.

Dezelfde stappen voor certificaten zijn van toepassing op Apache in plaats van nginx, en wanneer een publiek certificaat geen optie is, biedt een self-signed certificaat op Ubuntu uitkomst voor interne services.

FAQ

Moet ik poort 80 openstellen als mijn site alleen HTTPS aanbiedt?

Ja, voor de HTTP-01 challenge. Let's Encrypt start zijn validatieverzoek altijd op poort 80. Certbot heeft geen TLS-ALPN-01 implementatie, dus een firewall die alleen poort 443 openstelt, blokkeert zowel de eerste uitgifte als elke daaropvolgende automatische vernieuwing. Een redirect van poort 80 naar HTTPS is toegestaan; de validatie volgt deze redirect. De enige manier om poort 80 volledig te omzeilen is DNS-01, via een provider-plugin of met Certbot's --manual --preferred-challenges dns. Hierbij voegt u zelf het TXT-record toe, waardoor vernieuwing niet langer automatisch verloopt, tenzij u een --manual-auth-hook script aanlevert.

apt of snap, welke Certbot moet ik installeren voor nginx op Ubuntu 24.04?

Gebruik apt. sudo apt install certbot python3-certbot-nginx levert Certbot 2.9.0 op Ubuntu 24.04. Dit is actueel genoeg voor alles in deze handleiding, ontvangt beveiligingsupdates via unattended-upgrades en vereist geen snapd. Kies alleen voor de snap als u direct de nieuwste release nodig heeft of een DNS-plugin die uitsluitend als snap wordt gedistribueerd. Kies in ieder geval voor slechts één methode: twee installaties betekenen twee vernieuwingstimers die naar dezelfde /etc/letsencrypt-boom wijzen, en de vergeten installatie is degene die voor problemen zorgt.

Kan Certbot een wildcard-certificaat uitgeven voor nginx?

Alleen via DNS-01. Een wildcard zoals *.example.com heeft geen specifieke hostnaam om een challenge-bestand vanaf te halen, dus --nginx, --webroot en --standalone zijn niet bruikbaar. Installeer de plugin voor uw DNS-provider, plaats een API-token met beperkte rechten in een bestand dat alleen voor root leesbaar is, en voer certbot certonly --dns-cloudflare -d example.com -d '*.example.com' uit. Zet het wildcard-domein tussen aanhalingstekens om te voorkomen dat de shell dit als glob interpreteert.

Waarom serveert nginx nog steeds het oude certificaat na een succesvolle vernieuwing?

nginx houdt het certificaat in het geheugen en merkt het nieuwe bestand op de schijf pas op na een herlaadactie. certbot --nginx voert dit herladen voor u uit, maar --webroot en --standalone doen dit niet. Een vernieuwing kan dus slagen terwijl de browser nog steeds een verlopend certificaat ziet. Plaats een uitvoerbaar script in /etc/letsencrypt/renewal-hooks/deploy/ dat nginx -t && systemctl reload nginx uitvoert; dit script wordt na elke succesvolle vernieuwing aangeroepen.

Bewijst certbot renew --dry-run dat de vernieuwing zal werken?

Grotendeels. Het voert de daadwerkelijke challenge uit tegen de staging-omgeving, met dezelfde firewall, dezelfde DNS en hetzelfde codepad, zonder uw productie-rate-limits te raken en zonder certificaten op de schijf op te slaan. Een geslaagde test betekent dus dat het netwerkgedeelte in orde is. Het bewijst echter niet betrouwbaar of uw deploy-hook wordt geactiveerd. Test dit afzonderlijk: voer het hook-script handmatig uit en controleer sudo grep renew_hook /etc/letsencrypt/renewal/example.com.conf.