> ## Content Index
> Fetch the complete content index at: https://mlog.me/llms.txt
> Use this file to discover other available public pages before exploring further.

# Windows에서 Neon PostgreSQL 5432 연결 실패 해결: 회사망 진단부터 OCI SSH 터널까지
- URL: https://mlog.me/windows-neon-postgresql-5432-oci-ssh-tunnel/
- Published: 2026-09-04T00:40:22.000Z
- Updated: 2026-09-04T00:40:22.000Z
- Description: 회사망에서 Neon PostgreSQL 5432 연결이 실패할 때 Windows 명령으로 원인을 좁히고, 일반 HTTPS 프록시가 맞지 않는 이유와 승인된 OCI SSH 터널을 DBeaver·Spring Boot에 적용하는 절차를 정리합니다.
- Author: mLog
- Tags: 네트워크, 개발, PostgreSQL, SSH, OCI·Oracle Cloud, #방화벽, #SSL·TLS

집이나 휴대폰 핫스팟에서는 Neon PostgreSQL에 연결되는데 회사 네트워크에서만 `5432` 포트 연결이 실패했습니다. 처음에는 Windows 방화벽 규칙만 찾았고, 다음에는 Nginx Proxy Manager로 데이터베이스를 HTTPS 뒤에 숨기면 될 것이라고 생각했습니다. OCI 서버의 `5432` 인바운드를 열어야 하는지도 고민했습니다.

결론부터 말하면 세 접근 모두 문제의 층을 정확히 나누지 못한 상태에서 나온 가설이었습니다. `Test-NetConnection` 실패는 TCP 연결에 실패했다는 뜻이지 회사 방화벽이 원인이라는 판정이 아닙니다. PostgreSQL은 HTTP가 아니므로 일반적인 Nginx Proxy Manager의 Proxy Host로 전달할 수도 없습니다. 로컬 SSH 포워딩을 사용한다면 OCI 서버가 외부에서 받아야 할 포트는 PostgreSQL `5432`가 아니라 SSH 포트 하나입니다.

이번 글은 단순히 명령 몇 줄만 나열하지 않습니다. Windows에서 DNS와 TCP를 분리해 확인하고, 회사망 문제를 증거로 좁히고, 승인된 OCI 서버를 SSH 점프 호스트로 사용해 DBeaver와 Spring Boot까지 연결하는 전체 과정을 정리합니다. 중간에 자주 발생하는 Neon TLS·SNI 문제와 `localhost`의 함정도 함께 다룹니다.

> **3줄 요약**
> 
> - `TcpTestSucceeded : True`는 대상 포트까지 TCP 연결이 성립했다는 뜻일 뿐, PostgreSQL TLS·인증·쿼리까지 성공했다는 뜻은 아닙니다.
> - Nginx의 일반 HTTP Proxy Host는 PostgreSQL 프로토콜을 HTTPS로 바꾸지 못합니다. TCP Stream은 가능하지만 DB를 공개 포트로 노출하는 구조가 되어 별도 보안 설계가 필요합니다.
> - 승인된 SSH 터널은 Windows의 `127.0.0.1:15432`를 OCI를 거쳐 Neon `5432`에 연결합니다. OCI에는 SSH 인바운드만 열고, Neon 구간의 TLS와 호스트명 검증은 그대로 유지해야 합니다.

> **회사 환경에서 먼저 확인할 것**
> 
> 이 글의 SSH 터널은 보안 통제를 몰래 우회하기 위한 방법이 아닙니다. 회사 보안팀의 사전 승인, 허용된 OCI 계정과 DB, 감사 가능한 접속 기록, 자격 증명 반출 금지 정책을 모두 충족하는 경우에만 사용해야 합니다. 정책상 외부 DB 직접 연결이 금지되어 있다면 기술적으로 연결할 수 있어도 진행하면 안 됩니다.

## 목차

