CodeWorlds
Powrót do kolekcji
Przewodnik14 min czytaniaZespół CodeWorlds

Caddy, serwer HTTP z automatycznym HTTPS

Caddy sam pobiera i odnawia certyfikaty TLS. Wersja 2.11.4, licencja Apache 2.0, Caddyfile, on-demand TLS i uczciwe porównanie z nginx plus certbot.

Caddy, serwer HTTP z certyfikatami TLS w środku

Caddy to serwer HTTP napisany w Go, który sam pobiera i odnawia certyfikaty TLS. Dwie linie konfiguracji wystarczą, żeby dostać działające HTTPS z odnawianiem w tle. Bieżące wydanie to 2.11.4, licencja jest jedna i permisywna, a projekt utrzymuje się ze sponsoringu, a nie ze sprzedaży zablokowanych funkcji.

Co Caddy robi inaczej niż nginx

W typowym zestawie z nginx certyfikat jest sprawą osobną od serwera. Certbot albo acme.sh gada z urzędem certyfikacji, zapisuje pliki gdzieś w katalogu systemowym, a potem uruchamia hak, który każe serwerowi przeładować konfigurację. Odnawianiem zajmuje się jednostka czasowa systemd albo wpis w cronie. To trzy niezależne elementy, z których każdy potrafi zawieść osobno: certbot może pobrać certyfikat, ale nie przeładować serwera, timer może przestać działać po aktualizacji systemu, a serwer może działać przez trzy tygodnie z wygasłym plikiem, bo nikt nie sprawdził logów.

Caddy przenosi to wszystko do wnętrza procesu serwera. Zarządzanie certyfikatami realizuje biblioteka CertMagic, wydzielona z tego samego projektu. Kiedy Caddy wczytuje konfigurację i widzi w niej publiczną nazwę domeny, sam nawiązuje sesję ACME, sam przechodzi wyzwanie, sam zapisuje certyfikat do skonfigurowanego magazynu i sam pilnuje terminu odnowienia w pętli działającej w tym samym procesie. Nie ma crona, nie ma haka przeładowującego, nie ma pytania „czy timer na pewno wystartował”.

Za tę wygodę płaci się konkretną cenę i lepiej ją znać z góry. Proces serwera przechowuje teraz klucz konta ACME i klucze prywatne certyfikatów, więc potrzebuje zapisywalnego i trwałego katalogu danych. Serwer musi mieć dostęp sieciowy do urzędu certyfikacji, co w środowiskach odciętych od internetu wymaga osobnego rozwiązania. Jeżeli certyfikaty i tak przychodzą z zewnątrz, na przykład z firmowego PKI albo z terminacji TLS na load balancerze chmurowym, automatyka Caddy'ego zaczyna przeszkadzać i trzeba ją jawnie wyłączyć.

Jest jeszcze różnica w tym, gdzie leży stan. Nginx sam z siebie jest bezstanowy: konfiguracja to plik, certyfikaty to inne pliki, a proces można ubić i podnieść bez konsekwencji. Caddy trzyma stan w katalogu danych i traktuje go jako część systemu, co zmienia sposób pakowania go do kontenera i sposób odtwarzania środowiska po awarii maszyny.

Druga różnica dotyczy domyślnych ustawień. Nginx domyślnie nasłuchuje na porcie 80 bez szyfrowania. Caddy domyślnie serwuje wszystko po HTTPS, przekierowuje ruch z portu 80 na 443 i nawet dla adresu localhost wystawia certyfikat z własnego, lokalnie zaufanego urzędu certyfikacji, instalując jego korzeń w magazynie zaufania systemu. Dla części zespołów to oszczędność tygodnia pracy, dla innych zaskoczenie przy pierwszym uruchomieniu w kontenerze.

Wersja, licencja i model finansowania

Bieżące wydanie to 2.11.4. Tag v2.11.4 w repozytorium nosi datę 1 czerwca 2026 roku według serwera modułów Go, a samo wydanie na GitHubie zostało opublikowane 3 czerwca 2026 roku. Ta dwudniowa różnica to normalny odstęp między otagowaniem a zbudowaniem artefaktów, ale jeśli automatyzujesz sprawdzanie wersji, warto wiedzieć, że dwa źródła podają dwie daty.

