실무에서는 Nginx 같은 웹서버를 앞에 두고, 뒤쪽에 Spring Boot나 Node.js 같은 애플리케이션 서버를 두는 구성을 자주 쓴다. 처음 보면 둘 다 HTTP 요청을 받는 서버처럼 보이기 때문에 굳이 나눌 필요가 있나 싶다. 하지만 트래픽, 보안, 배포 구조를 생각하기 시작하면 역할을 분리하는 이유가 꽤 분명해진다.
웹서버는 요청의 앞단에서 정적 파일을 빠르게 응답하고, 필요하면 요청을 적절한 WAS로 넘긴다. Nginx나 Apache가 대표적이다. 반면 WAS는 로그인, 결제, DB 조회, 외부 API 호출처럼 서비스의 실제 비즈니스 로직을 처리한다. Spring Boot, Node.js, Tomcat 같은 서버가 여기에 해당한다.
WAS가 모든 일을 하면 생기는 문제
WAS도 정적 파일을 응답할 수 있다. 작은 서비스에서는 하나의 서버가 HTML, CSS, 이미지, API 요청을 모두 처리해도 큰 문제가 없을 수 있다. 문제는 트래픽이 늘어났을 때다. WAS는 DB 조회, 인증, 계산처럼 상대적으로 무거운 작업을 처리해야 하는데, 이미지나 JS 파일 요청까지 계속 맡으면 핵심 로직을 처리할 여유가 줄어든다.
웹서버를 앞에 두면 정적 파일은 웹서버가 빠르게 응답하고, 동적 요청만 WAS로 전달할 수 있다. 예를 들어 이벤트 페이지에 사용자가 몰렸을 때 이미지와 CSS는 Nginx나 CDN이 처리하고, WAS는 응모 처리나 당첨 계산 같은 핵심 로직에 집중하는 식이다.
사용자 → Nginx → Spring/Node.js → DB, 외부 API
├─ 정적 파일 직접 응답
├─ SSL 종료
├─ 로드밸런싱
└─ 동적 요청만 WAS로 전달보안과 노출 범위
WAS는 보통 데이터베이스나 내부 시스템과 연결되어 있다. 이 서버가 외부에 직접 노출되면 공격 표면이 넓어진다. 웹서버를 앞단에 두면 클라이언트는 WAS의 실제 주소를 몰라도 되고, 외부 요청은 먼저 웹서버를 통과하게 된다.
또 SSL 인증서를 웹서버에서 관리하면 인증서 갱신과 암복호화 처리를 한 곳에 모을 수 있다. 악성 요청 필터링, 특정 경로 차단, 요청 크기 제한 같은 정책도 앞단에서 처리할 수 있다.
location /api {
proxy_pass http://backend:8080;
}이런 리버스 프록시 구조에서는 클라이언트가 /api로 요청을 보내도 실제 WAS 주소는 외부에 드러나지 않는다.
로드밸런싱과 확장성
서비스가 커지면 WAS를 한 대만 두기 어렵다. 여러 WAS 인스턴스를 띄우고, 웹서버가 요청을 분산하면 특정 서버 하나에 장애가 나더라도 나머지 서버로 요청을 보낼 수 있다.
upstream backend {
server was1:8080;
server was2:8080;
server was3:8080;
}이 구조의 장점은 웹서버와 WAS를 서로 다른 기준으로 확장할 수 있다는 점이다. 동적 요청이 많으면 WAS를 늘리고, 정적 콘텐츠 요청이 많으면 CDN이나 캐싱 전략을 조정한다. 모든 역할이 한 서버에 묶여 있으면 이런 분리가 어렵다.
웹서버가 주로 맡는 일
웹서버는 단순히 “정적 파일 서버”에 그치지 않는다. 보통은 리버스 프록시, 로드밸런싱, SSL 종료, 정적 파일 캐싱을 함께 담당한다. 클라이언트 요청을 받아 어느 백엔드로 보낼지 결정하고, HTTPS 암복호화를 처리하고, 자주 요청되는 파일은 캐시해 빠르게 응답한다.
물론 웹서버를 앞에 둔다고 모든 문제가 자동으로 해결되는 것은 아니다. 웹서버 자체가 단일 장애 지점이 될 수 있으므로 고가용성이 필요하면 웹서버도 이중화해야 한다. SSL을 웹서버에서 종료한 뒤 내부 통신을 HTTP로 보낼 경우, 내부 네트워크 보안도 함께 고려해야 한다. Keep-Alive 설정이 잘못되면 요청마다 TCP 연결을 새로 맺어 성능이 떨어질 수도 있다.
정리하면 웹서버와 WAS를 나누는 이유는 서버를 더 복잡하게 만들기 위해서가 아니다. 빠르게 처리할 일과 신중하게 처리할 일을 분리하기 위해서다. 웹서버는 요청의 앞단에서 가벼운 일과 라우팅을 맡고, WAS는 서비스의 핵심 로직에 집중한다. 이 역할 분리가 성능, 보안, 확장성을 함께 챙기는 기본 구조가 된다.