Come generare certificati punto stella gratis con acme.sh

Certificati del tipo *.domain.com

Aggiornata a giugno 2026

Un certificato "punto stella" (wildcard) è quello del tipo *.dominio.com: copre con un colpo solo tutti i sottodomini (www, mail, blog, cloud, quello che vi pare) senza doverne emettere uno per ciascuno. Comodissimo quando si hanno tanti servizi sotto lo stesso dominio.

La fregatura è che per un wildcard non si può usare la classica validazione via HTTP (quella in cui Let's Encrypt scarica un file dal vostro sito): l'unico modo è la validazione DNS, cioè dimostrare di controllare il dominio creando un record TXT su _acme-challenge. E per farlo in automatico serve che acme.sh sappia parlare con le API del vostro provider DNS.

Uso acme.sh invece di certbot perché è un solo script in shell, senza dipendenze Python, e supporta un'enormità di provider DNS già pronti (ovviamente non il mio, ma vabbè).

Installazione

# Scarica e installa acme.sh registrando l'account ACME con la vostra email
# (serve per gli avvisi di scadenza di Let's Encrypt)
curl https://get.acme.sh | sh -s email=email@email.com

L'installer si mette in /root/.acme.sh/, si aggiunge da solo al .bashrc e crea un cron giornaliero: da qui in poi i rinnovi sono automatici, non dovrete più ripensarci (i certificati Let's Encrypt durano 90 giorni, acme.sh li rinnova quando mancano ~30 giorni alla scadenza).

Notifiche Telegram

Comodo farsi avvisare su Telegram quando un certificato viene rinnovato (o se qualcosa va storto). Servono il token del bot e l'id della chat:

export TELEGRAM_BOT_APITOKEN="..."     # token del bot, da @BotFather
export TELEGRAM_BOT_CHATID="-..."      # id della chat/gruppo (negativo se è un gruppo)
acme.sh --set-notify --notify-hook telegram

Certificato singolo (validazione HTTP)

Per un dominio normale (non wildcard) va benissimo la validazione HTTP: acme.sh con --apache configura al volo Apache, mette il file di verifica sotto la webroot e lo rimette com'era a fine emissione. Mi sono fatto una funzione per non ricordare ogni volta tutti i parametri:

function installCert(){
    # --issue --apache: emissione con validazione HTTP-01 gestita via Apache
    /root/.acme.sh/acme.sh --issue --apache -d "$1"
    # --install-cert: copia i file dove dico io e, a ogni rinnovo automatico,
    # esegue il reloadcmd per far ricaricare i certificati ad Apache
    /root/.acme.sh/acme.sh --install-cert -d "$1" \
      --cert-file      /etc/acme/$1_cert.pem  \
      --key-file       /etc/acme/$1_privkey.pem  \
      --fullchain-file /etc/acme/$1_fullchain.pem \
      --reloadcmd     "service apache2 force-reload"
}

Si usa così: installCert dominio.com.

Certificato punto-stella (validazione DNS)

Qui sta il bello. Per il wildcard si passa alla validazione DNS1 col plugin del proprio provider nel mio caso dns_gidinet. Chiedo due nomi nello stesso certificato: il dominio nudo (dominio.com) e il wildcard ( *.dominio.com), così copro sia il dominio sia tutti i sottodomini.

function installCertStella(){
  # --dns dns_gidinet: crea il record TXT _acme-challenge via API di Gidinet.
  # --dnssleep 5: aspetta 5s che il record si propaghi prima di far verificare
  #   Let's Encrypt (se la verifica fallisce per propagazione lenta, alzatelo:
  #   --dnssleep 60 o più, dipende da quanto è pigro il vostro DNS)
  acme.sh --dnssleep 5 --issue --dns dns_gidinet -d "$1" -d "*.$1"
  /root/.acme.sh/acme.sh --install-cert -d "$1" \
    --cert-file      "/etc/acme/$1_cert.pem" \
    --key-file       "/etc/acme/$1_privkey.pem" \
    --fullchain-file "/etc/acme/$1_fullchain.pem" \
    --reloadcmd      "apache2ctl configtest && apache2ctl restart"
}

Anche questa si usa col dominio come argomento: installCertStella dominio.com.

Una piccola accortezza nel reloadcmd: apache2ctl configtest && apache2ctl restart fa il configtest prima del riavvio, così se la configurazione di Apache è rotta il restart non parte nemmeno — meglio un certificato non ricaricato che un web server web fermo.

