Skip to content
블로그로 돌아가기
보안

.htpasswd 파일 만드는 법: HTTP Basic 인증 가이드

bcrypt 또는 apr1로 .htpasswd 파일을 만들고 Apache·nginx 등 웹 서버와 Docker·Kubernetes에서 HTTP Basic 인증을 설정·보호하는 2026 실전 가이드.

11 분 소요

.htpasswd 파일 만드는 법: HTTP Basic 인증 가이드

.htpasswd 파일은 HTTP Basic 인증이 쓰는 서버 측 자격 증명 저장소입니다. 각 줄이 하나의 username:hash 쌍으로 이루어진 일반 텍스트 파일이죠. 만드는 방법은 해시된 줄을 생성해 웹 서버가 읽는 위치에 저장하는 것이고, 경로는 세 가지입니다.

  • htpasswd 명령어 (apache2-utils / httpd-tools 패키지에 포함) — 표준 도구입니다.
  • openssl passwd — 거의 모든 환경에 이미 설치되어 있어 별도 패키지가 필요 없습니다.
  • 브라우저에서htpasswd 생성기를 사용하면 설치 없이 로컬에서 항목을 만들고, 어떤 데이터도 네트워크로 전송하지 않습니다.

이 가이드는 한 줄짜리 명령어에서 멈추지 않습니다. Basic 인증 핸드셰이크가 실제로 어떻게 동작하는지, 파일을 만드는 세 가지 방법, 다섯 가지 해시 형식 중 무엇을 골라야 하는지, Apache·nginx·Docker·Kubernetes·Caddy·Traefik에 어떻게 연결하는지, 그리고 누구나 내려받는 자격 증명 파일을 배포하지 않도록 어떻게 잠가야 하는지까지 짚습니다.

.htpasswd 파일이란?

.htpasswd 파일의 모든 줄은 한 사용자의 자격 증명을 콜론으로 구분된 쌍으로 담습니다. 사용자명은 그대로 저장되지만, 비밀번호는 단방향 해시로만 저장되므로 평문이 디스크에 기록되는 일은 없습니다. bcrypt 한 줄의 구조는 다음과 같습니다.

admin    :    $2y$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy
│             │
└─ username   └─ hash (algorithm prefix $2y$ + cost + salt + digest)

사용자명이 먼저 오고, 그다음 단일 :, 그다음 해시가 옵니다. 사용자명은 최대 255바이트까지 가능하며, 콜론은 필드 구분자이므로 절대 콜론을 포함해서는 안 됩니다. 해시는 자체 알고리즘 표시자를 접두사로 가지고 있어서(bcrypt는 $2y$, Apache MD5는 $apr1$, SHA-1은 {SHA}), 서버는 별도 설정 없이도 어떻게 검증할지 알 수 있습니다.

여러 사용자를 등록하려면 사용자마다 한 줄씩 추가합니다.

admin:$2y$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy
alice:$2y$10$3bQ8xY7tLp2mZ0xW5cR4fO9vK1jH6sD2nG8aQ5wE3rT7uI4oP1cm
bob:$apr1$mZ0xW5cR$4fK1jH6sD2nG8aQ5wE3rT2

같은 파일 안에서 알고리즘을 섞어 쓸 수 있습니다. 서버는 각 줄을 읽고 접두사로 형식을 감지한 다음, 사용된 방식에 맞춰 검증합니다. 여기서는 bcrypt 사용자 두 명과 apr1 사용자 한 명이 아무 문제 없이 공존합니다.

.htpasswd vs .htaccess

이 둘은 Apache에서 항상 함께 다녀서 끊임없이 혼동되지만, 서로 다른 일을 합니다. .htaccess는 Apache의 디렉터리별 설정 파일입니다. 지시어를 담고 있으며, Basic 인증을 켜고 자격 증명 저장소를 가리키는 지시어도 여기에 포함됩니다. .htpasswd자격 증명 데이터베이스로, 설정은 없고 username:hash 줄만 들어 있습니다.

