BlueByte
FATAL: no pg_hba.conf entry for host (28000)Fixed

PostgreSQL: FATAL — no pg_hba.conf entry for host

작성 Haneul Seo2026년 9월 18일 업데이트12 min

안녕하세요, BlueByte입니다. 새 앱 서버나 컨테이너, VPN에 붙은 노트북이 접속을 시도하면 PostgreSQL이 FATAL: no pg_hba.conf entry for host "10.0.2.15", user "app", database "shop", no encryption이라고 답합니다. 암호도 맞고 데이터베이스도 있고 포트도 열려 있습니다 — 서버가 이 클라이언트를 들여보내는 규칙을 하나도 찾지 못했다는 뜻일 뿐입니다. 오늘은 메시지의 각 부분이 말해주는 것, 규칙이 빠지게 되는 다섯 가지 경로, 서버가 실제로 읽은 규칙을 보는 법, 원인별 해결, 그리고 다음 서브넷이 같은 벽에 부딪히지 않게 하는 방법까지 하나씩 짚어보겠습니다.

메시지가 말하는 것, 그리고 마지막 단어가 알려주는 것

문서는 이 상황을 한 문장으로 설명합니다. 서버에 닿는 데는 성공했지만 서버가 대화를 원하지 않는다는 것입니다. 서버는 pg_hba.conf를 위에서 아래로 훑으며 접속 유형·클라이언트 주소·데이터베이스·사용자가 전부 일치하는 첫 줄을 씁니다. 다음 줄로 넘어가는 fall-through는 없고, 일치하는 레코드가 없으면 접근이 거부됩니다. 메시지는 서버가 맞춰 보려 한 값을 그대로 인용하니 글자 그대로 읽으세요:

psql: error: connection to server at "10.0.1.5", port 5432 failed: FATAL:  no pg_hba.conf entry for host "10.0.2.15", user "app", database "shop", no encryption

마지막 구절은 접속의 암호화 상태입니다 — 서버 소스를 보면 no encryption, SSL encryption, GSS encryption 셋 중 하나입니다. 거의 같은 뜻의 형제가 둘 있습니다. pg_hba.conf rejects connection for host …는 일치하는 줄이 있었고 그 방법이 reject였다는 뜻이고, no pg_hba.conf entry for replication connection from host …는 replication 가상 데이터베이스를 쓰는 standby에서 난 같은 실패입니다.

일치하는 줄이 없게 되는 다섯 가지 경로

클라이언트 주소가 포함되지 않았다. 가장 흔한 경우입니다. pg_hba.conf는 127.0.0.1/32와 예전 서브넷만 허용하는데, 새 앱 서버나 Docker 브리지의 172.17.0.3 컨테이너는 둘 다 아닙니다. 메시지 속 주소는 NAT를 거친 뒤 서버가 본 값이니, 클라이언트 IP라고 짐작하는 값보다 그쪽을 믿으세요.

줄은 있지만 다른 접속 유형용이다. hostssl 줄은 SSL 접속에만, hostnossl은 평문 접속에만 맞습니다. libpq의 sslmode 기본값은 prefer로, 먼저 SSL 접속을 시도하고 실패하면 비SSL 접속을 시도합니다. 그래서 규칙이 hostssl뿐인데 SSL 시도가 다른 이유로 거부되면 클라이언트는 평문으로 재시도해 다시 거부되고, 여러분에게는 두 번째 오류 — no encryption으로 끝나는 — 를 보여주며, 로그에는 둘 다 남습니다.

localhost는 local이 아니다. local 줄은 Unix 도메인 소켓에만 맞습니다. psql -h localhost는 TCP라서 127.0.0.1/32용 host 줄이 필요하고, localhost는 ::1로 먼저 해석되는 일이 많아 ::1/128도 필요합니다.

데이터베이스나 사용자가 안 맞는다. sameuser나 특정 데이터베이스용 줄은 사용자 app의 shop에 맞지 않습니다. 두 열 모두 all이면 맞습니다.

파일을 고쳤지만 다시 읽히지 않았거나, 엉뚱한 파일을 고쳤다. 서버는 pg_hba.conf를 시작 시와 SIGHUP을 받을 때 읽습니다. 파일을 저장해도 reload 전에는 아무것도 바뀌지 않습니다. 일부 배포판은 이 파일을 데이터 디렉터리 밖에 두므로, 편집한 파일이 서버가 쓰는 파일이 아닐 수 있습니다.

서버가 실제로 보는 규칙 읽기

아직 되는 경로 — 보통 postgres OS 사용자로 Unix 소켓 — 로 접속해, 서버가 어느 파일을 쓰고 어떻게 파싱했는지 물어보세요:

