[토이프로젝트] Nginx로 Reverse Proxy 구성하기: (4) 애플리케이션 서버 보호 기능 검증
지난 포스팅에서 Nginx가 Reverse Proxy로 동작할 수 있도록 최소한의 구성을 적용해봤습니다.
클라이언트가 Nginx의 80 포트로 요청하면 Nginx가 해당 요청을 애플리케이션 서버로 전달할 수 있습니다. 하지만 아직 문제가 하나 남아 있습니다.
현재 실행 중인 컨테이너를 확인해보면 애플리케이션 서버인 api-01의 8000 포트가 호스트의 8001 포트와 매핑되어 있습니다.
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
2bd57b3e33a6 nginx "/docker-entrypoint.…" 4 days ago Up 4 days 0.0.0.0:80->80/tcp, [::]:80->80/tcp nginx-rp
85b2de399fc1 simple-api "java -jar simple-ap…" 5 days ago Up 4 days 0.0.0.0:8001->8000/tcp, [::]:8001->8000/tcp api-01
따라서 클라이언트는 Nginx를 통해 정상적으로 접근할 수도 있지만, 호스트의 8001 포트로 요청하여 애플리케이션 서버에 직접 접근하는 것도 가능합니다.
flowchart LR
Client["클라이언트"]
subgraph VM["가상머신"]
Nginx["nginx-rp<br/>포트 80"]
App["api-01<br/>호스트 포트 8001<br/>컨테이너 포트 8000"]
end
Client -->|"정상 경로 : 80"| Nginx
Nginx --> App
Client -.->|"직접 접근 : 8001"| App
Reverse Proxy는 구성했지만, 애플리케이션 서버로 직접 접근할 수 있는 경로가 여전히 남아 있는 상태인 것입니다.
그래서 이번 포스팅에서 Reverse Proxy로 구성된 Nginx를 통해서만 접근할 수 있도록 구조를 변경해서 이 문제를 해결해보도록 하겠습니다.
1. 구조 변경
먼저 외부에 있는 클라이언트가 애플리케이션 서버에 직접 접근할 수 있는 경로를 제거해야 합니다.
하지만 Reverse Proxy 역할을 하는 Nginx는 애플리케이션 서버와 통신할 수 있어야 합니다.
이를 위해 별도의 Docker Network를 구성했습니다. 동일한 사용자 정의 Docker Network에 연결된 컨테이너끼리는 Docker가 제공하는 DNS를 이용해 컨테이너 이름으로 서로를 찾을 수 있습니다.
예시
- http://api-01:8000
구성하려는 구조는 다음과 같습니다.
flowchart LR
Client["클라이언트"]
subgraph VM["가상머신"]
subgraph Network["rp-network"]
Nginx["nginx-rp"]
App["api-01<br/>포트 8000"]
Nginx --> App
end
end
Client --> Nginx
외부에는 Nginx의 포트만 공개하고, 애플리케이션 서버의 포트는 호스트에 공개하지 않는 방식입니다.
2. Docker Network 구성
rp-network라는 이름으로 Docker Network를 생성했습니다.
$ docker network create rp-network
이어서 호스트의 Docker Network의 목록에 rp-network가 추가되어 있는 것을 확인할 수 있었습니다.
$ docker network ls
NETWORK ID NAME DRIVER SCOPE
4347c31fdfbe bridge bridge local
278e34f32899 host host local
1efc34a92de3 none null local
65dbef218e5a rp-network bridge local
3. 애플리케이션 컨테이너 재구성
애플리케이션 서버를 재구성하기 위해 기존에 구성된 컨테이너(api-01)를 제거했습니다.
$ docker rm -f api-01
그리고 기존 컨테이너 구성에서 -p 8001:8000를 삭제하고, --network rp-network 옵션을 추가했습니다.
$ docker run -d \
--name api-01 \
--network rp-network \
-e SERVER_NAME=api-01 \
simple-api
-p 8001:8000삭제 : 이제는 더 이상 애플리케이션 서버의 포트를 외부에 공개하지 않습니다.--network rp-network추가 :api-01컨테이너를rp-network에 연결합니다.
컨테이너를 다시 실행하면 이제 호스트의 8001 포트는 더 이상 api-01 컨테이너와 연결되지 않습니다. 외부의 클라이언트가 이전처럼 192.168.56.11:8001로 요청해도 애플리케이션 서버까지 전달되지 않습니다.
반면 동일한 rp-network에 연결된 컨테이너에서는 Docker DNS를 이용해 api-01:8000으로 애플리케이션 서버에 접근할 수 있습니다. 따라서 Nginx 역시 rp-network에 연결해야 합니다.
변경 전
flowchart LR
Host1["호스트<br/>포트 8001"]
App1["api-01<br/>포트 8000"]
Host1 -->|"8001 → 8000"| App1
변경 후
flowchart LR
subgraph Network["rp-network"]
App2["api-01<br/>포트 8000"]
end
4. Nginx의 Reverse Proxy 설정 변경
nginx.conf 파일에서 proxy_pass 설정의 변경도 필요합니다.
수정된 nginx.conf
events {
}
http {
server {
listen 80;
location / {
proxy_pass http://api-01:8000;
}
}
}
- 기존 : http://192.168.56.11:8001
- 변경 : http://api-01:8000
api-01컨테이너의 Spring Boot 애플리케이션은8000포트에서 동작하고 있으므로,Nginx는api-01:8000으로 요청을 전달하도록 설정합니다.
요청 경로는 다음과 같이 변경됩니다.
flowchart LR
Client["클라이언트"]
subgraph VM["호스트(가상머신)"]
subgraph Network["rp-network"]
Nginx["nginx-rp<br/>Nginx"]
App["api-01<br/>Spring Boot<br/>포트 8000"]
Nginx -->|"proxy_pass<br/>http://api-01:8000"| App
end
end
Client -->|"포트 80"| Nginx
이제 Nginx는 호스트의 8001 포트를 거치지 않고 Docker Network를 통해 api-01 컨테이너에 직접 요청을 전달합니다.
5. Nginx 컨테이너 재구성
Reverse Proxy 구성 변경을 위해 기존에 구성된 Nginx 컨테이너를 제거했습니다.
$ docker rm -f nginx-rp
그리고 --network rp-network 옵션을 추가하여 컨테이너를 다시 실행했습니다.
$ docker run -d \
--name nginx-rp \
--network rp-network \
-p 80:80 \
-v /home/sanghoon/nginx/nginx.conf:/etc/nginx/nginx.conf:ro \
nginx
--network rp-network추가 :Nginx를api-01과 동일한Docker Network에 연결합니다.
전체 구조는 다음과 같습니다.
flowchart LR
Client["클라이언트"]
subgraph VM["가상머신"]
Host80["호스트<br/>포트 80"]
subgraph Network["rp-network"]
subgraph NginxContainer["nginx-rp 컨테이너"]
Nginx["Nginx<br/>포트 80"]
end
subgraph AppContainer["api-01 컨테이너"]
App["Spring Boot<br/>포트 8000"]
end
Nginx -->|"api-01:8000"| App
end
Host80 -->|"80 → 80"| Nginx
end
Client -->|"192.168.56.11:80"| Host80
애플리케이션 서버의 호스트 포트 매핑을 제거했기 때문에 외부 클라이언트는 애플리케이션 서버에 직접 접근할 수 없습니다. 반면 동일한 rp-network에 연결된 Nginx 컨테이너는 Docker DNS를 이용해 api-01:8000으로 요청을 전달할 수 있습니다.
6. 애플리케이션 서버의 보호 기능 검증
검증해야 할 내용은 두 가지입니다.
- 외부 클라이언트에서 애플리케이션 서버로 직접 접근할 수 없는가?
Nginx를 통한 요청은 정상적으로 처리되는가?
6.1. 애플리케이션 서버 직접 접근 확인
구조 변경 전에 사용했던 8001 포트로 애플리케이션 서버에 직접 접근해봤습니다.
$ curl http://192.168.56.11:8001/api/server
결과는 연결 실패였습니다.
curl: (7) Failed to connect to 192.168.56.11 port 8001 after 0 ms: Couldn't connect to server
컨테이너를 재구성하면서 해당 포트 매핑을 삭제했기 때문에 애플리케이션 컨테이너로 전달할 경로가 더 이상 존재하지 않습니다. 이를 통해 외부 클라이언트가 이전처럼 애플리케이션 서버로 직접 접근할 수 없게 된 것을 확인했습니다.
6.2. Reverse Proxy를 통한 접근 확인
반면 Nginx를 통해 요청을 보내면 정상적으로 응답이 반환되는 것은 확인할 수 있었습니다.
$ curl http://192.168.56.11/api/server
{
"time":"2026-06-11T14:08:35.498203914",
"serverName":"api-01"
}
실제 요청 흐름은 다음과 같습니다.
sequenceDiagram
participant Client as 클라이언트
participant Nginx as nginx-rp
participant App as api-01
Client->>Nginx: GET /api/server
Nginx->>App: GET /api/server
App-->>Nginx: 200 OK + JSON
Nginx-->>Client: 200 OK + JSON
외부 클라이언트는 Nginx의 주소만 이용하지만, Nginx는 Docker Network 내부에서 api-01:8000으로 애플리케이션 서버를 찾아 요청을 전달합니다.
결과적으로 다음 두 가지를 모두 확인할 수 있었습니다.
192.168.56.11:8001을 이용한 애플리케이션 서버 직접 접근은 실패192.168.56.11:80을 이용한 Reverse Proxy 경유 접근은 성공
7. 다음 포스팅
애플리케이션 서버에 대한 외부의 직접 접근 경로를 제거하고, 외부 진입점을 Reverse Proxy 역할을 하도록 구성된 Nginx로 한정하도록 구조를 변경해봤습니다.
다만 이것만으로 애플리케이션의 모든 보안 문제가 해결되는 것은 아닙니다. Reverse Proxy는 외부 진입점을 통제하고 백엔드 서버의 직접 노출을 줄이는 데 도움을 주지만, 애플리케이션 자체의 인증·인가, 취약점 관리, 네트워크 접근 제어 등의 보안 대책은 별도로 필요합니다.
다음 포스팅에서는 애플리케이션 서버를 여러 개로 확장하고, Nginx가 요청을 여러 서버에 분산하는 로드밸런싱 기능을 직접 검증해보겠습니다.