Repozytorium caddyserver/caddy powstało w styczniu 2015 roku, ma około 75,1 tysiąca gwiazdek, 4897 rozgałęzień i 272 otwarte zgłoszenia. Ostatnia zmiana w gałęzi głównej pochodzi z 19 sierpnia 2026 roku, repozytorium nie jest zarchiwizowane. Jak na projekt infrastrukturalny to zdrowe tempo.

Licencja jest przypadkiem czystym, co przy audycie zależności zdarza się rzadziej, niż powinno. Sprawdziłem trzy miejsca i wszystkie mówią to samo. W katalogu głównym repozytorium leży plik LICENSE z pełnym tekstem Apache License 2.0. Interfejs programistyczny GitHuba raportuje dla tego repozytorium identyfikator apache-2.0. Archiwum modułu pobrane z serwera proxy.golang.org dla wersji v2.11.4 zawiera plik LICENSE o rozmiarze 11358 bajtów, czyli ten sam tekst Apache. Sprawdziłem dodatkowo rzecz, o której łatwo zapomnieć: gotowe binarium ze strony wydania. Archiwum caddy_2.11.4_linux_amd64.tar.gz zawiera dokładnie trzy pozycje, LICENSE, README.md oraz plik wykonywalny caddy. Nie ma tu luki znanej z części projektów, gdzie repozytorium ma licencję, a dystrybuowana paczka już nie. Wydania są podpisane, obok każdego artefaktu leżą pliki .sig i .pem oraz zestawienie składników w formacie SBOM.

Pieniądze płyną ze sponsoringu i to jest zarazem mocna, i słaba strona projektu. Strona sponsorska deklaruje wprost, że w Caddy nie ma funkcji za murem płatnym, a wszystkie tiery kupują wsparcie i priorytet, nie dostęp do kodu. Cennik jest podany miesięcznie: Indie 25 dolarów, Indie Pro 50 dolarów, Startup 99 dolarów, Startup Pro 249 dolarów, Business 999 dolarów, Business+ 2999 dolarów. Istnieje jeszcze tier oznaczony jako Enterprise+, przy którym nie znalazłem podanej kwoty, więc jej nie zmyślam. Ta sama strona porównuje się z konkurencją, podając ceny Traefika i NGINX Plus, ale to liczby marketingowe samego Caddy'ego i nie sprawdzałem ich u tamtych dostawców, więc traktuj je jako cudzą deklarację.

Ryzyko takiego modelu jest realne. Rozwój zależy od garstki utrzymujących i od tego, czy sponsorzy będą płacić dalej. Nie kupujesz umowy wsparcia razem z oprogramowaniem, tylko osobno, i dopóki jej nie kupisz, nie masz żadnego terminu odpowiedzi na incydent. Licencja Apache 2.0 daje ochronę patentową i pełną swobodę użycia komercyjnego, więc od strony prawnej jesteś bezpieczny, ale prawo nie zastępuje utrzymania.

Caddyfile, czyli konfiguracja w kilku liniach

Caddy czyta natywnie JSON, a Caddyfile to adapter konfiguracji, który tłumaczy przyjazny format na ten JSON. Najkrótsza sensowna konfiguracja produkcyjna ma dwie linie.

Code
CADDYFILE
example.com

reverse_proxy localhost:8080

To wszystko. Pierwsza linia to adres serwowanej witryny i zarazem sygnał dla Caddy'ego, że ma zdobyć certyfikat dla example.com. Druga przekazuje ruch do aplikacji nasłuchującej lokalnie. Przekierowanie z HTTP na HTTPS, nagłówki X-Forwarded-*, kontrola stanu upstreamu i odnawianie certyfikatu działają bez dodatkowych wpisów.

Realna konfiguracja rośnie, ale nie eksploduje.