요약하면, .htaccess는 어떤 디렉터리가 로그인을 요구하는지와 사용자 목록을 어디서 찾을지 결정하고, .htpasswd는 그 사용자 목록입니다. nginx는 .htaccess를 전혀 사용하지 않습니다. nginx의 Basic 인증 설정은 메인 설정의 server 또는 location 블록에 들어가지만, 동일한 .htpasswd 자격 증명 형식을 읽습니다.

HTTP Basic 인증이 동작하는 방식

HTTP Basic 인증은 HTTP 명세에 내장된 챌린지-응답 핸드셰이크입니다. 세 단계만 이해하면 뒤에 나오는 문제 해결도 한결 수월해집니다.

  1. 클라이언트가 자격 증명 없이 보호된 리소스를 요청합니다.
  2. 서버가 401 Unauthorized로 응답하며 WWW-Authenticate: Basic realm="..." 헤더를 포함합니다 — 이것이 챌린지입니다.
  3. 클라이언트가 요청을 재시도하며 Authorization: Basic <base64(user:password)> 헤더를 함께 보냅니다. 자격 증명이 .htpasswd 파일의 한 줄과 일치하면 서버가 리소스를 반환합니다.

이게 프로토콜 전부입니다. 로그인 폼도, 세션 쿠키도, 토큰도 없습니다. 이후의 모든 요청은 같은 헤더를 실어 보냅니다.

401 / WWW-Authenticate 챌린지

WWW-Authenticate 헤더는 두 가지 일을 합니다. Basic 토큰은 클라이언트에게 어떤 스킴을 쓸지 알려주고, realm 문자열은 보호 영역에 이름을 붙입니다. 브라우저는 로그인 대화상자에 realm 텍스트를 표시하고(“사이트 메시지: …”) 이를 캐시 키로 사용합니다. 한 realm에 입력된 자격 증명은 같은 realm의 다른 URL에 재사용되므로, 사용자는 페이지마다 다시 입력하라는 요청을 받지 않습니다.

다음은 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

Authorization: Basic 헤더

클라이언트가 보내는 자격 증명은 base64(username:password)입니다. 이것이 Basic 인증에 관한 가장 중요한 보안 사실입니다. base64는 인코딩이지 암호화가 아닙니다. 누구나 완전히 되돌릴 수 있으므로, 자격 증명은 사실상 평문 형태로 전송됩니다. 왕복 과정을 직접 확인할 수 있습니다.

# 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

이러한 가역성 때문에 Basic 인증은 반드시 HTTPS 위에서 실행해야 합니다. TLS가 없으면 네트워크 경로상의 누구나 비밀번호를 읽을 수 있습니다. 이 헤더를 직접 만들거나 살펴보고 싶다면, Base64 인코더/디코더가 브라우저에서 동일한 user:password 변환을 수행합니다.

.htpasswd 파일 만드는 법

파일을 만드는 실용적인 방법은 세 가지입니다. 무엇이 설치되어 있는지, 그리고 비밀번호를 어디에 두고 싶은지에 따라 고르세요.

htpasswd 명령어 사용하기

htpasswd 바이너리는 Apache의 유틸리티 패키지에 들어 있습니다. 먼저 설치하세요.

# Debian / Ubuntu
sudo apt install apache2-utils

# RHEL / CentOS / Fedora
sudo yum install httpd-tools

파일과 첫 사용자를 만듭니다. -c 플래그는 생성을 의미하며 기존 파일을 덮어씁니다. 맨 처음 한 번만 사용하세요.

htpasswd -c /etc/nginx/.htpasswd admin
# prompts twice for the password, then writes the file

사용자를 더 추가하려면 -c를 빼서 덮어쓰지 않고 이어붙이세요.

htpasswd /etc/nginx/.htpasswd alice

