Jak utworzyć plik .htpasswd: przewodnik po HTTP Basic Auth
Plik .htpasswd to magazyn poświadczeń po stronie serwera dla HTTP Basic Authentication: zwykły plik tekstowy, w którym każda linia to jedna para username:hash. Aby utworzyć plik .htpasswd, należy wygenerować tę zahaszowaną linię i zapisać ją w miejscu, z którego serwer WWW może ją odczytać. Są trzy sposoby:
- Polecenie
htpasswd(z pakietuapache2-utils/httpd-tools) — kanoniczne narzędzie. openssl passwd— dostępne niemal wszędzie, bez dodatkowego pakietu.- W przeglądarce — użyj generatora htpasswd, aby utworzyć wpis lokalnie, bez żadnej instalacji i bez wysyłania czegokolwiek przez sieć.
Poza samą komendą warto wiedzieć, jak działa uzgadnianie Basic Auth, jak utworzyć plik na trzy sposoby, który z pięciu formatów hash wybrać oraz jak wpiąć go w Apache, nginx, Docker, Kubernetes, Caddy i Traefik. Na koniec — jak to zabezpieczyć, żeby nie udostępnić pliku poświadczeń, który każdy może pobrać.
Czym jest plik .htpasswd?
Każda linia w pliku .htpasswd przechowuje poświadczenia jednego użytkownika jako parę rozdzieloną dwukropkiem. Nazwa użytkownika jest zapisana w niezmienionej postaci; hasło jest przechowywane wyłącznie jako jednokierunkowy hash, więc tekst jawny nigdy nie trafia na dysk. Budowa pojedynczej linii bcrypt wygląda tak:
admin : $2y$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy
│ │
└─ username └─ hash (algorithm prefix $2y$ + cost + salt + digest)
Najpierw nazwa użytkownika, potem pojedynczy :, a następnie hash. Nazwa użytkownika może mieć do 255 bajtów i nie może zawierać dwukropka, bo dwukropek jest separatorem pól. Hash niesie własny znacznik algorytmu jako prefiks ($2y$ dla bcrypt, $apr1$ dla Apache MD5, {SHA} dla SHA-1), więc serwer wie, jak go zweryfikować, bez dodatkowej konfiguracji.
Dla wielu użytkowników dodaje się jedną linię na użytkownika:
admin:$2y$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy
alice:$2y$10$3bQ8xY7tLp2mZ0xW5cR4fO9vK1jH6sD2nG8aQ5wE3rT7uI4oP1cm
bob:$apr1$mZ0xW5cR$4fK1jH6sD2nG8aQ5wE3rT2
Algorytmy można mieszać w jednym pliku. Serwer czyta każdą linię, wykrywa format na podstawie prefiksu i weryfikuje hash zgodnie z tym, czego użyto. Powyżej obok siebie stoją dwaj użytkownicy bcrypt i jeden apr1.
.htpasswd a .htaccess
Te dwa pliki bywają mylone, bo na Apache idą w parze, ale pełnią różne funkcje. .htaccess to plik konfiguracyjny Apache działający per katalog. Zawiera dyrektywy — w tym te, które włączają Basic Auth i wskazują na magazyn poświadczeń. .htpasswd to baza poświadczeń — same linie username:hash, bez konfiguracji.
W skrócie: .htaccess decyduje, że katalog wymaga logowania i gdzie znaleźć listę użytkowników; .htpasswd to ta lista użytkowników. nginx w ogóle nie używa .htaccess — jego konfiguracja Basic Auth znajduje się w bloku server lub location głównej konfiguracji, ale czyta ten sam format poświadczeń .htpasswd.
Jak działa HTTP Basic Authentication
HTTP Basic Authentication to uzgadnianie typu wyzwanie-odpowiedź wbudowane w specyfikację HTTP. Kto rozumie te trzy kroki, ten później bez trudu zdiagnozuje problem:
- Klient żąda chronionego zasobu bez poświadczeń.
- Serwer odpowiada
401 Unauthorizedi dołącza nagłówekWWW-Authenticate: Basic realm="..."— to jest wyzwanie. - Klient ponawia żądanie z nagłówkiem
Authorization: Basic <base64(user:password)>. Jeśli poświadczenia pasują do linii w pliku.htpasswd, serwer zwraca zasób.
To cały protokół. Nie ma formularza logowania, nie ma cookie sesji, nie ma tokenu. Każde kolejne żądanie niesie ten sam nagłówek.
Wyzwanie 401 / WWW-Authenticate
Nagłówek WWW-Authenticate robi dwie rzeczy. Token Basic mówi klientowi, którego schematu użyć, a ciąg realm oznacza przestrzeń ochrony. Przeglądarki pokazują tekst realm w oknie logowania („Witryna informuje: …”) i traktują go jako klucz pamięci podręcznej: poświadczenia podane dla jednego realm trafiają do innych adresów URL w tym samym realm, więc okno logowania nie wyskakuje na każdej stronie z osobna.
Oto surowa wymiana, przechwycona przez curl -i:
$ curl -i https://example.com/admin/
HTTP/2 401
www-authenticate: Basic realm="Restricted Area"
$ curl -i -u admin:s3cret https://example.com/admin/
HTTP/2 200
Nagłówek Authorization: Basic
Poświadczenie wysyłane przez klienta to base64(username:password). I tu pada najważniejszy fakt bezpieczeństwa dotyczący Basic Auth: base64 jest kodowaniem, a nie szyfrowaniem. Każdy może je odwrócić, więc poświadczenie podróżuje w praktyce w postaci tekstu jawnego. Ten obieg w obie strony łatwo sprawdzić samodzielnie:
# Encode the credential the way a browser does
printf 'admin:s3cret' | base64
# → YWRtaW46czNjcmV0
# Anyone who captures the header can decode it instantly
printf 'YWRtaW46czNjcmV0' | base64 -d
# → admin:s3cret
Właśnie ta odwracalność wymusza, by Basic Auth działało przez HTTPS — bez TLS hasło jest czytelne dla każdego na trasie sieciowej. Do ręcznego zbudowania lub odczytania tego nagłówka przyda się koder/dekoder Base64, który wykonuje tę samą transformację user:password w przeglądarce.
Jak utworzyć plik .htpasswd
Plik można utworzyć na trzy praktyczne sposoby. Wybór zależy od tego, co jest zainstalowane i gdzie hasło ma się znaleźć.
Za pomocą polecenia htpasswd
Plik wykonywalny htpasswd jest dostarczany w pakiecie narzędziowym Apache. Najpierw go zainstaluj:
# Debian / Ubuntu
sudo apt install apache2-utils
# RHEL / CentOS / Fedora
sudo yum install httpd-tools
Utwórz plik i jego pierwszego użytkownika. Flaga -c oznacza create i nadpisze istniejący plik — używaj jej tylko za pierwszym razem:
htpasswd -c /etc/nginx/.htpasswd admin
# prompts twice for the password, then writes the file
Aby dodać kolejnych użytkowników, pomiń -c, tak aby dopisywać zamiast nadpisywać:
htpasswd /etc/nginx/.htpasswd alice
Aby wymusić bcrypt zamiast domyślnego dla platformy, dodaj -B. Aby wypisać wpis na stdout bez dotykania pliku (przydatne przy przesyłaniu do konfiguracji lub Dockerfile), połącz -b (hasło w wierszu poleceń) i -n (bez pliku):
htpasswd -Bbn admin 's3cret'
# → admin:$2y$10$N9qo8uLOickgx2ZMRZoMye...
Flagi, których faktycznie użyjesz:
| Flaga | Znaczenie |
|---|---|
-c | Utwórz nowy plik (nadpisuje, jeśli istnieje) — tylko pierwszy użytkownik |
-B | Użyj bcrypt |
-b | Pobierz hasło jako argument wiersza poleceń (bez pytania) |
-n | Wypisz na stdout zamiast zapisywać plik |
-D | Usuń wskazanego użytkownika z pliku |
Jedno zastrzeżenie do -b: hasło trafia do historii powłoki. Dla poświadczeń produkcyjnych lepiej sprawdzi się forma z pytaniem albo opcja w przeglądarce opisana niżej.
Bez apache2-utils — za pomocą OpenSSL
Brak pliku wykonywalnego htpasswd? OpenSSL jest praktycznie w każdym systemie i potrafi wprost wytworzyć hash apr1. Opakuj go w printf, aby zbudować kompletną linię:
printf "admin:$(openssl passwd -apr1 's3cret')\n" >> /etc/nginx/.htpasswd
# admin:$apr1$k3l4Hj9.$qN8vY7tLp2mZ0xW5cR4f.
Format apr1 jest przenośny między Apache i nginx, więc na minimalnej maszynie to droga, która nie wymaga żadnych dodatkowych zależności.
W przeglądarce — bez instalacji, bez wycieku
Jeśli nie chcesz instalować pakietu albo wolisz nie wpisywać hasła produkcyjnego do powłoki, gdzie wyląduje w ~/.bash_history, wygeneruj wpis po stronie klienta. Generator htpasswd oblicza hashe bcrypt, apr1 i SHA-1 w całości w przeglądarce, podaje gotową do wklejenia linię user:hash wraz z pasującym blokiem konfiguracji serwera i niczego nie przesyła przez sieć. Przy okazji warto utworzyć silne, unikalne hasło w generatorze haseł, zamiast używać istniejącego ponownie.
Porównanie formatów haseł htpasswd
Polecenie htpasswd może wyemitować pięć formatów, a nie są one sobie równe. Tabela poniżej pomaga przy wyborze:
| Format | Prefiks | Z solą | Siła | Zastosowanie |
|---|---|---|---|---|
| bcrypt | $2y$ | Tak | Najsilniejszy | Apache, Docker Registry, Caddy, Traefik |
| apr1 (Apache MD5) | $apr1$ | Tak | Umiarkowany | nginx (przenośny, bezpieczny domyślny) |
| SHA-1 | {SHA} | Nie | Słaby | Tylko zgodność ze starszymi systemami |
| crypt (DES) | (brak) | Tak (2 znaki) | Bardzo słaby | Nie używać |
| plain | (brak) | Nie | Brak | Tylko lokalne testy |
Kilka uwag, które nie zmieszczą się w komórce tabeli. bcrypt używa losowej 16-bajtowej soli i adaptacyjnego współczynnika kosztu (domyślnie 10, współczesna rekomendacja 12), więc identyczne hasła dają różne hashe, a nakład pracy rośnie razem z mocą sprzętu. Ma jedną osobliwość: obcina hasło do 72 bajtów, a wszystko dłuższe po cichu ignoruje. apr1 wykonuje 1 000 rund osolonego MD5. To wyraźnie słabsze od bcrypta, ale działa natywnie i w Apache, i w nginx, dlatego nadaje się jako wybór przenośny. SHA-1 nie ma soli, więc identyczne hasła dają identyczne skróty i działają na nim tęczowe tablice — zostaw go wyłącznie dla starszych systemów. crypt i plain zostały z powodów historycznych i testowych; do produkcji nie pasuje żaden.
Prefiksy $2a$ / $2b$ / $2y$
Hashe bcrypt zaczynają się od $2a$, $2b$ lub $2y$. To ten sam algorytm dający równoważne, wymienne hashe; litery wersji zostały po historycznych poprawkach błędów w tym, jak niektóre biblioteki obsługiwały znaki z ustawionym najwyższym bitem oraz długość ciągów. Apache htpasswd emituje $2y$, a Caddy, Traefik i Docker Registry poprawnie go weryfikują.
Głębsze porównanie bcrypta z nowoczesnymi alternatywami daje przewodnik bcrypt vs Argon2 vs scrypt — opisuje, czym te algorytmy haszowania haseł różnią się pod względem kosztu, twardości pamięciowej i modelu zagrożeń.
Konfiguracja Basic Auth na serwerze
Sam plik poświadczeń nic nie robi — serwerowi trzeba jeszcze powiedzieć, że ma go wymagać. Poniżej sześć platform.
Apache (.htaccess)
Wstaw to do pliku .htaccess w katalogu, który chcesz chronić (lub do bloku <Directory> w vhost):
AuthType Basic
AuthName "Restricted Area"
AuthUserFile /etc/apache2/.htpasswd
Require valid-user
AuthName to ciąg realm pokazywany przez przeglądarkę, AuthUserFile to bezwzględna ścieżka do pliku poświadczeń, a Require valid-user akceptuje każdego użytkownika z tej listy.
nginx (auth_basic)
nginx umieszcza konfigurację w bloku location lub server — nie ma .htaccess:
location /admin/ {
auth_basic "Restricted Area";
auth_basic_user_file /etc/nginx/.htpasswd;
}
Przeładuj poleceniem nginx -s reload. Tutaj sięgnij po format apr1. nginx deleguje weryfikację bcrypt do systemowego crypt(), co zawodzi w wielu kompilacjach (więcej o tym w sekcji o błędach), a apr1 weryfikuje wewnętrznie na każdej platformie.
Docker Registry i Kubernetes ingress-nginx
Backend htpasswd prywatnego Docker Registry akceptuje wyłącznie bcrypt. Wygeneruj wpis, zamontuj plik i wskaż na niego registry:
# Generate a bcrypt entry into a file
htpasswd -Bbn admin 's3cret' > auth/htpasswd
# Run the registry with that file
docker run -d -p 5000:5000 \
-v "$(pwd)/auth:/auth" \
-e REGISTRY_AUTH=htpasswd \
-e "REGISTRY_AUTH_HTPASSWD_REALM=Registry Realm" \
-e REGISTRY_AUTH_HTPASSWD_PATH=/auth/htpasswd \
registry:2
W przypadku Kubernetes ingress-nginx zapisz plik jako Secret i odwołaj się do niego przez adnotacje:
kubectl create secret generic basic-auth --from-file=auth=./auth/htpasswd
metadata:
annotations:
nginx.ingress.kubernetes.io/auth-type: basic
nginx.ingress.kubernetes.io/auth-secret: basic-auth
nginx.ingress.kubernetes.io/auth-realm: "Authentication Required"
Uwaga: klucz Secret musi nazywać się auth — ingress-nginx szuka dokładnie tego klucza.
Caddy i Traefik
Oba oczekują hashy bcrypt. Caddy używa dyrektywy basic_auth (wklej hash bcrypt, nie tekst jawny):
example.com {
basic_auth /admin/* {
admin $2y$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy
}
}
Traefik używa middleware basicauth, z parami user:bcrypt-hash (zaeskejpuj każdy $ zgodnie z formatem swojej konfiguracji):
http:
middlewares:
admin-auth:
basicAuth:
users:
- "admin:$2y$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy"
Gdy punkt końcowy jest już chroniony, sprawdź jego działanie z wiersza poleceń. Kreator poleceń cURL składa żądanie -u user:pass, co pozwala potwierdzić zarówno 401, jak i uwierzytelnione 200.
Najlepsze praktyki bezpieczeństwa
Basic Auth jest proste, więc nieliczne sposoby na jego nadużycie łatwo wychwycić.
- Zawsze serwuj przez HTTPS. Poświadczenie to odwracalne base64, więc zwykłe HTTP ujawnia hasło w transmisji. TLS należy terminować przed każdym chronionym punktem końcowym, bez wyjątków.
- Przechowuj plik poza katalogiem głównym WWW. Jeśli
.htpasswdleży w serwowanym katalogu, błędna konfiguracja może pozwolić komuś go pobrać. Trzymaj go gdzieś w stylu/etc/nginx/.htpasswd, ustawchmod 640i nadaj właściciela na użytkownika serwera WWW (www-data,nginx), aby plik mógł odczytać serwer, a inne konta już nie. - Używaj silnych, unikalnych haseł. Każde konto powinno dostać własne hasło o wysokiej entropii z generatora haseł, nigdy używane ponownie. Co znaczy „wystarczająco silne” w bitach, rozkłada na czynniki wyjaśnienie entropii hasła.
- Znaj jego ograniczenia. Basic Auth nie ma wylogowania ani sesji: przeglądarka buforuje poświadczenie per realm aż do zamknięcia i wysyła je ponownie przy każdym żądaniu. Szerszą listę kontrolną dotyczącą haszowania, nagłówków i walidacji daje wpis o najlepszych praktykach bezpieczeństwa webowego.
Rozwiązywanie typowych błędów
nginx: crypt_r() failed (22: Invalid argument)
To najczęstsza awaria nginx Basic Auth i zawsze oznacza to samo: nginx próbował zweryfikować hash bcrypt ($2y$) na libc, które nie zawiera schematu Blowfish — zazwyczaj musl w Alpine albo starszy glibc. Rozwiązaniem jest ponowne wygenerowanie wpisu jako apr1, który nginx weryfikuje wewnętrznie na dowolnej platformie:
printf "admin:$(openssl passwd -apr1 's3cret')\n" > /etc/nginx/.htpasswd
nginx -s reload
Przejście na obraz bazowy, którego libc obsługuje bcrypt, też działa, ale apr1 jest prostsze i bardziej przenośne.
401 mimo poprawnego hasła
Gdy hasło jest na pewno poprawne, a mimo to wraca 401, przejdź tę listę kontrolną po kolei:
- Ścieżka pliku. Potwierdź, że
AuthUserFile/auth_basic_user_filewskazuje na rzeczywisty plik (ścieżka bezwzględna, bez literówek). - Uprawnienia. Użytkownik serwera WWW musi móc odczytać plik. Sprawdź to poleceniem
sudo -u www-data cat /etc/nginx/.htpasswd. - Końce linii / kodowanie. Plik edytowany w systemie Windows może nieść znaki
\r, które psują hash. Uruchomfile .htpasswdi w razie potrzeby przepuść go przezdos2unix. - Nieaktualna pamięć podręczna przeglądarki. Przeglądarka buforuje poświadczenia per realm. Przetestuj w oknie prywatnym/incognito, aby wykluczyć zapamiętane stare hasło.
- Niezgodność hasha. Sprawdź, czy zapisany hash faktycznie pasuje do hasła — wklej oba w tryb weryfikacji generatora htpasswd, zanim obwinisz konfigurację.
Kiedy NIE używać Basic Auth
Basic Auth pasuje do wąskiego zestawu zadań: witryny staging, wewnętrznej ścieżki admina, punktu końcowego z artefaktami CI, pulpitu metryk, prywatnego registry. Nie ma zależności, a konfiguracja zajmuje dwie minuty.
Jest za to złym narzędziem do logowania w produkcie. Nie ma wylogowania, resetu hasła, ograniczania liczby żądań, blokady konta ani MFA. Poświadczenia wracają przy każdym żądaniu i zostają w pamięci przeglądarki aż do jej zamknięcia. Wszędzie tam, gdzie użytkownicy się logują, lepiej sprawdzą się sesje, OAuth lub OIDC. To rozróżnienie utrzymuje Basic Auth użytecznym dokładnie tam, gdzie naprawdę pasuje.
FAQ
Co właściwie zawiera pojedyncza linia w pliku .htpasswd?
Parę username:hash rozdzieloną dwukropkiem. Hash zaczyna się od prefiksu algorytmu ($2y$ dla bcrypt, $apr1$ dla Apache MD5, {SHA} dla SHA-1), a po nim idą sól i skrót. Hasło w postaci tekstu jawnego nigdy nie trafia do pliku.
Jaka jest różnica między .htpasswd a .htaccess?
.htaccess to plik konfiguracyjny Apache działający per katalog — włącza Basic Auth i wskazuje na magazyn poświadczeń. .htpasswd to ten magazyn poświadczeń, zawierający linie username:hash. nginx używa formatu .htpasswd, ale konfiguruje uwierzytelnianie w blokach server/location, a nie w .htaccess.
Jak dodać, zmienić lub usunąć użytkownika w pliku .htpasswd?
Aby dodać lub zmienić użytkownika, uruchom htpasswd /path/.htpasswd username bez -c — jeśli użytkownik istnieje, jego hash zostaje zaktualizowany. Aby usunąć użytkownika, uruchom htpasswd -D /path/.htpasswd username. Flagi -c używaj tylko dla pierwszego użytkownika, bo nadpisuje cały plik.
Jak przeglądarka pamięta poświadczenia Basic Auth i jak użytkownicy się wylogowują?
Przeglądarka buforuje poświadczenia kluczowane przez realm i automatycznie wysyła je ponownie przy każdym pasującym żądaniu. Nie ma standardowego wylogowania: poświadczenia czyści się tylko przez zamknięcie przeglądarki albo wyczyszczenie jej pamięci podręcznej. Ten brak wylogowania jest jednym z powodów, dla których Basic Auth nie nadaje się do uwierzytelniania w produkcie.
Czy można użyć tego samego pliku .htpasswd dla Apache i nginx?
Tak, o ile format hash obsługują oba. apr1 (Apache MD5) jest weryfikowany natywnie przez Apache i nginx wszędzie, więc to najbezpieczniejszy wspólny wybór. bcrypt działa na Apache, ale na nginx zależy od systemowego crypt(), które zawodzi na kompilacjach Alpine/musl.
Czy HTTP Basic Authentication wciąż ma znaczenie w 2026 roku?
Tak. Jako lekka bramka na szczycie HTTPS — narzędzia wewnętrzne, środowiska staging, prywatne registry, punkty końcowe monitorowania — wciąż jest praktyczna i nie ciągnie za sobą żadnych zależności. Nie należy jej tylko mylić z uwierzytelnianiem w produkcie skierowanym do użytkownika, które potrzebuje sesji, resetów, ograniczania liczby żądań i MFA, czego Basic Auth nie zapewnia.
Zweryfikowane przez zespół Go Tools: każde polecenie, blok konfiguracji i format hash w tym przewodniku sprawdzono względem wzorcowego wyniku Apache htpasswd (apache2-utils) oraz OpenSSL.