Code
CADDYFILE
example.com {
	encode zstd gzip

	handle_path /api/* {
		reverse_proxy localhost:3000 localhost:3001 {
			lb_policy least_conn
			health_uri /healthz
		}
	}

	handle {
		root * /srv/www
		try_files {path} /index.html
		file_server
	}

	header {
		Strict-Transport-Security "max-age=31536000; includeSubDomains"
		X-Content-Type-Options nosniff
	}

	log {
		output file /var/log/caddy/example.log
		format json
	}
}

Jedna rzecz w tym pliku łamie intuicję każdego, kto przychodzi z nginx. Kolejność dyrektyw w pliku nie ma znaczenia. Caddy sortuje je według własnej, ustalonej listy, więc encode zawsze wykona się w określonym miejscu łańcucha niezależnie od tego, gdzie go wpiszesz. Jeżeli potrzebujesz kolejności dosłownej, użyj dyrektywy route, która traktuje swoją zawartość jako jedną, nieprzestawianą całość. Kolejność globalną można też nadpisać opcją order w bloku globalnym. Bloki handle są wzajemnie wykluczające się, czyli zadziała tylko pierwszy pasujący, i tym różnią się od route.

Do pracy z plikiem służą trzy polecenia. caddy fmt --overwrite porządkuje wcięcia, caddy validate --config Caddyfile sprawdza, czy konfiguracja da się wczytać, a caddy adapt --config Caddyfile --pretty pokazuje wynikowy JSON. To ostatnie jest najlepszym narzędziem do nauki, bo pokazuje, co Caddyfile faktycznie ukrywa. Warto zauważyć, że sama adaptacja jest słabszym sprawdzeniem niż walidacja: plik może poprawnie zamienić się w JSON, a mimo to wywalić się przy wczytywaniu, bo wskazany plik certyfikatu nie istnieje.

Jak działa automatyczne HTTPS

Automatyka włącza się wtedy, gdy Caddy zna nazwę hosta lub adres IP, który ma serwować. Źródeł tej wiedzy jest kilka: adres witryny w Caddyfile, dopasowanie hosta na najwyższym poziomie w konfiguracji JSON, flagi wiersza poleceń --domain albo --from, wreszcie loader certyfikatów automate.

Równie ważne jest to, co automatykę wyłącza. Prefiks http:// przed adresem witryny, brak jakiejkolwiek nazwy w konfiguracji, nasłuch wyłącznie na porcie HTTP, ręczne wczytanie certyfikatów oraz jawne wyłączenie opcją globalną. Opcja auto_https przyjmuje cztery wartości: off, disable_redirects, ignore_loaded_certs i disable_certs. Rozróżnienie między nimi bywa kluczowe, bo za load balancerem chcesz zwykle wyłączyć samo przekierowanie, a nie całą maszynerię.

Dla publicznych nazw DNS certyfikat pochodzi z publicznego urzędu ACME, domyślnie Let's Encrypt albo ZeroSSL, z automatycznym przełączeniem na drugiego, gdy pierwszy zawiedzie. To ma praktyczną konsekwencję, o której dokumentacja uprzedza wprost: Let's Encrypt może wysłać ci maila o zbliżającym się wygaśnięciu, mimo że Caddy odnowił certyfikat u ZeroSSL. Dla localhost, adresów IP i nazw wewnętrznych Caddy wystawia certyfikat z własnego urzędu i próbuje zainstalować jego korzeń w magazynie zaufania systemu, o co może poprosić o hasło. Nazwy kończące się na .ts.net są delegowane do lokalnie działającego Tailscale zamiast do ACME.

Code
CADDYFILE
{
	email admin@example.com
	acme_ca https://acme-staging-v02.api.letsencrypt.org/directory
	key_type p256
	renewal_window_ratio 0.33
	storage file_system /var/lib/caddy
	admin localhost:2019
	servers {
		protocols h1 h2 h3
		trusted_proxies static private_ranges
		trusted_proxies_strict
	}
}

Opcja storage zasługuje na uwagę, bo odpowiada za najczęstszą awarię produkcyjną. Katalog danych musi być zapisywalny i trwały, inaczej każdy restart kontenera oznacza nowe konto ACME i nowy certyfikat, a limity Let's Encrypt są ostre. Ta sama opcja rozwiązuje problem klastra: kilka instancji Caddy'ego wskazujących ten sam magazyn samo koordynuje wydawanie certyfikatów, dzieli klucze i staple OCSP, bez żadnego dodatkowego uzgadniania. Dostępne są moduły magazynu oparte o zewnętrzne bazy, w tym o PostgreSQL czy Redis, ale to już wtyczki spoza standardowego wydania.

Certyfikaty wieloznaczne działają tylko wtedy, gdy gwiazdka stoi w skrajnie lewej etykiecie nazwy. *.example.com się kwalifikuje, a sub.*.example.com czy foo*.example.com już nie, i jest to ograniczenie publicznej infrastruktury kluczy, a nie Caddy'ego. Wyzwanie DNS, potrzebne do certyfikatów wieloznacznych i do serwerów bez publicznego portu 80, wymaga modułu konkretnego dostawcy DNS, którego w standardowym binarium nie ma.

On-Demand TLS i domeny klientów

To jest funkcja, dla której wiele firm sięga po Caddy'ego zamiast po cokolwiek innego. Zwykły tryb pobiera certyfikaty przy wczytywaniu konfiguracji, więc wszystkie nazwy muszą być znane z góry. On-Demand TLS odwraca ten porządek: certyfikat powstaje w trakcie pierwszego uzgadniania TLS dla nazwy, której serwer jeszcze nie zna. Uzgadnianie jest wstrzymywane na kilka sekund, certyfikat trafia do pamięci podręcznej i kolejne połączenia są już szybkie.

Zastosowanie jest oczywiste dla każdego produktu z domenami własnymi klientów. Klient ustawia rekord CNAME na twoją infrastrukturę, a certyfikat pojawia się sam, bez wpisu w konfiguracji i bez przeładowania serwera. Podobny problem rozwiązują usługi tunelowe pokroju Portless czy Cloudflare, tyle że tam certyfikat jest częścią oferty dostawcy i płacisz za nią abonamentem.

Mechanizm trzeba ograniczyć, bo inaczej każdy, kto skieruje dowolną domenę na twój adres IP, zmusi serwer do próby pobrania certyfikatu i wyczerpie limity urzędu. Caddy wymaga tego jawnie i nie pozwoli uruchomić on-demand bez restrykcji. Podstawową restrykcją jest punkt końcowy ask.

Code
CADDYFILE
{
	on_demand_tls {
		ask http://localhost:9000/tls-permission
	}
}

https:// {
	tls {
		on_demand
	}
	reverse_proxy localhost:8080
}

Kontrakt tego punktu końcowego jest prosty i dobrze udokumentowany. Caddy wysyła żądanie HTTP pod podany adres, dokładając ciąg zapytania ?domain= z nazwą domeny z uzgadniania. Kod odpowiedzi z zakresu 2xx oznacza zgodę na wydanie certyfikatu, każdy inny przerywa uzgadnianie z błędem. Punkt końcowy musi odpowiadać szybko, bo blokuje połączenie klienta, więc odpytywanie bazy przy każdym żądaniu bez pamięci podręcznej jest złym pomysłem. Restrykcje są globalne i nie da się ich ustawić osobno dla witryny.

Admin API, xcaddy i granice ekosystemu

Caddy wystawia interfejs administracyjny na localhost:2019. Adres można zmienić opcją admin albo zmienną środowiskową CADDY_ADMIN. Przez ten interfejs podmienia się całą konfigurację albo jej fragmenty, bez restartu procesu i bez zrywania połączeń.

Code
Bash
# podgląd aktywnej konfiguracji jako JSON
curl -s localhost:2019/config/ | jq

# zamiana Caddyfile na JSON i wysłanie go do działającego procesu
caddy adapt --config Caddyfile --pretty > caddy.json
curl -X POST localhost:2019/load \
  -H "Content-Type: application/json" \
  --data-binary @caddy.json

# ta sama operacja skrótem z wiersza poleceń
caddy reload --config Caddyfile

# start z ostatnią zapisaną konfiguracją po restarcie maszyny
caddy run --resume

# co dokładnie jest wkompilowane w to binarium
caddy build-info

# własne binarium z modułem DNS dla Cloudflare
xcaddy build --with github.com/caddy-dns/cloudflare

Ostatnia komenda prowadzi do najpoważniejszego ograniczenia projektu. Caddy nie ładuje wtyczek dynamicznie. Każdy moduł jest wkompilowany w binarium, więc dodanie obsługi wyzwania DNS, magazynu w bazie czy niestandardowego uwierzytelniania oznacza zbudowanie własnego pliku wykonywalnego narzędziem xcaddy i zainstalowanie Go na maszynie budującej. Aktualizacja Caddy'ego to wtedy przebudowanie obrazu, a nie apt upgrade. W zamian dostajesz jeden plik bez zależności systemowych, który da się skopiować gdziekolwiek.

Rozmiar ekosystemu jest drugim ograniczeniem i nie ma sensu go upiększać. Rejestr wtyczek prowadzi sam projekt i wystawia go publicznie pod adresem caddyserver.com/api/packages, więc liczbę można sprawdzić samodzielnie w każdej chwili. Oficjalny rejestr wtyczek Caddy'ego zawiera 319 zarejestrowanych pakietów. Nginx z Apache mają za sobą dwie dekady modułów, wpisów na blogach i gotowych fragmentów konfiguracji do skopiowania. Widać to w liczbach z Stack Overflow: tag caddy ma 369 pytań, traefik 2266, nginx 54493, a apache 91489. Kiedy potrzebujesz czegoś typowego, dokumentacja Caddy'ego jest lepsza niż nginksowa i wystarcza w zupełności. Kiedy potrzebujesz czegoś dziwnego, siedzisz na forum projektu i czekasz, zamiast znaleźć odpowiedź w dwie minuty. To jest prawdziwy koszt wyboru mniejszego narzędzia.

Warto to zestawić z platformami, które i tak zdejmują z ciebie warstwę serwera. Railway, Fly.io i Coolify same terminują TLS dla twoich aplikacji, więc konfigurowanie Caddy'ego wewnątrz kontenera bywa tam dublowaniem pracy.

Caddy a alternatywy

KryteriumCaddynginx plus certbotTraefik
Certyfikaty TLSw procesie serwera, bez cronaosobne narzędzie, timer i hak przeładowującyw procesie serwera, bez crona
Format konfiguracjiCaddyfile lub JSONwłasny język dyrektywYAML, TOML lub etykiety kontenerów
Przeładowanie konfiguracjiHTTP API na porcie 2019sygnał do procesuobserwacja plików i API dostawcy
Wtyczkiwkompilowane przez xcaddy, 319 w rejestrzemoduły dynamiczne i kompilowane, dwie dekady dorobkuwkompilowane, mniejszy zbiór
LicencjaApache 2.0, zero funkcji płatnychBSD dwuklauzulowa, wariant Plus komercyjnyMIT, wariant Hub komercyjny
Pytania na Stack Overflow369544932266
Wsparcie płatnesponsoring od 25 dolarów miesięcznieumowa na NGINX Plusumowa na Traefik Hub

Kolumna trzecia zasługuje na zdanie więcej, bo różnica z Traefikiem nie leży w certyfikatach, które oba robią tak samo, tylko w źródle tras: Caddy czyta je z pliku, który sam piszesz, a Traefik wykrywa usługi w Dockerze czy Kubernetesie i buduje trasy z ich etykiet. Przy statycznym zestawie usług plik wygrywa czytelnością, przy zmiennym środowisku kontenerowym wykrywanie oszczędza warstwę szablonów. Dwie wady Traefika trzeba jednak znać: jego magazyn certyfikatów to zwykły plik acme.json, więc przy wielu replikach automatyczne HTTPS nie zadziała bez płatnego dodatku, a nazwa produktu płatnego zmieniła się z Enterprise na Hub i starsze poradniki wciąż podają tę pierwszą.

Typowe błędy

Ulotny magazyn w kontenerze to błąd numer jeden. Jeżeli katalog danych Caddy'ego leży w warstwie zapisywalnej obrazu, każdy restart oznacza nowy klucz konta i nowe żądanie certyfikatu. Po kilku wdrożeniach dziennie wchodzisz na limit urzędu certyfikacji i zostajesz z witryną bez certyfikatu na kilka godzin. Zamontuj wolumen i wskaż go opcją storage file_system.

Zamknięty port 80 przy założeniu, że skoro serwujesz tylko HTTPS, to port 80 jest zbędny. Wyzwanie HTTP-01 wymaga portu 80 od strony publicznej. Alternatywą jest wyzwanie TLS-ALPN-01 na porcie 443 albo wyzwanie DNS, ale to ostatnie potrzebuje wkompilowanego modułu dostawcy DNS.

Testowanie na produkcyjnym urzędzie certyfikacji. Przy debugowaniu konfiguracji ustaw acme_ca na katalog testowy Let's Encrypt. Certyfikaty stamtąd nie są zaufane przez przeglądarki, ale limity są znacznie luźniejsze i nie zablokujesz sobie domeny na tydzień.

Oczekiwanie, że dyrektywy wykonają się w kolejności z pliku. Caddy sortuje je sam. Jeżeli logika zależy od kolejności, opakuj ją w route.

Postawienie Caddy'ego za innym proxy bez konfiguracji zaufania. Bez trusted_proxies adres klienta w logach będzie adresem proxy, a nagłówek X-Forwarded-For można podszyć. Jeżeli proxy dokleja adresy z prawej strony, włącz dodatkowo trusted_proxies_strict.

Brak prefiksu http:// tam, gdzie faktycznie chcesz zwykłego HTTP, na przykład na wewnętrznym punkcie zdrowia. Bez prefiksu Caddy potraktuje nazwę jako kandydata do certyfikatu i będzie próbował go zdobyć, zaśmiecając logi błędami ACME.

FAQ

Czy Caddy nadaje się do ruchu produkcyjnego?

Tak, i robi to od lat, między innymi w produktach SaaS obsługujących dziesiątki tysięcy domen klientów przez on-demand TLS. Wąskim gardłem częściej okazuje się aplikacja za proxy niż sam serwer. Jeżeli mierzysz mikrosekundy przy statycznych plikach, nginx nadal wygrywa w syntetycznych testach, ale różnica rzadko jest tym, co ogranicza system.

Czy mogę używać Caddy'ego w firmie bez płacenia?

Tak. Licencja Apache 2.0 obejmuje całość kodu i wszystkie funkcje, bez wariantu okrojonego. Sponsoring kupuje wsparcie, priorytet zgłoszeń i ewentualne własne łatki, a nie dostęp do funkcji. Bez sponsoringu nie masz jednak żadnego umownego czasu odpowiedzi na incydent.

Co się dzieje, gdy urząd certyfikacji jest niedostępny?

Caddy próbuje kolejnego skonfigurowanego wystawcy, domyślnie przełączając się między Let's Encrypt a ZeroSSL. Ponieważ odnawianie startuje na długo przed wygaśnięciem, sterowane parametrem renewal_window_ratio, kilkugodzinna awaria jednego urzędu nie kończy się przerwą w działaniu witryny.

Czy przeniosę istniejącą konfigurację nginx jeden do jednego?

Nie. Nie ma sensownego konwertera, a modele obu serwerów różnią się na tyle, że tłumaczenie linia po linii prowadzi do dziwnych wyników. Praktyczna droga to napisanie Caddyfile od zera dla jednej witryny, porównanie zachowania i dopiero potem migracja reszty.

Jak dodać wtyczkę bez kompilowania na serwerze produkcyjnym?

Zbuduj obraz w potoku ciągłej integracji poleceniem xcaddy build --with <moduł> i wdrażaj gotowe binarium albo obraz kontenera. Sprawdzenie, co faktycznie znalazło się w pliku, daje polecenie caddy build-info.

Skąd wziąć wiarygodne informacje o wersji i licencji?

Wersję sprawdzisz na stronie wydań w repozytorium, a pełną dokumentację opcji na caddyserver.com/docs. Warunki sponsoringu i cennik wsparcia opisuje strona sponsorska projektu.

Czytaj dalej

Używamy cookies, żeby zwiększyć Twoje doświadczenia na stronie