SHOW hba_file;
SELECT rule_number, type, database, user_name, address, auth_method, error
FROM pg_hba_file_rules;
 rule_number | type  | database | user_name |  address  |  auth_method  | error
-------------+-------+----------+-----------+-----------+---------------+-------
           1 | local | {all}    | {all}     |           | peer          |
           2 | host  | {all}    | {all}     | 127.0.0.1 | scram-sha-256 |
           3 | host  | {all}    | {all}     | ::1       | scram-sha-256 |

10.0.2.15를 포함하는 줄이 없습니다 — 첫 번째 원인입니다. error가 null이 아닌 행은 오타 때문에 서버가 건너뛴 줄입니다. 이 뷰는 기본적으로 superuser만 읽을 수 있고, 마지막으로 로드된 내용이 아니라 파일의 현재 내용을 보여줍니다 — 그래서 reload 전에 편집 내용을 검사하는 데 쓸모가 있습니다. 서버 로그에는 거부된 시도마다 한 줄씩 남고, 문서의 팁대로 로그가 클라이언트에 전달된 것보다 더 많이 말해줄 수 있습니다.

해결 1: 클라이언트의 실제 주소에 맞는 줄을 추가하고 reload

메시지의 유형·데이터베이스·사용자·주소에 맞는 줄을 추가합니다. CIDR은 배포 환경이 허용하는 만큼 좁게 잡고 scram-sha-256을 쓰세요:

# TYPE   DATABASE  USER  ADDRESS       METHOD
hostssl  shop      app   10.0.2.0/24   scram-sha-256

순서가 중요합니다. 구체적인 줄을 넓은 줄보다, 그리고 어떤 reject보다도 위에 두세요. 그런 다음 reload합니다 — 재시작도, 세션 끊김도 없습니다:

SELECT pg_reload_conf();
 pg_reload_conf
----------------
 t

pg_ctl reload, systemctl reload postgresql, postmaster에 kill -HUP도 같은 일을 합니다. Windows에서는 reload 없이 새 접속부터 파일이 반영된다고 문서가 밝힙니다.

해결 2: 클라이언트가 실제로 쓰는 접속 유형에 맞추기

규칙이 hostssl인데 메시지가 no encryption으로 끝난다면 어느 쪽을 움직일지 정하세요. TLS를 강제하려면 클라이언트의 fallback을 끊어 진짜 오류가 보이게 합니다:

psql "host=10.0.1.5 dbname=shop user=app sslmode=require"

둘 다 허용하려면 hostssl 대신 host를 쓰세요. SSL과 비SSL에 모두 맞습니다. localhost 경우에는 루프백 줄 둘 — 127.0.0.1/32와 ::1/128 — 을 모두 넣거나, -h를 빼서 psql이 소켓과 local 줄을 쓰게 하세요.

해결 3: 줄은 맞는데 서버가 다시 읽은 적이 없는 경우

SHOW hba_file 결과와 편집한 경로를 비교하세요. Debian·Ubuntu 패키지에서는 데이터 디렉터리가 아니라 /etc/postgresql/<version>/main/pg_hba.conf이고, 컨테이너에서는 보통 PGDATA 안에 있습니다. 서버가 지목하는 파일을 고친 뒤 reload하고 로그를 보세요:

sudo systemctl reload postgresql
sudo tail -n 3 /var/log/postgresql/postgresql-16-main.log
LOG:  received SIGHUP, reloading configuration files

로그에 대신 문법 오류와 함께 pg_hba.conf was not reloaded가 찍힌다면, pg_hba_file_rules가 가리키는 줄을 고칠 때까지 옛 규칙이 그대로 유효합니다.

실제 사례: 새 앱 서브넷과 거짓말한 "no encryption"

한 팀이 API를 10.0.1.0/24에서 10.0.2.0/24로 옮기자 모든 pod가 no pg_hba.conf entry for host "10.0.2.15", user "app", database "shop", no encryption으로 실패했습니다. pg_hba.conf에 hostssl 줄만 있었기에 첫 추측은 pod들이 TLS를 잃었다는 것이었습니다. 진짜 이야기는 서버 로그에 있었습니다. 시도마다 두 줄이 남았는데 첫 줄은 SSL encryption, 둘째 줄은 no encryption으로 끝났습니다 — prefer fallback입니다. 10.0.2.0/24를 포함하는 줄이 아예 없었으니 둘 다 맞지 않았던 것입니다. 옛 서브넷 줄 위에 hostssl shop app 10.0.2.0/24 scram-sha-256을 추가하고 pg_reload_conf()를 실행한 뒤, 앞으로 거부되면 올바른 접미사로 한 번만 보이도록 앱에 sslmode=require를 설정했습니다.

고친 뒤 끝까지 확인하고 재발 막기