플랫폼 기본값 대신 bcrypt를 강제하려면 -B를 추가하세요. 어떤 파일도 건드리지 않고 항목을 stdout에 출력하려면(설정이나 Dockerfile로 파이프할 때 유용합니다) -b(명령줄에서 비밀번호 전달)와 -n(파일 미생성)을 조합하세요.

htpasswd -Bbn admin 's3cret'
# → admin:$2y$10$N9qo8uLOickgx2ZMRZoMye...

실제로 쓰게 될 플래그는 다음과 같습니다.

플래그의미
-c새 파일 생성(존재하면 덮어씀) — 첫 사용자 전용
-Bbcrypt 사용
-b비밀번호를 명령줄 인수로 전달(프롬프트 없음)
-n파일에 쓰지 않고 stdout에 출력
-D파일에서 지정한 사용자 삭제

-b에는 한 가지 주의점이 있습니다. 비밀번호가 셸 히스토리에 남는다는 점입니다. 일회성 운영 자격 증명이라면 프롬프트 방식이나 아래의 브라우저 방식을 권장합니다.

apache2-utils 없이 — OpenSSL 사용하기

htpasswd 바이너리가 없나요? OpenSSL은 사실상 모든 시스템에 있고 apr1 해시를 직접 만들어낼 수 있습니다. printf로 감싸서 완전한 한 줄을 구성하세요.

printf "admin:$(openssl passwd -apr1 's3cret')\n" >> /etc/nginx/.htpasswd
# admin:$apr1$k3l4Hj9.$qN8vY7tLp2mZ0xW5cR4f.

apr1 형식은 Apache와 nginx 양쪽에서 이식 가능하며, 최소 구성 환경에서 의존성이 가장 적은 경로가 됩니다.

브라우저에서 — 설치도 유출도 없이

패키지를 설치하고 싶지 않거나, 운영 비밀번호를 ~/.bash_history에 남는 셸에 입력하고 싶지 않다면, 클라이언트 측에서 항목을 생성하세요. htpasswd 생성기는 bcrypt, apr1, SHA-1 해시를 전부 브라우저 안에서 계산합니다. 바로 붙여넣을 user:hash 줄과 그에 맞는 서버 설정 블록을 돌려주고, 어떤 데이터도 밖으로 보내지 않습니다. 온 김에 비밀번호를 재사용하지 말고 랜덤 비밀번호 생성기로 강력하고 고유한 비밀번호를 만들어 두세요.

htpasswd 비밀번호 형식 비교

htpasswd 명령어는 다섯 가지 형식을 출력할 수 있는데, 이들은 동등하지 않습니다. 이 표는 하나를 고를 때 참고할 빠른 레퍼런스입니다.

형식접두사솔트 적용강도적합한 용도
bcrypt$2y$가장 강함Apache, Docker Registry, Caddy, Traefik
apr1 (Apache MD5)$apr1$중간nginx (이식성 좋은 안전한 기본값)
SHA-1{SHA}아니오약함레거시 호환 전용
crypt (DES)(없음)예 (2자)매우 약함사용하지 말 것
plain(없음)아니오없음로컬 테스트 전용

표 칸에 다 담기지 않는 점 몇 가지를 덧붙입니다. bcrypt는 무작위 16바이트 솔트와 적응형 cost 인수(기본값 10, 최신 권장값 12)를 쓰는데, 그래서 같은 비밀번호라도 매번 다른 해시가 나오고 작업량은 하드웨어에 맞춰 늘릴 수 있습니다. 한 가지 특이점은 bcrypt가 비밀번호를 72바이트에서 잘라낸다는 점입니다. 그보다 긴 부분은 조용히 무시됩니다. apr1은 솔트가 적용된 MD5를 1,000라운드 돌립니다. bcrypt보다 훨씬 약하지만 Apache와 nginx 양쪽에서 자체적으로 구현되어 있어서 이식성 있는 선택지가 됩니다. SHA-1은 솔트가 없어서 동일한 비밀번호가 동일한 다이제스트를 내고 레인보우 테이블이 통합니다. 레거시 시스템에만 남겨 두세요. cryptplain은 역사적·테스트 목적으로 존재하며, 둘 다 운영 환경에는 어울리지 않습니다.