1. [먼저 연결 문제를 네 단계로 나눈다](#four-layers)
2. [Windows에서 5432 TCP 연결을 확인한다](#test-netconnection)
3. [DNS와 IPv4·IPv6 문제를 따로 검사한다](#dns)
4. [LISTENING과 0.0.0.0을 정확히 해석한다](#listener)
5. [timeout·refused·인증 오류를 구분한다](#errors)
6. [회사망 차단 여부를 증거로 좁힌다](#company-network)
7. [Nginx Proxy Manager의 HTTPS 프록시가 안 맞는 이유](#npm)
8. [권장 구조: OCI를 승인된 SSH 점프 호스트로 사용한다](#architecture)
9. [OCI 네트워크와 방화벽을 최소 권한으로 구성한다](#oci-network)
10. [Windows OpenSSH로 로컬 포워딩을 연다](#openssh)
11. [Neon TLS·SNI와 localhost 문제를 해결한다](#neon-tls)
12. [DBeaver에서 두 가지 방식으로 연결한다](#dbeaver)
13. [Spring Boot에 터널 접속 정보를 적용한다](#spring-boot)
14. [SSH 계정과 키를 운영 수준으로 제한한다](#hardening)
15. [실패 지점별 문제 해결 순서](#troubleshooting)
16. [성공 조건과 최종 체크리스트](#checklist)

## 1\. 먼저 연결 문제를 네 단계로 나눈다

“DB 접속이 안 된다”는 한 문장 안에는 서로 다른 네 단계가 섞여 있습니다.

1. **DNS**: Neon 호스트명이 IP 주소로 해석되는가?
2. **TCP**: 현재 네트워크에서 해당 IP의 `5432` 포트까지 연결되는가?
3. **TLS**: 클라이언트와 Neon이 암호화 연결을 만들고 서버 인증서를 검증하는가?
4. **PostgreSQL**: 데이터베이스 이름, 역할, 비밀번호, 접근 정책이 맞는가?

각 단계는 다음 단계보다 먼저 통과해야 합니다. DNS가 실패한 상태에서 비밀번호를 바꿔도 소용이 없고, TCP가 막힌 상태에서 JDBC 옵션만 조정해도 연결되지 않습니다. 반대로 TCP가 성공했다고 해서 DB 로그인이 성공한 것도 아닙니다.

진단 도구의 결과도 그 범위 안에서만 해석해야 합니다. Microsoft의 [Test-NetConnection](https://learn.microsoft.com/en-us/powershell/module/nettcpip/test-netconnection?view=windowsserver2025-ps&ref=mlog.me)은 원격 TCP 포트 연결을 검사할 수 있지만 PostgreSQL 로그인이나 SQL 실행을 검사하는 도구는 아닙니다.

이번 사례의 목표는 다음처럼 단계별 증거를 모으는 것이었습니다.

- 회사 PC에서 Neon 호스트명이 올바르게 해석되는지 확인합니다.
- 현재 네트워크에서 Neon `5432`까지 TCP가 성립하는지 확인합니다.
- 다른 승인된 네트워크 결과와 비교합니다.
- 직접 연결이 정책상 불가능하면 승인된 SSH 경로를 설계합니다.
- 마지막으로 실제 PostgreSQL 클라이언트로 TLS와 인증까지 확인합니다.

[목차로 돌아가기](#toc)

## 2\. Windows에서 5432 TCP 연결을 확인한다

PowerShell을 열고 Neon 콘솔의 연결 문자열에서 **호스트명만** 복사합니다. `postgresql://`, 사용자명, 비밀번호, 데이터베이스 경로는 넣지 않습니다.

```powershell
$NeonHost = '<EXACT_NEON_HOST>'

Test-NetConnection `
  -ComputerName $NeonHost `
  -Port 5432 `
  -InformationLevel Detailed

```

중요하게 볼 값은 다음과 같습니다.

- `RemoteAddress`: 실제로 선택된 대상 IP
- `NameResolutionResults`: 이름 해석 결과
- `InterfaceAlias`: 어느 네트워크 인터페이스를 사용했는지
- `SourceAddress`: 어떤 로컬 주소에서 나갔는지
- `NetRoute`: 선택된 경로 정보
- `TcpTestSucceeded`: TCP 연결 수립 성공 여부

결과가 `True`라면 현재 PC에서 대상의 `5432`까지 TCP 연결은 성립했습니다. 하지만 다음 항목은 아직 확인되지 않았습니다.

- Neon이 요구하는 TLS 협상
- 서버 인증서와 호스트명 검증
- PostgreSQL 역할과 비밀번호
- 데이터베이스 존재 여부
- 쿼리 권한

반대로 `False`도 “회사 방화벽 차단 확정”이 아닙니다. DNS 오답, IPv6 경로, 잘못된 호스트명, 라우팅, 원격 ACL, 서비스 상태, 중간 장비의 드롭 등 여러 원인이 같은 결과를 만들 수 있습니다.

`localhost` 검사도 목적이 다릅니다.

```powershell
Test-NetConnection -ComputerName '127.0.0.1' -Port 15432

```

이 검사는 나중에 만든 SSH 터널의 **로컬 리스너**가 열렸는지 보는 데 유용합니다. Neon 자체의 `5432`가 회사망에서 열렸는지를 확인하는 명령은 아닙니다.

[목차로 돌아가기](#toc)

## 3\. DNS와 IPv4·IPv6 문제를 따로 검사한다

호스트명 검사와 IP 검사를 한 번에 섞으면 DNS 문제를 네트워크 문제로 오해하기 쉽습니다. Windows의 [Resolve-DnsName](https://learn.microsoft.com/en-us/powershell/module/dnsclient/resolve-dnsname?view=windowsserver2025-ps&ref=mlog.me)으로 A와 AAAA 레코드를 나눠 확인합니다.

```powershell
$NeonHost = '<EXACT_NEON_HOST>'

Resolve-DnsName -Name $NeonHost -Type A
Resolve-DnsName -Name $NeonHost -Type AAAA

```

확인 순서는 다음과 같습니다.

1. 호스트명에 오타가 없는지 봅니다.
2. A 또는 AAAA 응답이 존재하는지 봅니다.
3. `Test-NetConnection`의 `RemoteAddress`가 어느 주소인지 확인합니다.
4. 회사 DNS와 다른 승인된 네트워크의 DNS 결과가 크게 다른지 비교합니다.

가능하다면 호스트명과 반환된 IP를 각각 테스트해 봅니다.

```powershell
Test-NetConnection -ComputerName $NeonHost -Port 5432 -InformationLevel Detailed
Test-NetConnection -ComputerName '<RESOLVED_IP>' -Port 5432 -InformationLevel Detailed

```

단, **IP로 TCP를 시험한 결과를 실제 JDBC 주소로 그대로 사용하면 안 됩니다.** Neon과 같은 관리형 서비스는 TLS SNI와 호스트명을 이용해 엔드포인트를 식별할 수 있고, 인증서도 DNS 호스트명에 발급됩니다. IP 직접 접속은 TCP 경로를 분리해 확인하는 진단용입니다.

또 하나의 함정은 `localhost`입니다. `localhost`와 `127.0.0.1`은 지금 명령을 실행하는 컴퓨터를 가리킵니다. 다른 PC에서 `localhost`를 입력하면 DB 서버가 아니라 그 다른 PC 자신에게 접속합니다. 환경에 따라 `localhost`가 `::1`로 먼저 해석될 수도 있으므로 로컬 터널을 검사할 때는 `127.0.0.1`을 명시하는 편이 분명합니다.

[목차로 돌아가기](#toc)

## 4\. LISTENING과 0.0.0.0을 정확히 해석한다

직접 운영하는 Windows 서버의 포트를 확인하거나 SSH 터널의 로컬 포트를 검사할 때는 다음 명령을 사용합니다.

```powershell
Get-NetTCPConnection -State Listen -LocalPort 15432 |
  Select-Object LocalAddress, LocalPort, State, OwningProcess

netstat -ano -p tcp | findstr ':15432'

```

[Get-NetTCPConnection](https://learn.microsoft.com/en-us/powershell/module/nettcpip/get-nettcpconnection?view=windowsserver2025-ps&ref=mlog.me)은 주소, 포트, 상태와 소유 프로세스 ID를 보여 줍니다. [netstat](https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/netstat?ref=mlog.me)의 `-a`, `-n`, `-o`는 각각 수신 포트 포함, 숫자 표시, PID 표시 역할을 합니다.

주소는 다음처럼 해석합니다.

- `127.0.0.1:15432`: 현재 PC의 IPv4 loopback에서만 접근할 수 있습니다. 로컬 SSH 터널의 안전한 기본값입니다.
- `[::1]:15432`: IPv6 loopback에서만 접근할 수 있습니다.
- `0.0.0.0:5432`: 서버의 모든 적절한 로컬 IPv4 인터페이스에 바인딩한 wildcard 리스너입니다.
- `[::]:5432`: IPv6 any 주소 계열의 리스너입니다.

`0.0.0.0`에서 `LISTENING`이라고 보여도 외부 접속이 허용됐다는 뜻은 아닙니다. Windows 방화벽, OCI NSG, Security List, 라우팅, NAT와 상위 방화벽이 여전히 차단할 수 있습니다. 반대로 클라이언트의 DB Host 칸에 `0.0.0.0`을 넣는 것도 잘못입니다. 이것은 서버의 바인딩 개념이지 접속할 원격 주소가 아닙니다.

이번 구성에서는 터널을 띄운 뒤 `127.0.0.1:15432`가 보여야 합니다. `0.0.0.0:15432`로 열렸다면 같은 네트워크의 다른 장치에 터널이 노출될 수 있으므로 명령의 bind 주소를 즉시 확인합니다.

[목차로 돌아가기](#toc)

## 5\. timeout·refused·인증 오류를 구분한다

에러 메시지는 원인을 확정하지는 못해도 조사 순서를 크게 줄여 줍니다.

### 연결 시간 초과

Windows 소켓의 `WSAETIMEDOUT(10060)` 계열은 정해진 시간 안에 적절한 응답을 받지 못했다는 뜻입니다. 중간 방화벽의 silent drop, 잘못된 경로, 호스트 장애, 패킷 손실 등이 후보입니다. “응답이 없었다”는 증거이지 어느 장비가 막았다는 증거는 아닙니다.

### 연결 거부

`WSAECONNREFUSED(10061)` 계열은 대상이 연결을 적극적으로 거부했다는 뜻입니다. 우선 해당 주소와 포트에 서비스가 실제로 리슨하는지, 포트가 맞는지, 컨테이너나 VM의 포트 매핑이 맞는지 확인합니다. 방화벽이 reject 응답을 만들 수도 있으므로 이것만으로 서비스 문제라고 확정하지는 않습니다.

### 이름 해석 실패

`Name or service not known`, `UnknownHostException`과 비슷한 오류는 TCP보다 DNS부터 확인합니다. Neon 콘솔의 정확한 호스트를 다시 복사하고 회사 DNS 정책도 확인합니다.

### TLS 또는 인증서 실패

`certificate verify failed`, `hostname mismatch`, `PKIX path building failed`는 TCP 연결 이후 TLS 단계의 문제입니다. 포트를 더 여는 대신 연결에 사용한 논리 호스트명, 시스템 신뢰 저장소, pgJDBC 버전을 확인합니다.

### PostgreSQL 인증 실패

`password authentication failed`, `database does not exist`, `permission denied`는 TCP와 TLS를 통과한 뒤 PostgreSQL 단계에서 발생합니다. Neon 역할, 비밀번호, 데이터베이스 이름과 권한을 확인합니다.

Microsoft의 [Windows 소켓 오류 코드 문서](https://learn.microsoft.com/en-us/windows/win32/winsock/windows-sockets-error-codes-2?ref=mlog.me)도 timeout, refused, network unreachable, host unreachable을 서로 다른 상태로 구분합니다. 한 오류를 모두 “방화벽”으로 묶지 않는 것이 핵심입니다.

[목차로 돌아가기](#toc)

## 6\. 회사망 차단 여부를 증거로 좁힌다

회사 네트워크가 의심될 때 가장 유용했던 방법은 동일한 PC와 동일한 명령으로 경로만 바꿔 비교하는 것이었습니다. 단, 휴대폰 핫스팟이나 외부 회선을 업무 PC에 연결하는 행위가 회사 정책상 허용되는지 먼저 확인해야 합니다.

승인된 비교 절차는 다음과 같습니다.

1. 회사 유선 또는 Wi-Fi에서 `Resolve-DnsName`과 `Test-NetConnection` 결과를 저장합니다.
2. 같은 PC에서 승인된 대체 네트워크로 전환합니다.
3. 같은 호스트명과 같은 포트로 명령을 다시 실행합니다.
4. `RemoteAddress`, `InterfaceAlias`, `SourceAddress`, `TcpTestSucceeded`를 비교합니다.
5. 결과와 시각을 보안팀에 전달해 방화벽·프록시 로그와 대조합니다.

회사망에서는 반복해서 timeout이 나고 승인된 외부 네트워크에서는 즉시 성공한다면 회사 경로의 정책 또는 장비를 강하게 의심할 수 있습니다. 그래도 다음 가능성은 남습니다.

- 회사 DNS가 다른 IP를 반환했습니다.
- IPv4와 IPv6 선택이 달라졌습니다.
- VPN이 특정 경로를 강제했습니다.
- EDR이나 로컬 보안 에이전트가 프로세스별로 통제했습니다.
- Neon IP Allow 정책이 서로 다른 출발지 IP를 다르게 처리했습니다.

따라서 보안팀에 “5432를 열어 주세요”만 요청하지 말고 대상 FQDN, 선택된 IP, TCP 포트, 테스트 시간, 출발지 네트워크와 오류 형태를 함께 제공합니다. 변경 이력과 승인 근거가 남고, 불필요하게 넓은 예외를 만들 가능성도 줄어듭니다.

[목차로 돌아가기](#toc)

## 7\. Nginx Proxy Manager의 HTTPS 프록시가 안 맞는 이유

회사에서 `443`은 허용되니 Neon PostgreSQL을 도메인과 HTTPS 뒤에 두면 되지 않을까 생각하기 쉽습니다. 여기에는 프로토콜 차이가 있습니다.

Nginx의 일반 [proxy\_pass HTTP 모듈](https://nginx.org/en/docs/http/ngx%5Fhttp%5Fproxy%5Fmodule.html?ref=mlog.me)과 Nginx Proxy Manager의 Proxy Host는 HTTP·HTTPS 요청을 전달하는 계층입니다. PostgreSQL 클라이언트가 보내는 것은 HTTP 요청이 아니라 PostgreSQL 와이어 프로토콜입니다. 인증, SQL, 결과 프레임이 HTTP로 자동 변환되지 않습니다.

Nginx에는 별도로 [Stream 모듈](https://nginx.org/en/docs/stream/ngx%5Fstream%5Fproxy%5Fmodule.html?ref=mlog.me)이 있어 raw TCP를 전달할 수 있고, Nginx Proxy Manager도 [Streams 기능](https://nginxproxymanager.com/guide/?ref=mlog.me)을 제공합니다. 하지만 이것은 PostgreSQL을 HTTPS로 바꾸는 기능이 아닙니다.

Stream을 선택하면 다음 운영 문제가 생깁니다.

- 외부에서 접근할 TCP 포트를 별도로 노출해야 합니다.
- NPM을 Docker로 운영하면 해당 stream 포트도 컨테이너에 publish해야 합니다.
- 같은 IP의 `443`을 기존 HTTPS 서비스가 이미 사용 중이면 단순히 함께 사용할 수 없습니다.
- PostgreSQL의 전통적인 TLS 협상은 첫 바이트부터 일반 HTTPS TLS가 아니라 `SSLRequest`로 시작할 수 있습니다. [PostgreSQL 프로토콜 문서](https://www.postgresql.org/docs/current/protocol-flow.html?ref=mlog.me)의 흐름도 이를 구분합니다.
- 인터넷에 공개된 DB 프록시는 인증 시도, 스캔, DoS와 자격 증명 공격 표면을 넓힙니다.
- 프록시 한 대가 새로운 장애 지점과 감사 지점이 됩니다.

따라서 NPM Stream은 기술적으로 가능한 별도 아키텍처일 수 있지만, “Proxy Host에서 도메인 하나 만들고 SSL 인증서를 붙이는 방법”과는 다릅니다. 이번처럼 특정 개발자만 승인된 경로로 접속해야 한다면 DB 포트를 공개하는 Stream보다 로컬 SSH 포워딩이 더 단순하고 노출 범위도 작습니다.

[목차로 돌아가기](#toc)

## 8\. 권장 구조: OCI를 승인된 SSH 점프 호스트로 사용한다

로컬 포워딩의 흐름은 다음 세 구간입니다.

1. Windows 애플리케이션은 `127.0.0.1:15432`에 접속합니다.
2. OpenSSH가 이 트래픽을 암호화해 OCI VM의 SSH 포트로 전달합니다.
3. OCI VM의 SSH 데몬이 Neon의 정확한 호스트명과 `5432`로 새 TCP 연결을 만듭니다.

OpenSSH의 [\-L](https://man.openbsd.org/ssh?ref=mlog.me) 옵션은 로컬에 수신 포트를 만들고, 연결이 오면 원격 SSH 서버 쪽에서 지정한 `host:port`로 접속합니다. 그래서 다음 두 방향의 방화벽 요구사항이 서로 다릅니다.

- Windows → OCI: OCI SSH 포트 인바운드
- OCI → Neon: TCP `5432` 아웃바운드

OCI가 외부에서 PostgreSQL 연결을 직접 받는 구조가 아니므로 OCI의 `5432` 인바운드는 필요하지 않습니다. OCI에 PostgreSQL이나 TCP 프록시를 설치할 이유도 없습니다.

이 설계가 회사 정책에 맞으려면 최소한 다음 조건이 필요합니다.

- 회사가 해당 OCI 점프 호스트와 외부 DB 사용을 승인했습니다.
- SSH 인바운드는 승인된 공인 IP 또는 VPN CIDR로 제한합니다.
- OCI 계정, SSH 키와 Neon DB 계정이 개인용이 아니라 관리 대상입니다.
- OCI와 Neon의 접속 로그를 보존할 수 있습니다.
- 개발자는 업무 종료 후 터널을 닫습니다.

승인 없이 개인 클라우드 서버를 만들고 SSH를 허용된 포트처럼 위장하는 것은 이 구조의 사용 사례가 아닙니다.

[목차로 돌아가기](#toc)

## 9\. OCI 네트워크와 방화벽을 최소 권한으로 구성한다

Oracle은 subnet 전체에 적용되는 Security List보다 특정 VNIC에 적용되는 NSG 사용을 권장합니다. 다만 [OCI Security Rules 문서](https://docs.oracle.com/en-us/iaas/Content/Network/Concepts/securityrules.htm?ref=mlog.me)에서 설명하듯 NSG와 Security List를 동시에 적용하면 허용 규칙은 교집합이 아니라 **합집합**입니다. NSG를 좁혀도 Security List에 넓은 규칙이 남아 있으면 그대로 허용됩니다.

점프 호스트에 필요한 규칙은 다음 원칙으로 잡습니다.

### SSH 인바운드

- 출발지: 승인된 회사·VPN 공인 IPv4 `/32` 또는 관리되는 CIDR
- 프로토콜: TCP
- 목적지 포트: 실제 SSH 포트 `22` 또는 조직이 정한 포트
- 상태: 일반적으로 stateful

`0.0.0.0/0 → 22`는 모든 IPv4 주소에서 인증을 시도할 수 있게 합니다. IPv6를 사용한다면 `::/0 → 22`가 따로 남아 있지 않은지도 확인합니다. SSH 포트를 10022처럼 바꾸는 것은 자동 스캔 소음을 줄일 수 있지만 접근 제어를 대신하지는 않습니다.

### Neon 아웃바운드

- 목적지: 운영 정책이 허용하는 Neon 대상 CIDR
- 프로토콜: TCP
- 목적지 포트: `5432`
- 상태: stateful

OCI 보안 규칙은 일반적으로 FQDN을 목적지로 쓰지 못하므로 Neon IP 변경 가능성과 운영 부담을 고려해야 합니다. 목적지 IP를 안정적으로 관리할 수 없다면 `0.0.0.0/0`의 **TCP 5432 아웃바운드**가 기능상 필요할 수 있지만, 이것은 모든 인터넷 PostgreSQL 목적지로 나갈 수 있게 하는 타협입니다. 보안팀과 로그·탐지·갱신 절차를 함께 정해야 합니다.

stateful egress에 대한 응답 트래픽은 자동 허용되므로 임시 포트용 인바운드 규칙을 별도로 만들지 않습니다. 무엇보다 `0.0.0.0/0 → inbound 5432`는 이 구조에서 완전히 불필요합니다.

### DNS와 라우팅

OCI VM은 `<EXACT_NEON_HOST>`를 해석할 수 있어야 합니다. OCI 기본 Internet and VCN Resolver는 `169.254.169.254:53`을 사용하며, 이 link-local 대역은 VCN 보안 규칙의 일반 적용 대상이 아닙니다. 그렇다고 OS 방화벽에서 기본 DNS를 막아도 된다는 뜻은 아닙니다. 커스텀 DNS를 사용한다면 그 리졸버의 UDP 53과 필요 시 TCP 53을 명시적으로 허용합니다.

공용 subnet의 공인 IP VM은 Internet Gateway와 기본 경로가, private subnet은 NAT Gateway와 기본 경로가 필요할 수 있습니다. `0.0.0.0/0 → Internet/NAT Gateway`라는 **라우팅 규칙**과 `0.0.0.0/0`에서 들어오는 트래픽을 허용하는 **인바운드 보안 규칙**은 전혀 다른 의미입니다.

마지막으로 OCI NSG·Security List뿐 아니라 VM의 nftables/iptables 같은 호스트 방화벽도 함께 봅니다. OCI Ubuntu 이미지에서 기존 규칙을 지우거나 UFW를 무작정 활성화하면 Oracle이 유지하는 link-local 통신까지 깨질 수 있으므로 현재 규칙을 백업하고 공식 절차로 변경합니다.

[목차로 돌아가기](#toc)

## 10\. Windows OpenSSH로 로컬 포워딩을 연다

먼저 Windows에서 OCI SSH 포트까지 연결되는지 확인합니다.

```powershell
$OciHost = '<OCI_PUBLIC_IP_OR_DNS>'
$SshPort = 22

Test-NetConnection `
  -ComputerName $OciHost `
  -Port $SshPort `
  -InformationLevel Detailed

```

OCI 서버에서도 Neon 이름 해석과 `5432` 아웃바운드를 확인합니다. `nc`가 설치된 서버라면 다음처럼 검사할 수 있습니다.

```bash
getent ahosts '<EXACT_NEON_HOST>'
nc -vz -w 5 '<EXACT_NEON_HOST>' 5432

```

그다음 Windows PowerShell에서 터널을 엽니다.

```powershell
$NeonHost = '<EXACT_NEON_HOST>'
$OciHost = '<OCI_PUBLIC_IP_OR_DNS>'
$SshPort = 22

ssh.exe -N -T `
  -i "$env:USERPROFILE\.ssh\oci.key" `
  -p $SshPort `
  -L "127.0.0.1:15432:${NeonHost}:5432" `
  -o IdentitiesOnly=yes `
  -o ExitOnForwardFailure=yes `
  -o ServerAliveInterval=30 `
  -o ServerAliveCountMax=3 `
  "ubuntu@$OciHost"

```

옵션의 역할은 다음과 같습니다.

- `-N`: 원격 명령을 실행하지 않고 포워딩만 합니다.
- `-T`: 가상 터미널을 만들지 않습니다.
- `-L 127.0.0.1:...`: 로컬 PC에서만 접근 가능한 포트를 엽니다.
- `IdentitiesOnly=yes`: 지정한 키만 인증에 사용합니다.
- `ExitOnForwardFailure=yes`: 로컬 포트 점유 등 포워딩 생성 실패 시 종료합니다.
- `ServerAliveInterval`과 `ServerAliveCountMax`: 서버가 약 90초 동안 응답하지 않으면 죽은 연결을 정리합니다.

첫 접속 때 표시되는 OCI 호스트 키 지문은 콘솔이나 조직의 신뢰 가능한 경로로 대조합니다. 편의를 위해 `StrictHostKeyChecking=no`를 넣지 않습니다. `-g`, 빈 bind 주소, `*`, `0.0.0.0`도 사용하지 않습니다.

터널을 실행한 PowerShell 창은 정상이라면 아무 출력 없이 계속 열린 상태로 남을 수 있습니다. 다른 PowerShell 창에서 로컬 리스너를 확인합니다.

```powershell
Get-NetTCPConnection -State Listen -LocalPort 15432 |
  Select-Object LocalAddress, LocalPort, State, OwningProcess

Test-NetConnection -ComputerName '127.0.0.1' -Port 15432

```

여기서 성공한 것은 SSH의 로컬 리스너까지입니다. `ExitOnForwardFailure`도 Neon의 지속적인 정상 상태까지 보장하지 않습니다. 실제 DB 클라이언트로 접속해야 터널 뒤 구간과 PostgreSQL 인증을 함께 확인할 수 있습니다.

[목차로 돌아가기](#toc)

## 11\. Neon TLS·SNI와 localhost 문제를 해결한다

SSH가 암호화하는 구간은 Windows에서 OCI까지입니다. OCI에서 Neon까지는 별도의 TCP 연결이므로 Neon이 요구하는 TLS를 제거하면 안 됩니다. Neon의 [보안 연결 문서](https://neon.com/docs/connect/connect-securely?ref=mlog.me)는 `sslmode`에 따라 암호화와 서버 검증 수준이 달라진다고 설명합니다.

pgJDBC에서 중요한 차이는 다음과 같습니다.

- `sslmode=require`: 암호화는 요구하지만 서버 인증서와 호스트명을 검증하지 않습니다.
- `sslmode=verify-full`: 신뢰할 수 있는 CA인지 확인하고 인증서의 호스트명이 접속 호스트와 맞는지도 검증합니다.

[pgJDBC SSL 문서](https://jdbc.postgresql.org/documentation/ssl/?ref=mlog.me)도 보안이 중요한 환경에는 `verify-full`을 권장합니다. `NonValidatingFactory`, `ssl=false`, 무조건 신뢰하는 사용자 정의 검증기는 해결책이 아닙니다.

여기서 로컬 터널의 대표적인 함정이 생깁니다.

```text
jdbc:postgresql://127.0.0.1:15432/appdb?sslmode=verify-full

```

접속 URL의 호스트는 `127.0.0.1`인데 서버 인증서는 Neon의 실제 DNS 호스트명에 발급됐습니다. 따라서 `verify-full`에서 hostname mismatch가 발생할 수 있습니다. Neon은 TLS SNI의 엔드포인트 호스트명으로 연결을 라우팅하므로 드라이버와 구성에 따라 `Endpoint ID is not specified` 오류도 생길 수 있습니다.

가장 안전한 원칙은 **네트워크 경로는 loopback으로 보내되 클라이언트가 사용하는 논리 호스트명은 정확한 Neon 호스트로 유지하는 것**입니다.

외부 `ssh.exe -L` 터널을 Spring Boot나 psql과 함께 사용할 때는 승인된 관리 절차에 따라 `C:\Windows\System32\drivers\etc\hosts`에 Neon 호스트를 임시 매핑할 수 있습니다. 관리자 권한으로 편집하되 나중에 정확히 찾을 수 있도록 고유한 주석을 붙입니다.

```text
127.0.0.1 <EXACT_NEON_HOST> # mlog-neon-tunnel

```

그 상태에서 URL은 Neon 호스트명과 로컬 포워드 포트를 사용합니다.

```text
jdbc:postgresql://<EXACT_NEON_HOST>:15432/<DB_NAME>?sslmode=verify-full

```

Windows는 `<EXACT_NEON_HOST>:15432`를 `127.0.0.1:15432`로 보내지만, pgJDBC는 URL에 남은 Neon 호스트명으로 SNI와 인증서 호스트 검증을 수행할 수 있습니다. SSH 명령 안의 `<EXACT_NEON_HOST>:5432`는 OCI 서버가 해석하므로 로컬 hosts 매핑의 영향을 받지 않습니다.

hosts 파일 변경은 시스템 전체에 영향을 줍니다. 정확한 한 줄만 추가한 뒤 아래처럼 OS 해석 결과가 loopback인지 확인합니다. `Resolve-DnsName`은 DNS 서버를 직접 질의할 수 있으므로 hosts 적용 여부 확인에는 .NET의 이름 해석 결과가 더 분명합니다.

```powershell
[System.Net.Dns]::GetHostAddresses('<EXACT_NEON_HOST>')

```

테스트가 끝나면 `# mlog-neon-tunnel`이 붙은 한 줄만 제거하고 DNS 캐시를 비웁니다.

```powershell
Clear-DnsClientCache

```

전체 hosts 파일을 오래된 백업으로 덮어쓰면 그 사이 추가된 다른 정상 항목까지 사라질 수 있으므로 피합니다. 회사 관리 PC에서는 관리자 권한과 변경 승인이 필요할 수 있습니다. WSL, Docker와 원격 JVM은 Windows와 네트워크 네임스페이스 및 hosts 파일을 공유한다고 가정하면 안 됩니다.

환경상 호스트명을 유지할 수 없을 때 Neon의 endpoint 옵션으로 라우팅 문제를 해결하고 `sslmode=require`를 쓰는 구성도 가능하지만, 이는 `verify-full`과 동급이 아닙니다.

```text
jdbc:postgresql://127.0.0.1:15432/<DB_NAME>?sslmode=require&options=endpoint%3D<ENDPOINT_ID>

```

이 형태는 암호화는 유지해도 pgJDBC가 서버 인증서와 호스트명을 검증하지 않습니다. 운영 기본값으로 복사하기보다 조직의 보안 검토를 거친 제한적 대안 또는 진단용으로 취급합니다.

[목차로 돌아가기](#toc)

## 12\. DBeaver에서 두 가지 방식으로 연결한다

DBeaver에서는 자체 SSH 터널 기능과 외부 `ssh.exe` 터널 중 하나만 선택합니다. 두 개를 동시에 켜면 포트와 경로가 꼬여 진단이 더 어려워집니다. Neon의 SNI와 `verify-full` 호스트명 검증을 확실하게 유지하려면 **외부 OpenSSH 터널과 hosts 매핑을 함께 쓰는 방법 B를 기본안**으로 권장합니다. 내장 터널은 DBeaver·드라이버 버전에 따라 실제 JDBC 대상을 localhost의 임의 포트로 바꿀 수 있으므로 방법 A를 선택했다면 최종 TLS 검증 결과까지 확인해야 합니다.

### 방법 A: DBeaver가 SSH 터널을 관리하는 호환 구성

Main 또는 PostgreSQL 연결 화면에는 Neon 정보를 그대로 넣습니다.

- Host: `<EXACT_NEON_HOST>`
- Port: `5432`
- Database: Neon 콘솔의 데이터베이스
- Username: Neon 역할
- Password: 비밀 저장 정책에 따라 입력

SSH 설정에는 OCI 정보를 넣습니다.

- SSH Host/IP: OCI 점프 호스트
- SSH Port: `22` 또는 승인된 포트
- User: 전용 SSH 사용자
- Authentication: Public Key 또는 승인된 Agent
- Host verification 우회: 사용 안 함

SSL 모드는 가능하면 `verify-full`로 두고, DBeaver의 **Certificate hostname** 필드가 보인다면 정확한 Neon 호스트를 지정합니다. 먼저 `Test tunnel configuration`으로 SSH만 확인하고, 이어서 `Test Connection`으로 PostgreSQL TLS와 인증까지 확인합니다. DBeaver 버전이나 드라이버가 내부 URL을 localhost로 바꿔 SNI 또는 인증서 검증에 실패한다면 검증을 끄지 말고 방법 B로 전환하는 편이 명확합니다.

DBeaver 공식 [SSH Configuration](https://dbeaver.com/docs/dbeaver/SSH-Configuration/?ref=mlog.me) 문서의 고급 포트 포워딩을 사용한다면 local host는 `127.0.0.1`, remote host는 정확한 Neon DNS, remote port는 `5432`로 지정합니다. DBeaver가 임의 로컬 포트를 자동 선택할 수 있으므로 JDBC URL을 직접 작성하는 모드에서는 실제 포트와 일치하는지 확인합니다.

### 방법 B: 외부 OpenSSH 터널 사용 — 기본 권장

앞 절의 `ssh.exe -L`을 실행한 뒤 DBeaver의 SSH 탭은 끕니다. Neon 호스트를 loopback으로 임시 매핑했다면 Main 설정은 다음과 같습니다.

- Host: `<EXACT_NEON_HOST>`
- Port: `15432`
- SSL mode: `verify-full`
- Database·Username·Password: Neon 콘솔 값

외부 터널은 포트와 수명을 직접 통제할 수 있어 Spring Boot와 같은 경로를 재사용하기 쉽습니다. 반면 터널 프로세스가 종료되면 DBeaver 연결도 끊기므로 실행·재연결·종료 절차를 따로 관리해야 합니다.

관리 작업에는 이름에 `-pooler`가 없는 direct endpoint가 이해하기 쉽습니다. Neon의 pooled endpoint는 많은 단기 연결을 효율적으로 처리할 때 유용하지만 transaction pooling의 제약이 있으므로 세션 상태에 의존하는 관리 도구와 기능은 확인이 필요합니다. [Neon 연결 풀링 문서](https://neon.com/docs/connect/connection-pooling?ref=mlog.me)를 기준으로 목적에 맞게 선택합니다.

[목차로 돌아가기](#toc)

## 13\. Spring Boot에 터널 접속 정보를 적용한다

Spring Boot는 JDBC URL을 읽을 뿐 SSH 터널을 자동으로 만들지 않습니다. [Spring Boot 데이터 액세스 문서](https://docs.spring.io/spring-boot/reference/data/sql.html?ref=mlog.me)의 `spring.datasource.url`도 실제 JDBC 접속 주소입니다. 따라서 애플리케이션을 시작하기 전에 외부 SSH 터널이 살아 있어야 합니다.

Neon 호스트명을 loopback으로 안전하게 매핑한 구성은 다음처럼 작성할 수 있습니다.

```yaml
spring:
  datasource:
    url: jdbc:postgresql://${NEON_DB_HOST}:15432/${NEON_DB_NAME}?sslmode=verify-full
    username: ${NEON_DB_USER}
    password: ${NEON_DB_PASSWORD}
    hikari:
      connection-timeout: 10000
      validation-timeout: 5000

```

환경변수의 예시는 값의 형태만 보여 줍니다. 실제 비밀번호를 소스 코드, Git 저장소, 블로그 화면이나 셸 히스토리에 남기지 않습니다.

```powershell
$env:NEON_DB_HOST = '<EXACT_NEON_HOST>'
$env:NEON_DB_NAME = '<DB_NAME>'
$env:NEON_DB_USER = '<DB_ROLE>'

```

필요하면 pgJDBC의 `channelBinding=require`를 별도 검증 후 추가할 수 있지만, 채널 바인딩은 CA와 호스트명 검증을 대신하지 않습니다. 우선 `sslmode=verify-full`이 정상 동작하는지 확인하는 것이 분명합니다.

애플리케이션 시작 전에는 다음 순서를 지킵니다.

1. OCI에서 Neon DNS와 `5432` 아웃바운드가 되는지 확인합니다.
2. Windows에서 SSH 터널을 실행합니다.
3. `127.0.0.1:15432` 리스너를 확인합니다.
4. 정확한 Neon 호스트명과 `15432`로 psql 또는 DBeaver 접속을 시험합니다.
5. 마지막으로 Spring Boot를 실행합니다.

Spring Boot가 Docker 컨테이너, WSL 또는 원격 서버에서 실행된다면 `localhost`는 Windows 호스트가 아니라 **그 프로세스가 속한 네트워크 공간 자신**입니다. DBeaver가 만든 임의 로컬 터널도 컨테이너가 자동으로 공유하지 않습니다. 터널과 애플리케이션을 같은 승인된 네트워크 네임스페이스에 두거나, 해당 런타임에 맞는 별도 접속 설계를 해야 합니다.

개발 중 연결 풀의 여러 커넥션이 동시에 실패하면 로그가 크게 늘 수 있습니다. HikariCP pool size를 늘려 문제를 해결하려 하지 말고 단일 psql 연결부터 성공시킨 뒤 풀 설정을 복원합니다.

[목차로 돌아가기](#toc)

## 14\. SSH 계정과 키를 운영 수준으로 제한한다

터널이 동작한 뒤에는 “연결 성공”을 “안전한 운영”으로 바꾸는 작업이 필요합니다.

### Windows 개인키

SSH 개인키는 비밀번호와 같은 수준으로 취급합니다.

- passphrase가 있는 키와 `ssh-agent` 사용을 권장합니다.
- 개인키 파일은 현재 사용자만 접근하도록 ACL을 확인합니다.
- 키 파일을 메신저, 이메일, 공유 폴더에 복사하지 않습니다.
- 저장소와 백업에서 키가 제외됐는지 확인합니다.
- 퇴사, 역할 변경, 사고 발생 시 즉시 폐기할 수 있어야 합니다.

Windows 도메인과 로캘에 따라 ACL 이름과 상속 상태가 달라질 수 있으므로 인터넷의 `icacls` 한 줄을 그대로 실행해 권한을 망가뜨리지 않습니다. Microsoft의 [Windows OpenSSH 키 관리](https://learn.microsoft.com/en-us/windows-server/administration/openssh/openssh%5Fkeymanagement?ref=mlog.me)와 조직의 관리 절차를 따르고, 필요하면 공식 Win32-OpenSSH 권한 복구 도구를 사용합니다.

### OCI의 전용 터널 계정

점프 호스트에 전용 사용자를 만들 수 있다면 일반 셸 작업 계정과 분리합니다. OpenSSH 서버의 `Match User`와 다음 옵션으로 포워딩 범위를 제한할 수 있습니다.

```text
Match User neon-tunnel
    AllowTcpForwarding local
    PermitOpen <EXACT_NEON_HOST>:5432
    AllowAgentForwarding no
    X11Forwarding no
    PermitTTY no

```

`PermitOpen`은 허용된 대상 호스트와 포트만 포워딩하도록 제한합니다. 변경 전 별도 관리 세션을 유지하고 `sshd` 설정 문법을 검사한 뒤, 운영체제에 맞는 방식으로 reload합니다. 잘못된 SSH 설정은 원격 복구 경로를 끊을 수 있으므로 즉석 변경하지 않습니다.

### Neon 접근 제어

Neon 플랜에서 IP Allow를 사용한다면 Neon이 보는 출발지는 개발자 PC가 아니라 터널의 마지막 연결을 만드는 OCI VM 또는 NAT Gateway의 공인 IP입니다. [Neon IP Allow 문서](https://neon.com/docs/introduction/ip-allow?ref=mlog.me)를 기준으로 고정 egress IP만 허용합니다.

DB 역할도 관리자 하나를 공유하지 않습니다. 애플리케이션 전용 역할, 읽기 전용 운영 역할, 마이그레이션 역할을 나누고 필요한 스키마 권한만 줍니다. SSH 키와 DB 비밀번호의 회전 주기, 접속 로그 보존 기간, 퇴직자 회수 절차까지 문서화해야 터널이 임시 해결책에서 관리 가능한 접속 경로가 됩니다.

[목차로 돌아가기](#toc)

## 15\. 실패 지점별 문제 해결 순서

한꺼번에 설정을 바꾸지 말고 아래 순서에서 처음 실패한 지점을 찾습니다.

### 1단계: 회사 PC에서 Neon DNS가 실패한다

- Neon 콘솔에서 호스트명을 다시 복사합니다.
- A와 AAAA 응답을 나눠 확인합니다.
- 회사 DNS 정책과 VPN split DNS를 확인합니다.
- 임의 공개 DNS로 바꾸기 전에 보안팀에 문의합니다.

### 2단계: 회사 PC에서 Neon 5432만 실패한다

- 같은 시각의 `Test-NetConnection -InformationLevel Detailed` 결과를 보존합니다.
- 승인된 다른 네트워크의 같은 테스트와 비교합니다.
- 방화벽, EDR, VPN, Neon IP Allow 로그를 확인합니다.
- 결과 하나만으로 차단 장비를 단정하지 않습니다.

### 3단계: OCI SSH 포트가 실패한다

- OCI 공인 IP와 실제 SSH 포트를 확인합니다.
- NSG와 Security List 양쪽에서 넓거나 누락된 규칙을 확인합니다.
- 승인 출발지 공인 IP가 바뀌지 않았는지 봅니다.
- Internet Gateway·route와 OS 방화벽·sshd 상태를 확인합니다.

### 4단계: SSH에 로그인되지만 터널이 생성되지 않는다

- `15432`가 다른 프로세스에 점유됐는지 확인합니다.
- `ExitOnForwardFailure=yes`가 반환한 오류를 읽습니다.
- `AllowTcpForwarding`과 `PermitOpen` 정책을 확인합니다.
- 로컬 bind 주소가 `127.0.0.1`인지 확인합니다.

### 5단계: 로컬 포트는 열렸지만 DB 연결이 timeout이다

- OCI에서 Neon 호스트명을 해석할 수 있는지 봅니다.
- OCI에서 Neon `5432` 아웃바운드를 시험합니다.
- NSG, Security List, OS 방화벽과 public/private subnet route를 확인합니다.
- Neon IP Allow에 OCI 또는 NAT의 고정 egress IP가 있는지 확인합니다.

### 6단계: Endpoint ID is not specified가 발생한다

- 최신 PostgreSQL 드라이버를 사용합니다.
- JDBC URL의 호스트가 정확한 Neon endpoint인지 확인합니다.
- 로컬 터널에서도 SNI에 Neon 호스트가 유지되는 구조를 사용합니다.
- 제한적 대안으로 Neon endpoint 옵션을 검토하되 인증서 검증 수준을 함께 평가합니다.

### 7단계: hostname mismatch 또는 인증서 오류가 발생한다

- `verify-full`을 끄기 전에 URL이 `localhost`나 IP인지 확인합니다.
- 정확한 Neon 호스트명을 논리 접속 호스트로 유지합니다.
- DBeaver의 Certificate hostname과 신뢰 저장소를 확인합니다.
- `PKIX path building failed` 또는 `root.crt` 오류라면 검증을 끄지 않습니다. 현재 pgJDBC SSL factory가 사용하는 `%APPDATA%\postgresql\root.crt` 또는 `sslrootcert`, DBeaver의 CA Certificate 설정, 선택한 SSL factory의 JRE truststore 중 실제 적용 대상을 확인하고 조직이 승인한 CA를 구성합니다.
- 회사 TLS 검사 장비가 DB TLS에 개입하는지는 보안팀과 확인합니다.

### 8단계: password authentication failed가 발생한다

- Neon 역할과 데이터베이스를 다시 확인합니다.
- 비밀번호의 특수문자가 URL에서 잘못 해석되지 않았는지 봅니다.
- URI 문자열보다 별도 username/password 속성을 사용합니다.
- 자격 증명을 채팅이나 로그에 붙여 넣지 말고 필요하면 회전합니다.

이 순서를 따르면 포트를 열었다 닫았다 하거나 SSL 옵션을 무작정 낮추는 시행착오를 줄일 수 있습니다.

[목차로 돌아가기](#toc)

## 16\. 성공 조건과 최종 체크리스트

이번 문제에서 실패했던 접근은 공통점이 있었습니다. `5432`가 안 된다는 현상만 보고 모든 층을 한 번에 해결하려 했습니다.

- `TcpTestSucceeded=False`를 곧바로 회사 방화벽으로 확정했습니다.
- 일반 HTTPS Proxy Host가 PostgreSQL까지 전달할 것이라고 생각했습니다.
- OCI에 inbound `5432`를 열면 터널이 될 것이라고 생각했습니다.
- TLS 오류가 나자 `sslmode`를 낮추는 방향부터 떠올렸습니다.
- DBeaver와 Spring Boot의 `localhost`가 항상 같은 곳이라고 가정했습니다.

성공한 방식은 반대였습니다. DNS, TCP, SSH, TLS, PostgreSQL 인증을 분리했고, 각 도구의 결과를 해당 층의 증거로만 사용했습니다. 외부에 DB 포트를 공개하지 않고 Windows loopback에만 터널을 열었으며, OCI는 Neon으로 나가는 연결만 만들게 했습니다. 마지막에는 Neon 호스트명을 유지해 TLS 서버 검증까지 통과시키는 것을 완료 조건으로 삼았습니다.

실제 적용 전후에는 다음 항목을 확인합니다.

- \[ \] 회사 보안팀이 외부 Neon DB와 OCI SSH 점프 호스트 사용을 승인했습니다.
- \[ \] `Resolve-DnsName`으로 Neon A·AAAA 결과를 확인했습니다.
- \[ \] `Test-NetConnection` 결과를 DNS·TCP 범위에서만 해석했습니다.
- \[ \] 회사망과 승인된 비교 네트워크의 결과와 시간을 기록했습니다.
- \[ \] NPM Proxy Host가 PostgreSQL용 해결책이 아님을 확인했습니다.
- \[ \] OCI SSH 인바운드는 승인 IP 또는 VPN CIDR로 제한했습니다.
- \[ \] OCI inbound `5432`는 열지 않았습니다.
- \[ \] OCI에서 Neon `5432` 아웃바운드, DNS와 route가 동작합니다.
- \[ \] NSG와 Security List의 허용 규칙을 함께 확인했습니다.
- \[ \] 로컬 포워드는 `127.0.0.1:15432`에만 바인딩했습니다.
- \[ \] SSH 호스트 키를 검증했고 `StrictHostKeyChecking=no`를 쓰지 않았습니다.
- \[ \] Neon 연결에서 TLS를 유지하고 가능하면 `sslmode=verify-full`을 사용합니다.
- \[ \] DBeaver 내장 터널과 외부 OpenSSH 터널을 동시에 켜지 않았습니다.
- \[ \] Spring Boot가 실행되는 네트워크 공간에서 터널 경로가 실제로 보입니다.
- \[ \] DB 비밀번호와 개인키를 Git·로그·스크린샷에 남기지 않았습니다.
- \[ \] 작업이 끝난 뒤 터널을 종료하고 임시 hosts 매핑을 제거했습니다.

마지막으로 가장 중요한 문장은 이것입니다.

> 포트 연결 실패를 해결한다는 것은 모든 방화벽을 여는 일이 아니라, 어느 층에서 실패했는지 증명하고 필요한 한 경로만 최소 권한으로 허용하는 일입니다.

[목차로 돌아가기](#toc)

## 참고한 공식 문서

- [Microsoft Test-NetConnection](https://learn.microsoft.com/en-us/powershell/module/nettcpip/test-netconnection?view=windowsserver2025-ps&ref=mlog.me)
- [Microsoft Resolve-DnsName](https://learn.microsoft.com/en-us/powershell/module/dnsclient/resolve-dnsname?view=windowsserver2025-ps&ref=mlog.me)
- [OpenSSH ssh와 로컬 포워딩](https://man.openbsd.org/ssh?ref=mlog.me)
- [Nginx HTTP Proxy Module](https://nginx.org/en/docs/http/ngx%5Fhttp%5Fproxy%5Fmodule.html?ref=mlog.me)
- [Nginx Stream Proxy Module](https://nginx.org/en/docs/stream/ngx%5Fstream%5Fproxy%5Fmodule.html?ref=mlog.me)
- [PostgreSQL SSL 프로토콜 흐름](https://www.postgresql.org/docs/current/protocol-flow.html?ref=mlog.me)
- [pgJDBC SSL 설정](https://jdbc.postgresql.org/documentation/ssl/?ref=mlog.me)
- [DBeaver SSH Configuration](https://dbeaver.com/docs/dbeaver/SSH-Configuration/?ref=mlog.me)
- [OCI Security Rules](https://docs.oracle.com/en-us/iaas/Content/Network/Concepts/securityrules.htm?ref=mlog.me)
- [OCI Internet Gateway](https://docs.oracle.com/en-us/iaas/Content/Network/Tasks/managingIGs.htm?ref=mlog.me)
- [OCI NAT Gateway](https://docs.oracle.com/en-us/iaas/Content/Network/Tasks/NATgateway.htm?ref=mlog.me)
- [Neon 애플리케이션 연결](https://neon.com/docs/connect/connect-from-any-app?ref=mlog.me)
- [Neon 연결 오류](https://neon.com/docs/connect/connection-errors?ref=mlog.me)
- [Neon IP Allow](https://neon.com/docs/introduction/ip-allow?ref=mlog.me)