실패했던 클라이언트에서 접속해, 이 세션에 대해 서버가 무엇을 보는지 물어보세요:

SELECT inet_client_addr() AS client, s.ssl
FROM pg_stat_ssl s WHERE s.pid = pg_backend_pid();
  client   | ssl
-----------+-----
 10.0.2.15 | t

주소는 오류에 나온 그 값이고, hostssl 줄을 통과했다면 ssl은 t입니다. 재발을 막으려면: 서브넷을 할당할 때마다 pg_hba.conf 줄도 함께 추가하고, 이 파일을 방화벽 규칙 옆에 구성 관리로 두세요. reload 전마다 pg_hba_file_rules를 조회해 오타를 잡으세요. host all all 0.0.0.0/0보다 좁은 CIDR의 hostssl을 쓰세요. 그리고 애플리케이션 접속 문자열에 sslmode=require를 넣어, 받는 메시지가 실제로 일어난 일을 말하게 하세요.

"password authentication failed", "Connection refused"와 어떻게 다른가

FATAL: password authentication failed for user "app"은 한 단계 뒤입니다. 줄은 맞았고, 그 줄이 지정한 방법에서 자격 증명이 실패한 것입니다 — 암호나 .pgpass, 역할의 문제이지 pg_hba.conf가 아닙니다. connection to server … failed: Connection refused는 한 단계 앞입니다. 클라이언트가 PostgreSQL에 닿지도 못한 것이니 listen_addresses·포트·방화벽을 보세요. 그리고 FATAL: database "shop" does not exist는 인증은 통과했고 이름이 틀린 것입니다.

다음에 이 오류를 만나면 이 순서를 거꾸로 따라가 보세요. 메시지의 주소와 마지막 단어를 글자 그대로 읽고, pg_hba_file_rules를 나열하고, 줄 하나를 추가하거나 고치고, reload한 뒤 inet_client_addr()로 확인하면 됩니다.

관련 질문

줄을 추가했는데도 여전히 실패합니다. 무엇을 놓쳤나요?

흔한 네 가지입니다. 서버를 reload하지 않았거나(pg_reload_conf() 또는 systemctl reload), SHOW hba_file이 가리키는 파일과 다른 파일을 고쳤거나, 위쪽의 줄 — reject 포함 — 이 먼저 일치했거나, 클라이언트 주소가 짐작과 다른 경우(Docker 브리지나 NAT 주소)입니다. 메시지의 host를 글자 그대로 읽고 pg_hba_file_rules의 error 열을 확인하세요.

클라이언트는 SSL을 쓰는데 왜 메시지가 "no encryption"으로 끝나나요?

기본값 sslmode=prefer에서 libpq는 SSL을 먼저 시도하고, 그 시도가 거부되면 SSL 없이 재시도한 뒤 두 번째 실패를 보고합니다. 서버 로그에는 두 시도가 모두 남습니다. 클라이언트에 sslmode=require를 설정하면 fallback이 멈추고 SSL 시도 자체의 오류가 보입니다.

host all all 0.0.0.0/0 scram-sha-256을 빠른 해결책으로 써도 되나요?

동작은 하고 scram-sha-256이 여전히 올바른 암호를 요구하지만, 포트에 닿을 수 있는 모든 주소가 인증을 시도할 수 있고 비SSL 접속에도 맞습니다. 이미 출발지를 제한하는 방화벽 뒤에서만 쓰고, 메시지에 나온 구체적인 CIDR의 hostssl을 우선하세요.

psql은 되는데 psql -h localhost는 왜 실패하나요?

psql만 치면 Unix 도메인 소켓으로 접속해 local 줄에 맞고, -h localhost는 TCP라서 127.0.0.1/32용 host 줄이 필요합니다 — localhost가 IPv6로 먼저 해석되는 일이 많아 ::1/128도 자주 필요합니다. 어느 주소가 시도됐는지는 메시지에 나옵니다.

pg_hba.conf를 고친 뒤 PostgreSQL을 재시작해야 하나요?

아닙니다. 파일은 시작 시와 SIGHUP에 읽히므로 pg_reload_conf(), pg_ctl reload, systemctl reload가 세션을 끊지 않고 적용합니다. Windows에서는 reload 없이 새 접속부터 반영된다고 문서가 밝힙니다.

참고 자료

Haneul Seo

Infrastructure engineer · 10+ years running Linux fleets

같은 카테고리 다른 글

detected dubious ownershipFixed

Git: fatal: detected dubious ownership in repository

Git 2.35.2의 CVE-2022-24765 수정 이후, Git은 작업 트리나 .git 디렉터리의 소유자가 명령을 실행한 사용자와 다르면 저장소를 읽지 않습니다. 컨테이너, CI 작업, sudo 세션, 공유 드라이브에서 주로 나타납니다. 저장소가 내 것이어야 한다면 소유권을 바로잡고, 아니라면 global 설정의 safe.directory에 정확한 경로를 추가하세요 — Git은 이 설정을 저장소 자신의 설정에서는 무시합니다.