$2a$ / $2b$ / $2y$ 접두사

bcrypt 해시가 $2a$, $2b$, $2y$로 시작하는 것을 보게 됩니다. 모두 같은 알고리즘이며 동등하고 상호 교환 가능한 해시를 만듭니다. 버전 글자는 일부 라이브러리가 상위 비트 문자와 문자열 길이를 처리하던 방식의 과거 버그 수정에서 남은 흔적입니다. Apache의 htpasswd$2y$를 출력하며, Caddy, Traefik, Docker Registry 모두 이를 올바르게 검증합니다.

bcrypt를 최신 대안들과 더 깊이 비교하고 싶다면, bcrypt vs Argon2 vs scrypt 가이드에서 이 비밀번호 해싱 알고리즘들이 비용, 메모리 경도, 위협 모델 면에서 어떻게 다른지 다룹니다.

서버에 Basic 인증 설정하기

자격 증명 파일은 그 자체로는 아무 일도 하지 않습니다. 서버에게 그것을 요구하도록 알려줘야 합니다. 여기 여섯 가지 플랫폼이 있습니다.

Apache (.htaccess)

보호하려는 디렉터리의 .htaccess 파일에(또는 vhost의 <Directory> 블록에) 다음을 넣으세요.

AuthType Basic
AuthName "Restricted Area"
AuthUserFile /etc/apache2/.htpasswd
Require valid-user

AuthName은 브라우저가 표시하는 realm 문자열이고, AuthUserFile은 자격 증명 파일의 절대 경로이며, Require valid-user는 파일에 등록된 모든 사용자를 허용합니다.

nginx (auth_basic)

nginx는 설정을 location 또는 server 블록에 넣습니다 — .htaccess는 없습니다.

location /admin/ {
    auth_basic           "Restricted Area";
    auth_basic_user_file /etc/nginx/.htpasswd;
}

nginx -s reload로 다시 불러오세요. 여기서는 apr1 형식을 사용하세요. nginx는 bcrypt 검증을 시스템 crypt()에 위임하는데, 이는 많은 빌드에서 실패합니다(문제 해결에서 더 자세히 다룹니다). 반면 apr1은 모든 플랫폼에서 내부적으로 검증됩니다.

Docker Registry & Kubernetes ingress-nginx

프라이빗 Docker Registry의 htpasswd 백엔드는 bcrypt만 받습니다. 항목을 생성하고, 마운트한 다음, 레지스트리가 그것을 가리키게 하세요.

# 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

Kubernetes ingress-nginx의 경우, 파일을 Secret으로 저장하고 어노테이션으로 참조하세요.

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"

Secret 키는 반드시 auth로 이름 지어야 합니다 — ingress-nginx는 정확히 그 키를 찾습니다.

Caddy & Traefik

둘 다 bcrypt 해시를 기대합니다. Caddy는 basic_auth 지시어를 사용합니다(평문이 아니라 bcrypt 해시를 붙여넣으세요).