Importante: perché dns_gidinet (o qualunque altro plugin DNS) funzioni, acme.sh deve avere le credenziali API del provider, esportate come variabili d'ambiente prima di lanciare l'emissione. Ogni provider ha le sue: l'elenco completo con i nomi esatti delle variabili è nel wiki di acme.sh, sezione dnsapi. acme.sh le salva poi in /root/.acme.sh/account.conf e le riusa da solo a ogni rinnovo, quindi le esportate una volta sola.

E se non ci fosse il plugin per il mio provider?

Se il provider non dispone delle API per modificare i DNS, è possibile creare una chiave TXT permanente, da inserire una sola volta manualmente, tramite il comando:

acme.sh --make-dns-persist-value -d example.com --dns-persist-wildcard

Se dispone di api, si può creare il proprio plugin, come ho fatto io. Infatti gidinet non è presente tra i plugin di acme.sh

Per crare un api custom basta creare il file ~/.acme.sh/dns_nomeprovider.sh in cui ci devono essere 2 funzioni:

Tutte e due hanno come parametri il dominio e il valore da inserire nel TXT.

Ecco per esempio il mio file per gidinet dns_gidinet.sh che utilizza SOAP:

#!/usr/bin/env sh
# shellcheck disable=SC2034
dns_myapi_info='Gidinet
Site: gidinet.com
Docs: blog.chalda.it
Options:
 USERNAME. utente di gidinet  gdnXXXXX 
 APIKEY. Impostata su https://www.gidinet.com/modules/private/account_password/
Issues: github.com/acmesh-official/acme.sh
Author: Chalda Pnuzig
'

USERNAME="gdnXXXXX"
APIKEY="XXXX"

APIKEY_B64=$(echo -n "$APIKEY" | base64)

dns_contabo_add() {
    fulldomain="${1?Errore: manca il sito}"
    txtvalue="${2?Errore: manca il txt}"
    _dns_contabo_send_soap "recordAdd" "$fulldomain" "$txtvalue"
}

dns_contabo_rm() {
    fulldomain="${1?Errore: manca il sito}"
    txtvalue="${2?Errore: manca il txt}"
    _dns_contabo_send_soap "recordDelete" "$fulldomain" "$txtvalue"
}

_dns_contabo_send_soap() {
    fn=$1
    fulldomain=$2
    txtvalue=$3
    domain=$(tldextract -j "$fulldomain"  | jq -r '.domain + "." + .suffix')
    
    _info "Using contabo"
    _debug fulldomain "$fulldomain"
    _debug domain "$domain"
    _debug txtvalue "$txtvalue"
    _debug fn "$fn"

    URL="https://api.quickservicebox.com/API/Beta/DNSAPI.asmx"

    SOAP_BODY=$(cat <<EOF
<?xml version="1.0" encoding="utf-8"?>
<soap12:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://www.w3.org/2003/05/soap-envelope">
  <soap12:Body>
    <${fn} xmlns="https://api.quickservicebox.com/DNS/DNSAPI">
      <accountUsername>${USERNAME}</accountUsername>
      <accountPasswordB64>${APIKEY_B64}</accountPasswordB64>
      <record>
        <DomainName>${domain}</DomainName>
        <HostName>${fulldomain}</HostName>
        <RecordType>TXT</RecordType>
        <Data>${txtvalue}</Data>
        <TTL>3600</TTL>
        <Priority>0</Priority>
      </record>
    </${fn}>
  </soap12:Body>
</soap12:Envelope>
EOF
    )

    RESPONSE=$(curl -s -X POST "$URL" \
      -H "Content-Type: application/soap+xml; charset=utf-8" \
      -d "$SOAP_BODY")

    _debug response "$RESPONSE"
}

Richiede l'installazione dei comandi jq e tldextract

Rinnovo

Non c'è niente da fare a mano: il cron creato in fase di installazione gira ogni giorno, controlla le scadenze, rinnova ciò che serve (riusando provider DNS e credenziali memorizzati) ed esegue il reloadcmd per ricaricare Apache. Se avete configurato Telegram, vi arriva pure il messaggio. Per vedere lo stato:

acme.sh --list                 # certificati gestiti e date di scadenza
acme.sh --renew -d dominio.com --force   # forzare un rinnovo subito, per provare

Commenti

Nessun commento ancora. Sii il primo!

Lascia un commento