Git
failed calling webhookFixed

Kubernetes: Internal error occurred: failed calling webhook

admission webhook이 쓰기 요청 앞에 서 있는데 API server가 응답을 받지 못했고, 기본값인 failurePolicy: Fail이 그 침묵을 거부로 바꾼 것입니다. 메시지 끝부분이 곧 진단입니다 — context deadline exceeded는 호출이 도달하지 못한 것, no endpoints available은 떠 있는 게 없는 것, x509 줄은 API server가 webhook 인증서를 신뢰하지 않는 것입니다. 원인마다 해결이 다르고, 어느 것도 매니페스트 문제가 아닙니다.

Kubernetes
1205Fixed

MySQL: ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

다른 트랜잭션이 쥐고 있는 행 잠금을 innodb_lock_wait_timeout 만큼 기다리다 포기한 것입니다. sys.innodb_lock_waits 가 막고 있는 세션을 지목하고 KILL 문까지 만들어 주며, blocking_query 가 NULL 이면 블로커가 열린 트랜잭션 위에서 놀고 있다는 뜻입니다. 재시도 로직이 가장 자주 틀리는 지점은 따로 있습니다. 기본값에서는 타임아웃된 문장 하나만 롤백되므로, 트랜잭션은 그대로 열린 채 앞서 잡은 잠금을 전부 쥐고 있습니다.

MySQL
MISCONFFixed

Redis: MISCONF Redis is configured to save RDB snapshots, but it's currently unable to persist to disk

읽기는 되는데 모든 쓰기가 거부됩니다. 마지막 백그라운드 저장이 실패했고 stop-writes-on-bgsave-error 기본값이 yes 이기 때문입니다. 진짜 원인은 로그에 적혀 있습니다 — 디스크 공간 부족, redis 사용자가 쓸 수 없는 dir, rename 시점의 read-only 마운트, 또는 fork 가 Cannot allocate memory 로 실패하는 경우입니다. 원인을 고치고 BGSAVE 한 번만 돌리면 재시작 없이 쓰기가 돌아옵니다. rdb_last_bgsave_status 가 err 에서 ok 로 바뀝니다. stop-writes-on-bgsave-error no 는 쓰기를 즉시 되살리지만 스냅샷은 여전히 실패한 상태로 두므로, 해결이 아니라 의도한 맞교환으로 다뤄야 합니다.

Redis
FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memoryFixed

Node.js: FATAL ERROR: Reached heap limit — JavaScript heap out of memory (종료 코드 134)

V8 힙에는 시스템 메모리와 Node 릴리스에서 유도되는 자기만의 천장이 있고, 가진 RAM 보다 훨씬 낮은 경우가 흔합니다. 빌드나 서버가 거기 닿으면 V8 은 FATAL ERROR: Reached heap limit 과 종료 코드 134 로 스스로 중단합니다. v8.getHeapStatistics().heap_size_limit 으로 실제 한도를 읽은 뒤, 큰 작업은 --max-old-space-size(MiB)나 NODE_OPTIONS 로 올리고, 컨테이너 안에서는 cgroup 한도보다 낮게 잡고, 오래 도는 프로세스의 누수는 --heapsnapshot-near-heap-limit 으로 잡으세요. FATAL ERROR 줄 없이 137 로 끝나면 컨테이너 kill 이지 이 오류가 아닙니다.

Node.js
exec /docker-entrypoint.sh: exec format errorFixed

Docker: 컨테이너 시작 직후 "exec format error" — 플랫폼이 다른 이미지, 에뮬레이터 없음, 셔뱅 없는 스크립트

컨테이너가 첫 명령에서 exec format error 로 끝납니다. 커널의 ENOEXEC, 즉 파일은 있지만 여기서는 실행할 수 없다는 뜻입니다. 실제로는 한 CPU 아키텍처에서 빌드한 이미지(Apple 실리콘 Mac은 linux/arm64 를 만듭니다)를 binfmt_misc 에 QEMU 핸들러가 없는 다른 아키텍처(x86_64 서버)에서 돌리거나, 엔트리포인트 스크립트의 첫 줄이 셔뱅이 아닌 경우입니다. uname -m, docker image inspect, ls /proc/sys/fs/binfmt_misc 로 원인을 가르고, docker buildx build --platform 을 명시(또는 두 플랫폼 매니페스트 목록 발행)하거나, 에뮬레이션이 목적이면 QEMU 등록과 --platform, 스크립트라면 #!/bin/sh 한 줄로 해결합니다.

Docker