example.com {
    basic_auth /admin/* {
        admin $2y$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy
    }
}

Traefik은 basicauth 미들웨어를 user:bcrypt-hash 쌍으로 사용합니다(설정 형식에 맞게 $를 이스케이프하세요).

http:
  middlewares:
    admin-auth:
      basicAuth:
        users:
          - "admin:$2y$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy"

엔드포인트를 보호했다면, 명령줄에서 동작하는지 검증하세요. cURL 명령어 빌더-u user:pass 요청을 조립해 주므로 401과 인증된 200을 모두 확인할 수 있습니다.

보안 모범 사례

Basic 인증은 단순하기 때문에, 잘못 쓰는 몇 가지 방식도 알아채기 쉽습니다.

  • 항상 HTTPS로 제공하세요. 자격 증명은 되돌릴 수 있는 base64이므로, 일반 HTTP는 통신선상에 비밀번호를 노출합니다. 보호된 엔드포인트 앞에서 예외 없이 TLS를 종단하세요.
  • 파일을 웹 루트 바깥에 저장하세요. .htpasswd가 서비스되는 디렉터리에 있으면, 설정 실수로 누군가 그것을 내려받을 수 있습니다. /etc/nginx/.htpasswd 같은 곳에 두고, chmod 640을 설정하며, 웹 서버 사용자(www-data, nginx)가 소유하게 하여 서버는 읽을 수 있고 다른 계정은 읽을 수 없게 하세요.
  • 강력하고 고유한 비밀번호를 사용하세요. 각 계정은 랜덤 비밀번호 생성기로 만든 자체의 높은 엔트로피 비밀번호를 받아야 하며, 절대 재사용하지 마세요. “충분히 강한”이 비트 단위로 무엇을 뜻하는지 이해하고 싶다면, 비밀번호 엔트로피 설명에서 그 수학을 분석합니다.
  • 한계를 알아 두세요. Basic 인증에는 로그아웃도 세션도 없습니다. 브라우저는 닫을 때까지 자격 증명을 realm별로 캐시하고, 매 요청마다 다시 보냅니다. 해싱, 헤더, 검증에 관한 더 폭넓은 체크리스트는 웹 보안 모범 사례를 참고하세요.

흔한 오류 해결하기

nginx: crypt_r() failed (22: Invalid argument)

이것은 가장 흔한 nginx Basic 인증 실패이며, 언제나 같은 것을 뜻합니다. nginx가 Blowfish 스킴을 포함하지 않는 libc(보통 Alpine의 musl이나 오래된 glibc) 위에서 bcrypt($2y$) 해시를 검증하려 한 것입니다. 해결책은 항목을 apr1로 다시 생성하는 것이며, nginx는 이를 어떤 플랫폼에서도 내부적으로 검증합니다.

printf "admin:$(openssl passwd -apr1 's3cret')\n" > /etc/nginx/.htpasswd
nginx -s reload

libc가 bcrypt를 지원하는 베이스 이미지로 바꾸는 것도 효과가 있지만, apr1이 더 단순하고 이식성 있는 해결책입니다.

올바른 비밀번호인데도 401

비밀번호가 분명히 맞는데도 여전히 401을 받는다면, 이 체크리스트를 순서대로 따라가세요.

  1. 파일 경로. AuthUserFile / auth_basic_user_file이 실제 파일을 가리키는지 확인하세요(절대 경로, 오타 없음).
  2. 권한. 웹 서버 사용자가 파일을 읽을 수 있어야 합니다. sudo -u www-data cat /etc/nginx/.htpasswd로 확인하세요.
  3. 줄 끝 문자 / 인코딩. Windows에서 편집한 파일은 해시를 손상시키는 \r 문자를 가질 수 있습니다. file .htpasswd를 실행하고 필요하면 dos2unix로 변환하세요.
  4. 오래된 브라우저 캐시. 브라우저는 자격 증명을 realm별로 캐시합니다. 기억된 옛 비밀번호 가능성을 배제하려면 비공개/시크릿 창에서 테스트하세요.
  5. 해시 불일치. 저장된 해시가 실제로 비밀번호와 일치하는지 검증하세요. 설정을 탓하기 전에 둘 다 htpasswd 생성기의 검증 모드에 붙여넣어 확인하세요.

Basic 인증을 쓰지 말아야 할 때

Basic 인증은 좁은 범위의 작업에 적합한 도구입니다. 스테이징 사이트, 내부 관리자 경로, CI 아티팩트 엔드포인트, 메트릭 대시보드, 프라이빗 레지스트리 같은 것이죠. 의존성이 없고 설정하는 데 2분이면 됩니다.

제품 로그인에는 맞지 않습니다. 로그아웃도, 비밀번호 재설정도, 속도 제한도, 계정 잠금도, MFA도 없으니까요. 자격 증명은 매 요청마다 다시 보내지고 브라우저가 닫힐 때까지 캐시됩니다. 사용자가 로그인하는 곳이라면 세션, OAuth, OIDC를 쓰세요. 이 경계를 분명히 지킬 때 Basic 인증도 제 역할을 하는 자리에서 제대로 쓰입니다.

FAQ

.htpasswd 파일의 한 줄에는 실제로 무엇이 들어 있나요?

콜론으로 구분된 username:hash 쌍입니다. 해시는 알고리즘 접두사(bcrypt는 $2y$, Apache MD5는 $apr1$, SHA-1은 {SHA})로 시작하고, 그다음 솔트와 다이제스트가 옵니다. 평문 비밀번호는 파일에 절대 나타나지 않습니다.

.htpasswd와 .htaccess의 차이는 무엇인가요?

.htaccess는 Apache의 디렉터리별 설정 파일로, Basic 인증을 켜고 자격 증명 저장소를 가리킵니다. .htpasswd는 그 자격 증명 저장소로, username:hash 줄을 담습니다. nginx는 .htpasswd 형식을 사용하지만 인증은 .htaccess가 아니라 server/location 블록에서 설정합니다.

.htpasswd 파일에서 사용자를 추가, 변경, 제거하려면 어떻게 하나요?

사용자를 추가하거나 변경하려면 -c 없이 htpasswd /path/.htpasswd username을 실행하세요. 사용자가 존재하면 그 해시가 업데이트됩니다. 제거하려면 htpasswd -D /path/.htpasswd username을 실행하세요. -c는 전체 파일을 덮어쓰므로 맨 첫 사용자에게만 사용하세요.

브라우저는 Basic 인증 자격 증명을 어떻게 기억하며, 사용자는 어떻게 로그아웃하나요?

브라우저는 realm을 키로 자격 증명을 캐시하고 일치하는 모든 요청에 자동으로 다시 보냅니다. 표준 로그아웃은 없습니다. 자격 증명을 지우는 유일한 방법은 브라우저를 닫거나 캐시를 비우는 것입니다. 그 빠진 로그아웃이 Basic 인증이 제품 인증에 맞지 않는 한 가지 이유입니다.

같은 .htpasswd 파일을 Apache와 nginx 양쪽에 쓸 수 있나요?

그렇습니다. 해시 형식이 양쪽 모두에서 지원되는 한 가능합니다. apr1(Apache MD5)은 Apache와 nginx 어디서나 자체적으로 검증되므로 가장 안전한 공유 선택지입니다. bcrypt는 Apache에서는 동작하지만 nginx에서는 시스템 crypt()에 의존하며, 이는 Alpine/musl 빌드에서 실패합니다.

HTTP Basic 인증은 2026년에도 여전히 유효한가요?

그렇습니다. HTTPS 위에 얹는 가벼운 관문으로서 — 내부 도구, 스테이징 환경, 프라이빗 레지스트리, 모니터링 엔드포인트 — 여전히 실용적이고 의존성이 없습니다. 다만 세션, 재설정, 속도 제한, MFA가 필요한 사용자 대면 제품 인증으로 착각하지는 마세요. 그런 것들은 Basic 인증이 제공할 수 없습니다.

Go Tools 팀이 검수했습니다. 이 가이드의 모든 명령어, 설정 블록, 해시 형식은 Apache htpasswd(apache2-utils)와 OpenSSL 참조 출력과 대조하여 확인했습니다.

태그: htpasswd basic-auth http-authentication nginx apache bcrypt security