한 줄 정의
Web Server는 이미 만들어져 있는 파일을 찾아서 그대로 건네주고, WAS는 프로그램을 실행해서 그 순간에 답을 새로 만들어 낸다.
게임 홈페이지를 떠올려 보자. 로고 이미지와 패치 노트 배경은 누가 접속하든 똑같은 파일이다. 반면 “내 캐릭터 랭킹”은 접속한 사람이 누구인지, 지금 점수가 얼마인지에 따라 달라진다. 앞의 것을 담당하는 쪽이 Web Server, 뒤의 것을 담당하는 쪽이 WAS다.
WAS(Web Application Server)는 한국 개발 현장에서 특히 널리 쓰는 표현이다. 넓게는 웹 애플리케이션을 실행하는 서버 소프트웨어를 뜻한다. 영어권 자료에서는
application server,servlet container,backend server같은 말이 더 흔하니 함께 알아 두면 좋다.
먼저, 서버가 뭔가?
서버(Server)는 다른 컴퓨터의 요청을 받아 무언가를 돌려주는 컴퓨터, 또는 그 컴퓨터에서 돌아가는 프로그램이다. 요청하는 쪽을 클라이언트(Client)라고 부른다. 브라우저가 대표적인 클라이언트다.
“서버”는 기계 이름이 아니라 역할 이름이라는 점이 중요하다. 노트북 한 대에 Web Server와 WAS와 Database를 모두 띄울 수도 있고, 반대로 하나의 역할을 수백 대에 나눠 담을 수도 있다.
게임의 “서버”와는 조금 다르다. 스킬을 쓰면 상대 화면에 실시간으로 보이는 그 서버는 보통 HTTP가 아니라 TCP/UDP로 계속 연결을 유지하는 게임 서버다. 이 글에서 다루는 Web Server·WAS는 “요청 한 번에 응답 한 번”으로 끝나는 HTTP 서버다. 같은 게임 회사라도 홈페이지를 띄우는 서버와 전투를 처리하는 서버는 별개다.
정적 콘텐츠와 동적 콘텐츠
Web Server와 WAS를 가르는 기준은 결국 이 두 단어다.
구분 | 뜻 | 게임 홈페이지 예 |
|---|---|---|
정적(static) | 요청이 올 때마다 계산하지 않고, 디스크에 있는 파일을 그대로 전달한다 | 게임 로고, 패치 노트 이미지, CSS, JavaScript 파일 |
동적(dynamic) | 요청이 온 그 순간에 누가·언제·어떤 데이터로 요청했는지 따져 결과를 새로 만든다 | 내 캐릭터 랭킹, 내 인벤토리, 로그인 결과 |
흔한 오해 하나. “정적”은 파일이 영원히 안 바뀐다는 뜻이 아니다. 운영자가 새 패치 이미지를 올리면 파일은 당연히 바뀐다. 핵심은 요청을 처리하는 그 순간에 사용자별 계산을 하느냐다. 파일을 통째로 갈아 끼우는 건 계산이 아니다.
아래 두 응답을 비교하면 차이가 분명해진다. 같은 주소를 다른 사람이 요청했다고 생각하고 보자.
GET /images/patch-2026-08.png → 누가 요청해도 똑같은 이미지 바이트
GET /api/me/ranking → 홍길동이 요청하면 1,204위
→ 김철수가 요청하면 87위
위쪽은 Web Server가 파일 하나 읽어서 그대로 뱉으면 끝난다. 아래쪽은 “요청한 사람이 누구인지” 확인하고, Database에서 점수를 꺼내고, 순위를 계산해야 한다. 이 계산을 담당하는 것이 WAS다.
한 페이지 안에서 둘은 같이 일어난다
여기서 가장 헷갈리기 쉬운 지점을 짚고 가자. 정적과 동적은 페이지 단위로 갈리는 것이 아니다. 한 화면을 띄우는 동안에도 정적인 요청과 동적인 요청이 나란히 일어난다.
아래 파일을 보자. 댓글 목록을 서버에서 받아 와 화면에 그리는 최소 예제다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>1000PAGE 블로그 동적 테스트</title>
</head>
<body>
<h1>댓글 불러오기</h1>
<h2>댓글</h2>
<p id="status">불러오는 중...</p>
<ul id="comments"></ul>
<script>
// 이 파일 안에는 댓글이 한 줄도 적혀 있지 않다.
// 열리는 순간 서버에 물어서 받아 온다. 그래서 열 때마다 결과가 달라진다.
const API = 'https://1000page.pages.dev/api/comments?doc=dev-tools/web-server-was-database';
fetch(API)
.then(res => res.json())
.then(json => {
document.getElementById('status').textContent =
'댓글 ' + json.data.total + '개';
document.getElementById('comments').innerHTML = json.data.comments
.map(c => '<li><b>' + c.authorName + '</b>' + c.bodyHtml + '</li>')
.join('');
});
</script>
</body>
</html>
다운로드 후 열어보면 이 게시물의 댓글이 나온다.
이 파일 하나를 여는 동안 서버에는 요청이 두 번 간다. 그리고 둘의 성격이 다르다.
① GET /comments.html → Web Server가 디스크의 파일을 그대로 준다 [정적]
↓ 브라우저가 HTML을 읽고 <script>를 실행한다
② GET /api/comments?doc=… → WAS가 Database를 조회해 그 자리에서 만든다 [동적]
↓ 브라우저가 받은 JSON으로 <li>를 그린다
①에서 서버가 건넨 것은 언제 요청하든 똑같은 파일이다. 파일 안에 댓글이 없으니 계산할 것도 없다. 그래서 정적이다.
②는 다르다. 누군가 방금 댓글을 달았다면 결과가 달라진다. 서버가 Database를 뒤져 그 순간의 답을 새로 만들어 돌려준다. 이것이 동적이고, 이 일을 하는 것이 WAS다.
{
"ok": true,
"data": {
"total": 2,
"comments": [
{ "authorName": "천성훈", "bodyHtml": "<p>정리 감사합니다</p>" },
{ "authorName": "홍길동", "bodyHtml": "<p>이제 이해했어요</p>" }
]
}
}
직접 해 볼 수 있다. 위 코드를
comments.html로 저장해서 브라우저로 열어 보자. 이 글에 달린 실제 댓글이 뜬다. 댓글을 하나 더 달고 새로고침하면 파일은 그대로인데 결과가 달라지는 것을 눈으로 볼 수 있다. 최대 10초 정도 늦게 반영될 수 있는데, 서버가 응답을 10초간 캐시하기 때문이다.
그래서 “이 파일은 정적, 저 파일은 동적”처럼 파일에 꼬리표를 붙이는 것은 정확하지 않다. 물어야 할 것은 “지금 이 요청에 대해 서버가 무엇을 했는가”다. 파일을 그대로 건넸으면 정적, 코드를 돌려 답을 만들었으면 동적이다.
Web Server란?
Web Server는 브라우저의 HTTP 요청을 받아 HTTP 응답을 돌려주는 서버 소프트웨어다. 대표 제품은 Nginx와 Apache HTTP Server다. 서버 컴퓨터 자체를 “웹 서버”라 부르기도 하지만, 이 글에서는 그 위에서 돌아가는 소프트웨어를 뜻한다.
맡는 일은 크게 두 가지다.
파일 전달. 요청 경로에 해당하는 파일을 디스크에서 찾아 응답에 실어 보낸다.
요청 전달(리버스 프록시). 자기가 처리할 수 없는 요청은 뒤에 있는 WAS로 넘기고, 돌아온 응답을 브라우저에 전달한다.
여기에 더해 HTTPS 처리, 캐시, 압축, 접근 제어, 여러 대의 WAS로 요청을 나누는 부하 분산도 앞단에서 함께 맡는 경우가 많다.
리버스 프록시(reverse proxy)라는 말이 낯설 수 있다. 프록시는 “대신 심부름하는 중개자”라는 뜻이다. 브라우저 쪽에 붙어 사용자를 대신하면 정방향 프록시, 서버 쪽에 붙어 뒤쪽 서버를 대신하면 리버스 프록시다. 브라우저는 Nginx 주소 하나만 알면 되고, 그 뒤에 WAS가 몇 대 있는지는 알 필요가 없다.
설정을 보면 두 역할이 한눈에 들어온다. Nginx 예시다.
server {
listen 80;
server_name game.example.com;
# 1) /images/ 로 시작하는 요청 → 디스크의 파일을 그대로 준다 (정적)
location /images/ {
root /var/www/game;
}
# 2) /api/ 로 시작하는 요청 → 8080 포트의 WAS에게 넘긴다 (동적)
location /api/ {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
이 파일 하나가 이 글의 요약이다. /images/는 Web Server가 직접 끝내고, /api/는 WAS에게 넘긴다. proxy_pass가 바로 “길 안내”에 해당하는 줄이다.
WAS란?
WAS는 우리가 작성한 애플리케이션 코드를 실행할 환경을 제공한다. 로그인한 사용자가 누구인지 확인하고, 입력값을 검사하고, 규칙에 따라 계산하고, 필요하면 Database에 데이터를 요청한다. 그리고 그 결과를 HTML이나 JSON 같은 응답으로 만든다.
Java 웹 애플리케이션에서는 Apache Tomcat이 가장 익숙한 예다. Tomcat은 Servlet·JSP 규격을 구현해서, 우리가 만든 클래스가 HTTP 요청을 받을 수 있게 해 준다. Spring Boot로 프로젝트를 만들면 기본적으로 Tomcat이 안에 내장되어 함께 실행된다.
다만 모든 언어가 “WAS”라는 단어를 쓰지는 않는다. 자리는 같지만 부르는 이름이 다르다.
기술 스택 | WAS 자리에 오는 것 |
|---|---|
Java / Spring | Apache Tomcat, Jetty, Undertow |
Node.js | Express·NestJS 등이 돌아가는 Node 프로세스 자체 |
Python | Gunicorn·Uvicorn 위의 Django·FastAPI 애플리케이션 |
PHP | PHP-FPM |
아주 단순화한 WAS 코드다. Spring Boot 기준이며, 이 메서드 하나가 “동적 처리”의 전부라고 봐도 된다.
@RestController
public class RankingController {
private final RankingService rankingService;
public RankingController(RankingService rankingService) {
this.rankingService = rankingService;
}
// GET /api/me/ranking
@GetMapping("/api/me/ranking")
public RankingResponse myRanking(@AuthenticationPrincipal Long userId) {
// 1) 요청한 사람이 누구인지 확인한다 → 사람마다 달라지는 지점
// 2) DB에서 그 사람의 점수와 순위를 꺼낸다
// 3) 화면에 필요한 모양으로 가공해서 돌려준다
return rankingService.findRankingOf(userId);
}
}
{
"characterName": "홍길동",
"score": 48210,
"rank": 1204,
"totalPlayers": 92830
}
같은 코드지만 userId가 달라지면 응답도 달라진다. 파일에는 이 JSON이 어디에도 저장되어 있지 않다. 요청이 올 때마다 만들어진다. 이것이 동적 콘텐츠다.
요청 흐름 따라가기
정적 요청 — 패치 이미지를 열 때
브라우저 → Web Server → 디스크의 이미지 파일
브라우저 ← Web Server ← 디스크의 이미지 파일
브라우저가
GET /images/patch-2026-08.png를 요청한다.Web Server가 설정된 경로에서 그 파일을 찾는다.
파일 내용과 함께 상태 코드
200, 콘텐츠 종류image/png등을 응답한다.
WAS도 Database도 등장하지 않는다. 애플리케이션 코드가 단 한 줄도 실행되지 않는다.
동적 요청 — 내 랭킹을 볼 때
브라우저 → Web Server → WAS → Database
브라우저 ← Web Server ← WAS ← Database
브라우저가
GET /api/me/ranking을 요청한다.Web Server는
/api/로 시작하니 자기 일이 아니라고 판단하고 WAS로 넘긴다.WAS가 요청에 담긴 로그인 정보를 확인해 “이 사람이 누구인지” 판별한다.
WAS가 Database에 그 사용자의 점수와 순위를 조회한다.
Database가 돌려준 값을 WAS가 화면에 필요한 JSON으로 가공한다.
응답이 Web Server를 거쳐 브라우저로 돌아오고, 브라우저가 이를 랭킹 화면으로 그린다.
이 그림은 대표적인 구조일 뿐 유일한 정답이 아니다. 작은 서비스는 Web Server 없이 WAS가 브라우저 요청을 직접 받기도 하고, 큰 서비스는 사이에 CDN, 로드 밸런서, API 게이트웨이, 캐시 서버, 여러 대의 WAS가 더 들어간다.
Database는 어디에 있나
Database(DB)는 데이터를 안전하게 저장하고 빠르게 찾아 주는 전문 시스템이다. 캐릭터 점수, 회원 정보, 아이템 목록이 여기에 들어간다. MySQL, PostgreSQL, Oracle 등이 대표적이다.
중요한 규칙이 하나 있다. 브라우저는 Database에 직접 접속하지 않는다. 반드시 WAS를 거친다. 이유는 단순하다.
보안. 브라우저가 DB에 직접 붙을 수 있다면, DB 접속 정보를 사용자 컴퓨터까지 내려보내야 한다. 그 순간 누구나 남의 데이터를 읽고 지울 수 있다.
규칙 적용. “내 인벤토리만 볼 수 있다”, “골드는 음수가 될 수 없다” 같은 규칙은 코드로 검사해야 한다. 그 코드가 도는 곳이 WAS다.
연결 관리. DB가 동시에 감당할 수 있는 연결 수는 한정적이다. WAS가 연결 몇 개를 미리 만들어 돌려쓰는 커넥션 풀(connection pool)로 이를 관리한다.
그래서 셋의 관계를 한 줄로 정리하면 이렇게 된다.
Web Server : 파일을 주거나, 길을 안내한다
WAS : 코드를 실행해 그때그때 답을 만든다
Database : 데이터를 저장하고 찾아 준다 (WAS만 접근한다)
API 서버는 또 뭔가?
API 서버는 Web Server·WAS와 나란히 놓고 비교할 대상이 아니다. 층이 다른 말이다. Web Server와 WAS가 “어떤 소프트웨어냐”를 가리킨다면, API 서버는 “무엇을 응답하느냐”를 가리킨다.
API 서버란 화면(HTML) 대신 데이터(주로 JSON)를 응답하는 서버를 부르는 말이다. 그리고 그 API 서버의 실체는 대부분 WAS다. 즉 “JSON을 응답하도록 만든 WAS = API 서버”라고 이해하면 거의 맞다.
그래서 “WAS는 웹 페이지를 제공하고 API 서버는 데이터를 제공한다”라고 외우면 절반만 맞다. 무엇을 응답하느냐는 맞는 구분이지만, 그 둘이 서로 다른 종류의 서버인 것은 아니다. 똑같은 Tomcat 한 대가 어떤 주소에서는 HTML을, 다른 주소에서는 JSON을 응답할 수 있다. 응답 방식을 정하는 것은 서버 제품이 아니라 우리가 짠 코드다.
응답 방식 | 무엇을 돌려주나 | 화면은 누가 만드나 |
|---|---|---|
서버 렌더링 | 완성된 HTML | WAS가 데이터를 HTML에 끼워 넣어 완성해서 보낸다 |
API 방식 | JSON 같은 데이터 | 브라우저의 JavaScript나 앱이 받아서 화면을 그린다 |
API 방식이 널리 쓰이는 이유는 같은 서버를 웹·안드로이드·iOS가 함께 쓸 수 있기 때문이다. 화면 모양은 각자 다르지만 “내 랭킹은 1204위”라는 데이터는 똑같으니, 데이터만 주고 화면은 각자 그리게 하는 편이 효율적이다.
웹 브라우저 ┐
안드로이드 앱 ├→ API 서버(WAS) → Database
iOS 앱 ┘
헷갈리기 쉬운 지점 정리.
API 서버는 WAS와 반대말이 아니라 WAS의 한 가지 쓰임새다. 앞의 Spring Boot 예제 코드도 JSON을 응답하므로 API 서버라고 부를 수 있다.@RestController의Rest가 바로 그 뜻이다.
왜 굳이 둘로 나눌까?
한 프로그램이 다 하면 안 되나 싶을 수 있다. 실제로 작은 서비스는 그렇게 한다. 그럼에도 규모가 커지면 나누는 이유는 네 가지다.
① 성능 — 쉬운 일은 앞에서 끝낸다
패치 이미지 하나 줄 때마다 무거운 애플리케이션 코드를 깨울 이유가 없다. Nginx는 이런 파일 전송에 최적화되어 있고, 앞에서 끝내 버리면 WAS는 로그인·전투 기록·인벤토리처럼 진짜 계산이 필요한 일에 집중할 수 있다.
다만 “분리하면 무조건 빨라진다”는 아니다. 실제 효과는 파일 크기, 캐시 설정, 네트워크 상황에 따라 달라지므로 측정해서 판단한다.
② 확장 — 필요한 쪽만 늘린다
대규모 업데이트 날에는 패치 파일 다운로드가 폭증하고, 이벤트 마감 직전에는 랭킹 조회가 몰린다. 역할이 나뉘어 있으면 붐비는 쪽만 골라서 증설할 수 있다. Web Server가 여러 대의 WAS에 요청을 나누는 부하 분산도 여기서 나온다.
③ 보안 — 애플리케이션을 바깥에 직접 내놓지 않는다
외부 사용자는 앞단 Web Server에만 접속하고, WAS와 Database는 내부 네트워크에 숨겨 둘 수 있다. HTTPS 처리, 요청 크기 제한, IP 차단 같은 공통 방어도 앞단에서 한 번에 적용된다.
물론 Web Server를 앞에 뒀다는 사실만으로 안전해지지는 않는다. WAS의 인증·인가, 입력값 검증, DB 권한 분리, 보안 업데이트가 함께 있어야 한다.
④ 책임 분리 — 고칠 곳을 빨리 찾는다
이미지가 안 보이는 문제와 랭킹 숫자가 틀린 문제는 서로 다른 계층에서 확인하면 된다. 애플리케이션을 재배포하는 동안 앞단이 “점검 중입니다” 페이지를 대신 띄우게 만드는 것도 분리되어 있어야 가능하다.
초보자가 자주 하는 오해 네 가지
“Web Server는 DB에 접근할 수 없다” — 절대 규칙은 아니다. 모듈이나 외부 프로그램을 붙이면 가능하다. 다만 관례적으로 DB 접근과 업무 규칙은 애플리케이션 계층에 몰아 두어 책임을 나눈다.
“WAS는 항상 느리다” — 틀렸다. 캐시된 동적 응답은 아주 빠를 수 있고, 반대로 수십 MB짜리 정적 파일은 전송에 한참 걸린다. 속도는 단정하지 말고 측정한다.
“Web Server는 OS 레벨, WAS는 애플리케이션 레벨” — 정확하지 않다. 둘 다 운영체제 위에서 도는 평범한 서버 소프트웨어다. 차이는 실행 위치가 아니라 맡은 책임이다.
“JavaScript가 들어 있으면 그 파일은 동적이다” — 파일에 붙이는 꼬리표가 아니다. 앞의
comments.html에서 봤듯, 그 파일을 받아 오는 요청은 정적이고 그 안의fetch()가 부르는 API 요청이 동적이다. 둘은 한 화면 안에서 같이 일어난다. 기준은 언제나 “이 요청에 서버가 무엇을 했는가”다.
직접 확인해 보기
브라우저 개발자도구(F12)의 Network 탭을 열고 아무 웹사이트나 새로고침해 보자. 요청 목록에서 두 종류가 눈에 들어온다.
.png,.css,.js로 끝나는 요청 — 정적 파일이다. 응답 헤더에Cache-Control이 붙어 있는 경우가 많다. 브라우저에 저장해 두고 다시 안 받아도 된다는 뜻이다./api/가 들어가고 응답이 JSON인 요청 — 동적 처리다. 새로고침할 때마다 값이 달라질 수 있어 보통 캐시하지 않는다.
응답 헤더의 Server 항목을 보면 앞단에 무엇이 있는지 힌트가 나오기도 한다. nginx 나 Apache 가 찍혀 있으면 그 Web Server가 앞에 서 있는 것이다. 보안상 일부러 감추는 곳도 많다.
# 응답 헤더만 확인하는 명령
curl -I https://example.com
# 응답 헤더만 확인하는 명령
curl.exe -I https://example.com
HTTP/2 200
content-type: text/html; charset=UTF-8
server: nginx
cache-control: max-age=604800
현대 웹에서는 경계가 흐려진다
Web Server와 WAS는 서로 배타적인 상자가 아니다. Apache HTTP Server는 모듈을 붙이면 동적 콘텐츠도 직접 처리하고, 반대로 Tomcat의 기본 서블릿은 정적 파일도 제공한다. Nginx 역시 정적 파일 제공과 프록시를 모두 한다.
그러니 제품 이름만 보고 역할을 단정하지 말고 실제 설정을 봐야 한다. 판단 기준은 규모다.
상황 | 권장 구성 |
|---|---|
개인 프로젝트, 소규모 서비스 | WAS 하나가 정적 파일과 API를 모두 처리한다. 단순한 게 낫다 |
트래픽이 늘고 서버가 여러 대 | 앞에 Nginx를 두고 정적 파일은 직접, API는 여러 WAS로 분배 |
정적 사이트 + 일부 동적 기능 | CDN이 정적 파일을 전담하고, 동적 요청만 별도 API 서버로 보낸다 |
참고로 지금 보고 있는 이 사이트가 세 번째 구성이다. 글 본문은 미리 만들어 둔 정적 파일이고, 댓글과 좋아요만 별도 API가 처리한다.
마지막 정리
정적 콘텐츠 — 게임 로고·패치 이미지·CSS·JavaScript처럼 미리 준비된 파일. Web Server가 그대로 전달한다.
동적 콘텐츠 — 내 랭킹·인벤토리처럼 요청한 사람과 현재 데이터에 따라 달라지는 결과. WAS가 코드를 실행해 만든다.
둘은 한 화면에서 같이 일어난다 — HTML 파일을 받는 건 정적, 그 안의
fetch()가 API를 부르는 건 동적. 파일이 아니라 요청 하나하나를 기준으로 본다.Database — 데이터 보관소. 브라우저는 직접 못 붙고 항상 WAS를 거친다.
API 서버 — WAS의 반대말이 아니라, HTML 대신 JSON을 응답하도록 만든 WAS를 부르는 말.
대표 구조 —
브라우저 → Web Server → WAS → Database. 다만 규모에 따라 합치거나 더 나눈다.분리하는 이유 — 성능, 필요한 쪽만 늘리는 확장, 보안 경계, 책임 분리.
HTTP 요청과 응답 자체가 아직 낯설다면 클라이언트·서버와 HTTP·HTTPS 를 먼저 읽으면 이 글이 훨씬 쉬워진다.
참고 자료
Nginx Beginner’s Guide (2026-08-02 확인)
Nginx Reverse Proxy 가이드 (2026-08-02 확인)
Apache HTTP Server Getting Started (2026-08-02 확인)
Apache HTTP Server Reverse Proxy Guide (2026-08-02 확인)
Apache Tomcat 11 Documentation Index (2026-08-02 확인)
Apache Tomcat Default Servlet Reference (2026-08-02 확인)
MDN — 클라이언트-서버 개요 (2026-08-02 확인)
댓글 0
댓글을 불러오는 중…