dehydrated — klient ACME w Bashu z obsługą certyfikatów wildcard
Jeden skrypt w Bashu, cztery narzędzia obecne na każdym systemie i żadnej instalacji Pythona. Omawiamy jedenaście haczyków, aliasy wymagane przy certyfikatach wildcard, pułapkę certyfikatu, który nie obejmuje domeny nadrzędnej, nowe wyzwanie dns-persist-01 z jednym trwałym rekordem TXT zamiast dynamicznych aktualizacji DNS, certyfikaty na adres IP o siedmiodniowej ważności oraz to, co znaczy przejęcie utrzymania repozytorium przez komercyjne centrum certyfikacji.
Automatyzacja certyfikatów jest problemem rozwiązanym — dopóki serwer wygląda jak podręcznikowy przykład. Kiedy jednak trafiamy na maszynę klienta bez Pythona, na minimalny obraz kontenera, na urządzenie z niewielką ilością pamięci albo na system, w którym menedżer pakietów jest zablokowany polityką, standardowy klient ACME z rodziną zależności robi się problemem większym niż zadanie, które ma rozwiązać.
dehydrated (github.com/dehydrated-io/dehydrated) podchodzi do tego inaczej: jest jednym skryptem w Bashu, który do pracy potrzebuje openssl, curl, sed, grep, awk i mktemp. Wszystkie poza curl są praktycznie na każdym systemie. Cała reszta — obsługa kluczy, żądania do centrum certyfikacji, odnawianie — to około stu kilobajtów kodu powłoki.
Skrypt jest przy tym zgodny z zsh, obsługuje ACME v2 wraz z certyfikatami wildcard, cztery typy wyzwań i — od niedawna — certyfikaty na adres IP.
Stan projektu i zmiana, o której trzeba wiedzieć
Dane z API GitHuba na 5 października 2026:
- 6248 gwiazdek i 724 forki, repozytorium założone 5 grudnia 2015 — jedenaście lat,
- licencja MIT, ostatnie wydanie v0.7.2 z maja 2025, ostatni push z końca kwietnia 2026,
- 85 otwartych zgłoszeń, oficjalny kanał na IRC-u z mostkiem do Matriksa,
- commity z pierwszej połowy 2026 dokładają rzeczy niekosmetyczne: obsługę wyzwania
dns-persist-01, formatowanie adresów IPv6 pod wymogi Let's Encrypt i możliwość nadpisania nagłówka klienta przezCURL_OPTS.
Najważniejsza informacja o tym projekcie nie dotyczy jednak kodu. Repozytorium jest dziś oficjalnie utrzymywane przez ZeroSSL — komercyjne centrum certyfikacji sprzedające certyfikaty SSL. README nosi jego oznaczenia graficzne, a nota na końcu opisuje utrzymanie jako część zobowiązania firmy wobec rozwiązań TLS, wraz z odesłaniem do jej oferty darmowych i płatnych certyfikatów.
Jak to ocenić uczciwie? Kod pozostaje na MIT, a lista obsługiwanych centrów certyfikacji jest konfiguracją, nie wpisaną na stałe decyzją — parametr --ca przyjmuje adres albo gotowy skrót, a domyślną wartością w przykładowej konfiguracji jest letsencrypt. Nie ma tu więc uwiązania: klient utrzymywany przez jedno centrum certyfikacji domyślnie kieruje ruch do innego. Z drugiej strony warto wiedzieć, kto trzyma repozytorium, bo interesy komercyjne bywają widoczne dopiero w rzeczach, których nie zrobiono. Na dziś nie widzę śladu takiego wpływu w kodzie — a projekt przez dekadę był utrzymywany przez jedną osobę, więc firmowe zaplecze jest raczej wzmocnieniem niż zagrożeniem.
domains.txt: cała konfiguracja tego, co ma być podpisane
Lista certyfikatów mieszka w jednym pliku tekstowym, po jednym certyfikacie na linię. Pierwsza nazwa jest główna, pozostałe to nazwy alternatywne:
example.org
example.com www.example.com
example.net www.example.net wiki.example.netDo tego dochodzi mechanizm, który w praktyce jest ważniejszy, niż wygląda — aliasy, oznaczane znakiem >. Alias zastępuje nazwę główną jako nazwę katalogu z certyfikatem i jako klucz wyszukiwania konfiguracji dla tego certyfikatu. Dzięki temu ten sam zestaw domen może istnieć w kilku wariantach o różnej konfiguracji:
*.service.example.org service.example.org > star_service_rsa
*.service.example.org service.example.org > star_service_ecdsaWystarczy wtedy plik certs/star_service_rsa/config z wpisem KEY_ALGO="rsa" i drugi z algorytmem krzywej eliptycznej — i dostajesz dwa certyfikaty na te same nazwy, wydane różnymi algorytmami. Przy migracji ze RSA na krzywe eliptyczne, gdzie trzeba przez chwilę utrzymać oba, jest to dokładnie to, czego potrzeba.
Jest też katalog wrzutkowy domains.txt.d: pliki *.txt z tego katalogu są dołączane do listy w kolejności alfabetycznej nazw. Dla automatyzacji jest to funkcja pierwszej potrzeby, bo dodanie domeny nie wymaga edytowania istniejącego pliku — wystarczy położyć nowy. Dokumentacja dokłada jednak własne ostrzeżenie, które warto uszanować: zachowanie tej funkcji może się zmienić, bo nazewnictwo domains.txt.d wobec zmiennej konfiguracyjnej DOMAINS_D jest mylące.
Wildcard i pułapka, na którą wpada każdy
Certyfikaty wildcard przyszły z ACME v2 i dehydrated je obsługuje — z jednym wymogiem i jedną niespodzianką.
Wymóg: certyfikat, którego pierwszą (albo jedyną) nazwą jest domena wildcard, musi mieć ustawiony alias. Powód jest prozaiczny: nazwa główna służy jako nazwa katalogu, a katalog nie może nazywać się *. — dokumentacja zaznacza wprost, że aliasy nie mogą się od tego zaczynać.
*.service.example.com > star_service_example_comNiespodzianka jest w dokumentacji wyróżniona i zasługuje na to: taki certyfikat nie będzie prawidłowy dla service.example.com. Będzie prawidłowy dla foo.service.example.com, ale nie dla samej domeny, od której gwiazdka pochodzi. To jest właściwość samego standardu, nie tego narzędzia — ale to tutaj najłatwiej ją przeoczyć, bo w pliku konfiguracyjnym wygląda niewinnie.
Właściwy zapis dla „domena i wszystko pod nią" wygląda więc tak i nie wymaga aliasu, bo nazwa główna nie jest gwiazdką:
service.example.com *.service.example.comTen jeden certyfikat pokrywa oba przypadki. Warto tę linię traktować jako domyślny wzorzec i sięgać po wariant z aliasem tylko wtedy, gdy naprawdę chodzi wyłącznie o poddomeny.
Cztery wyzwania, w tym jedno nowe i wygodne
http-01 to domyślna droga: skrypt zapisuje plik w katalogu wskazanym przez WELLKNOWN, a serwer HTTP musi go udostępniać pod ścieżką weryfikacyjną. Najprostsze i wystarczające dla zwykłych domen.
dns-01 jest konieczne dla certyfikatów wildcard i wymaga skryptu haczyków, który założy rekord TXT. Dokumentacja opisuje kontrakt dokładnie: haczyk dostaje operację, domenę, token wyzwania (przy tym typie nieużywany) i wartość do wstawienia w rekord _acme-challenge. Podane jest też rozwiązanie dla dostawców bez API — haczyk wypisuje potrzebne dane i czeka na potwierdzenie z terminala, żeby dać czas na ręczne wprowadzenie rekordu i propagację. Mało elegancko, ale w praktyce ratuje sytuację przy dostawcy DNS, którego nie da się zautomatyzować.
dns-persist-01 to dodatek z marca 2026 i najciekawsza nowość w całym narzędziu. Zamiast dynamicznie zakładać i usuwać rekord TXT przy każdym żądaniu, tworzysz jeden trwały rekord i zostawiasz go w spokoju. Rekord nazywa się _validation-persist i zawiera identyfikator Twojego konta u centrum certyfikacji wraz z metadanymi:
_validation-persist.example.com. IN TXT (
"letsencrypt.org;"
" accounturi=https://acme-v02.api.letsencrypt.org/acme/acct/1234567890;"
" policy=wildcard"
)Konsekwencje są poważne i warto je nazwać. Skrypt haczyków przestaje być potrzebny, bo dehydrated nie wykonuje przy tym typie żadnych aktualizacji DNS. Znika więc cała klasa problemów: przechowywanie na serwerze poświadczeń do API dostawcy DNS, opóźnienia propagacji, limity zapytań, awarie w środku odnawiania. Adres konta odczytuje się raz z odpowiedzi rejestracji albo z pliku registration.json, a policy=wildcard pozwala na certyfikaty wildcard. Dla wielu wdrożeń, w których jedyną przyczyną używania dns-01 była potrzeba wildcarda, jest to wyraźnie prostsza droga.
tls-alpn-01 zamyka listę i ma osobny katalog na certyfikaty weryfikacyjne, ustawiany parametrem --alpn.
Dokumentacja dokłada do tego rozsądną uwagę, dodaną zresztą w kwietniu 2026: warto sprawdzić u swojego centrum certyfikacji, które metody weryfikacji obsługuje. Lista wyzwań w kliencie nie znaczy, że wszystkie są dostępne u każdego wydawcy.
Jeden katalog na wszystkie domeny
Przy http-01 jest wzorzec konfiguracji, który warto przyjąć od razu i nie wracać do tematu. Naiwne podejście — wskazanie WELLKNOWN na katalog dokumentów witryny — działa tylko przy jednej witrynie na serwerze. Przy kilku, a tym bardziej przy odwrotnym proxy albo równoważniku obciążenia, przestaje.
Właściwe rozwiązanie to jeden wspólny katalog dla wszystkich domen plus aliasy w konfiguracji serwera. W nginksie sprowadza się to do trzech linii, dodanych do dowolnego bloku server:
location ^~ /.well-known/acme-challenge {
alias /var/www/dehydrated;
}Dokumentacja podaje odpowiedniki dla Apache'a, Lighttpd i Hiawathy, ale idea jest wszędzie ta sama. Zysk polega na tym, że dodanie nowej domeny nie wymaga już żadnej zmiany w konfiguracji serwera HTTP — nowa nazwa dostaje ten sam alias, a skrypt zapisuje token do tego samego katalogu.
Jest tu jeszcze jedno wymaganie, o którym się zapomina przy witrynach wymuszających HTTPS: ścieżka weryfikacyjna musi być dostępna zwykłym HTTP na porcie 80. Przekierowanie na HTTPS zadziała, ale punktem wyjścia zawsze jest HTTP — więc reguła zapory blokująca port 80 „bo i tak wszystko idzie po TLS-ie" jest cichym sabotażem odnawiania certyfikatów.
Jedenaście haczyków
Cały mechanizm rozszerzeń to jeden skrypt wywoływany z nazwą operacji jako pierwszym argumentem. Przykładowy plik w repozytorium definiuje jedenaście funkcji i ta lista jest właściwie dokumentacją cyklu życia certyfikatu:
deploy_challengeiclean_challenge— założenie i usunięcie odpowiedzi na wyzwanie,deploy_cert— wywoływany po wydaniu certyfikatu, z pełnymi ścieżkami do klucza, certyfikatu, pełnego łańcucha i łańcucha pośredniego oraz znacznikiem czasu. To tutaj przeładowuje się nginksa,sync_cert— osobny haczyk do rozsyłania plików, przydatny, gdy certyfikat ma trafić na inne maszyny,unchanged_cert— wywoływany, gdy certyfikat nie wymagał odnowienia. Wygląda na zbędny, a jest ważny: pozwala sprawdzić, czy na serwerze docelowym leży ta wersja, którą powinien,deploy_ocsp— wdrożenie odpowiedzi OCSP przy zszywaniu,invalid_challengeirequest_failure— obsługa błędów. Tu wpina się powiadomienie na Slacka albo wpis w monitoringu, i to jest różnica między świadomością, że odnawianie padło, a odkryciem tego z wygasłego certyfikatu,generate_csr— własne generowanie żądania podpisania, dla przypadków zaawansowanych,startup_hookiexit_hook— początek i koniec przebiegu.
Osobno warto znać przełącznik HOOK_CHAIN="yes". Domyślnie haczyk jest wołany raz na każdą domenę, a przy włączonym łączeniu — raz na cały certyfikat, ze wszystkimi wyzwaniami w jednym wywołaniu. Przy DNS-ie jest to różnica między pięcioma wywołaniami API i pięcioma oczekiwaniami na propagację a jednym wywołaniem i jednym oczekiwaniem. Przy dostawcy z limitem zapytań bywa to różnica między działaniem a niedziałaniem.
Konfiguracja: kilka wartości, które trzeba przemyśleć
Skrypt szuka konfiguracji kolejno w /etc/dehydrated/config, /usr/local/etc/dehydrated/config, katalogu roboczym powłoki i katalogu, z którego został uruchomiony. Z kilkudziesięciu zmiennych te są istotne operacyjnie:
DEHYDRATED_USERiDEHYDRATED_GROUP— użytkownik, na którego skrypt się przełącza, wymuszany automatycznie przy uruchomieniu jako root. Dobre domyślne zachowanie w narzędziu, które i tak zwykle chodzi z crona roota,RENEW_DAYS="32"— odnawianie na trzydzieści dwa dni przed wygaśnięciem. Przy certyfikatach dziewięćdziesięciodniowych daje trzy podejścia, zanim zrobi się gorąco,PRIVATE_KEY_RENEW="yes"— i to jest wartość, którą trzeba świadomie zaakceptować albo zmienić, bo domyślnie klucz prywatny jest generowany od nowa przy każdym odnowieniu, a nie tylko podpisywany nowy certyfikat. Dla bezpieczeństwa lepiej, ale każdy mechanizm przypinający klucz publiczny przestanie działać,PRIVATE_KEY_ROLLOVER="no"— dodatkowy klucz zapasowy, właśnie dla scenariuszy z przypinaniem,KEY_ALGO=secp384r1— domyślnie krzywa eliptyczna; obsługiwane są teżrsaiprime256v1,PREFERRED_CHAIN— wybór łańcucha po nazwie wydawcy, do dziś przydatny przy starszych klientach mobilnych,ACME_PROFILE— profil żądania u centrum certyfikacji, potrzebny przy certyfikatach o skróconej ważności,KEEP_GOING— kontynuowanie po błędzie przy wielu certyfikatach. Bez tego jedna zepsuta domena blokuje odnowienie wszystkich pozostałych w przebiegu, co jest jedną z częstszych przyczyn cichej katastrofy,LOCKFILEoraz parametry--no-locki--lock-suffix— przy równoległych przebiegach dla różnych domen blokada wymaga rozróżnienia.
Praca codzienna to w zasadzie jedna komenda: dehydrated --cron podpisuje i odnawia to, czego brakuje, co się zmieniło albo co wygasa. Do tego --cleanup przenoszące nieużywane pliki do archiwum, --revoke do unieważniania i --env wypisujące zmienne konfiguracyjne do użycia w innych skryptach.
Certyfikaty na adres IP
Nowość, o której warto wiedzieć, bo dopiero niedawno stała się praktyczna. ACME pozwala wydawać certyfikaty dla adresów IP, a dehydrated obsługiwał identyfikatory IP od dawna — użyteczne stało się to, gdy Let's Encrypt publicznie udostępnił takie wydawanie. Zastosowania to sieci wewnętrzne, urządzenia, systemy tymczasowe i środowiska bez wiarygodnego DNS-u.
Ograniczenia są jednak dwa i oba istotne. Weryfikacja jest możliwa wyłącznie przez http-01, więc serwer musi być publicznie osiągalny pod tym adresem. I rzecz ważniejsza: Let's Encrypt wydaje certyfikaty na adresy IP tylko przez profil krótkoterminowy, a te są ważne siedem dni. Dokumentacja stawia z tego jasny wniosek: takie certyfikaty trzeba odnawiać często i odpowiednio ustawić harmonogram zadań. Wpis w cronie raz na dobę, wystarczający dla certyfikatów dziewięćdziesięciodniowych, przy siedmiodniowych jest ryzykowny.
Pułapki
- Certyfikat wildcard bez nazwy nadrzędnej nie obejmuje domeny nadrzędnej. Najkosztowniejsza pomyłka w tym narzędziu,
- Wildcard jako nazwa główna wymaga aliasu, a alias nie może zaczynać się od gwiazdki,
- Domyślnie klucz prywatny jest wymieniany przy każdym odnowieniu — sprawdź, czy nic w Twojej infrastrukturze go nie przypina,
- Bez
KEEP_GOINGjedna zepsuta domena zatrzymuje cały przebieg, a pozostałe certyfikaty po cichu się nie odnawiają, - Zacznij od adresu testowego centrum certyfikacji. README ostrzega o tym pogrubioną czcionką, bo limity zapytań Let's Encrypt są realne i wyczerpuje się je szybciej, niż się wydaje,
- Bez haczyków
invalid_challengeirequest_failurenie dowiesz się o awarii odnawiania — dopóki certyfikat nie wygaśnie, - Certyfikaty na adres IP są ważne siedem dni i tylko przez
http-01, - Zachowanie katalogu
domains.txt.dmoże się zmienić — dokumentacja mówi to sama, - Lista wyzwań w kliencie nie jest listą wyzwań u wydawcy; sprawdź, co obsługuje Twoje centrum certyfikacji,
- Wydania są rzadkie: v0.7.1 w 2022, v0.7.2 w 2025. Prace idą w gałęzi głównej, więc korzystanie z wydania oznacza brak części nowych funkcji —
dns-persist-01włącznie, - To wciąż skrypt powłoki. Osiemdziesiąt pięć otwartych zgłoszeń przy jedenastu latach istnienia to niewiele, ale diagnostyka błędu w stukilobajtowym pliku Bash jest tym, czym jest,
- Repozytorium należy do komercyjnego centrum certyfikacji; dziś bez wpływu na kod, ale warto o tym wiedzieć.
Gdzie to ma sens
- Serwery bez Pythona albo z zablokowanym menedżerem pakietów. Podstawowy powód istnienia tego narzędzia i wciąż najlepszy,
- Obrazy kontenerów, w których każdy megabajt widać. Jeden skrypt i
opensslwobec interpretera z zależnościami, - Wildcard dla środowisk poddomenowych. Panel klienta pod
*.app.klient.plto jeden wpis wdomains.txt, a zdns-persist-01— bez żadnych poświadczeń do API DNS na serwerze, - Rozsyłanie certyfikatów na kilka maszyn. Haczyk
sync_certplusunchanged_certdo weryfikacji daje to bez dodatkowego narzędzia, - Certyfikaty dla usług wewnętrznych po adresie IP, jeśli maszyna jest publicznie osiągalna i przyjmiesz siedmiodniowy cykl,
- Wdrożenia, w których liczy się audytowalność. Skrypt powłoki da się przeczytać w całości i pokazać zespołowi bezpieczeństwa klienta — czego o kliencie z pluginami napisanymi w Pythonie nie powie się z taką samą pewnością.
Kiedy sięgnąć po coś innego: jeśli serwer HTTP potrafi obsłużyć ACME sam (Caddy, Traefik, nowsze wersje nginksa z odpowiednim modułem), to osobny klient jest zbędną częścią ruchomą. A jeśli klient oczekuje wsparcia producenta i gotowych wtyczek do dostawców DNS, standardowy klient z ekosystemem wtyczek zaoszczędzi pisania haczyków.
Podsumowanie
- Jeden skrypt w Bashu plus
openssl,curl,sed,grep,awkimktemp, domains.txtto cała lista tego, co ma być podpisane, z aliasami po znaku>i katalogiem wrzutkowym do automatyzacji,- Wpisuj
domena *.domena, jeśli certyfikat ma obejmować także nazwę nadrzędną, - Aliasy dają kilka wariantów tego samego zestawu domen — na przykład osobno RSA i krzywe eliptyczne przy migracji,
dns-persist-01zdejmuje potrzebę haczyka i poświadczeń DNS — jeden trwały rekord_validation-persistzamiast dynamicznych aktualizacji,- Jedenaście haczyków obejmuje cały cykl życia, a
HOOK_CHAIN="yes"łączy wszystkie wyzwania certyfikatu w jedno wywołanie, - Wpnij powiadomienia w
invalid_challengeirequest_failure— to najtańsza rzecz, jaką można zrobić dla spokoju, - Ustaw
KEEP_GOING, jeśli jeden przebieg obsługuje wiele certyfikatów, - Domyślna wymiana klucza przy odnowieniu jest decyzją, nie przypadkiem,
- Testuj na adresie testowym wydawcy, zanim wyczerpiesz limity,
- Jeden wspólny katalog
WELLKNOWNplus alias w serwerze HTTP — i port 80 musi zostać otwarty, - Nowe funkcje są w gałęzi głównej, nie w wydaniach — sprawdź, której wersji faktycznie używasz.
Licencja: dehydrated jest na MIT — wolno używać komercyjnie, modyfikować, wdrażać u klientów i redystrybuować, przy zachowaniu noty licencyjnej i tekstu licencji. Przy jednym skrypcie powłoki bez zależności zewnętrznych jest to sytuacja licencyjnie najprostsza z możliwych: cała paczka to jeden plik, którego pochodzenie i warunki da się sprawdzić w minutę, bez drzewa zależności do audytu. Dwie rzeczy warto natomiast rozdzielić od licencji kodu. Po pierwsze, fakt utrzymywania repozytorium przez ZeroSSL nie zmienia niczego w prawach do kodu — MIT jest nieodwoływalne dla wydanych wersji, a wybór centrum certyfikacji pozostaje parametrem konfiguracji. Po drugie i praktycznie ważniejsze: licencja klienta nie mówi nic o warunkach wydawcy certyfikatów. Regulamin Let's Encrypt, ZeroSSL czy innego centrum, jego limity zapytań, wymóg akceptacji warunków (--accept-terms) i zasady dotyczące certyfikatów wydawanych dla domen klienta to odrębna umowa, którą zawiera się w chwili rejestracji konta. To ona, nie licencja skryptu, decyduje o tym, co wolno zrobić z wydanymi certyfikatami — i o tym warto powiedzieć klientowi przy wdrożeniu.