EC2란? + Nginx 역할 — 내 Spring Boot 앱이 인터넷에 떠 있는 방법

최병현·2026년 6월 17일

spring boot

목록 보기
27/34

1. 개념 소개

로컬에서 Spring Boot 애플리케이션을 잘 만들었어도, 그게 인터넷 어디에선가 다른 사람도 접속할 수 있는 서버 위에서 돌아가야 진짜 서비스가 된다. 이때 등장하는 게 EC2Nginx다.

EC2(Elastic Compute Cloud)는 AWS가 제공하는 가상 서버다. 물리적인 서버를 직접 사 와서 설치할 필요 없이, 필요한 사양의 컴퓨터를 클릭 몇 번으로 빌려서 인터넷에 연결된 서버로 쓸 수 있다.

Nginx는 그 서버 앞에서 들어오는 요청을 받아서 적절한 곳으로 전달해주는 웹 서버다. 정적 파일을 직접 서빙하거나, 우리 Spring Boot 애플리케이션으로 요청을 넘겨주는 리버스 프록시 역할을 한다.

둘을 합치면 이런 그림이 된다.

[인터넷] → [EC2 서버] → [Nginx] → [Spring Boot 애플리케이션 (Tomcat)]

2. 왜 필요한가

EC2가 필요한 이유

서비스를 운영하려면 24시간 켜져 있는 컴퓨터가 필요하다. 직접 서버를 사서 데이터센터에 두는 건(On-Premise) 비용도 크고 관리도 복잡하다.

  • 초기 비용 없이 바로 서버를 빌릴 수 있다 — 클릭 몇 번으로 서버 생성
  • 필요에 따라 사양을 바꿀 수 있다 — 트래픽이 늘면 더 큰 인스턴스로 변경 가능
  • 전기, 냉방, 하드웨어 장애 같은 물리적 관리를 AWS가 대신 해준다
  • 전 세계 여러 리전에 서버를 둘 수 있다 — 사용자와 가까운 곳에 배치 가능

Nginx가 필요한 이유

Spring Boot는 내장 Tomcat이 있어서 그 자체로도 HTTP 요청을 처리할 수 있다. 그런데도 앞에 Nginx를 두는 이유가 있다.

  • 포트 80/443을 직접 노출하지 않아도 됨 — Spring Boot는 보통 8080 같은 내부 포트로 두고, Nginx가 80/443을 받아서 전달
  • HTTPS(SSL) 처리를 Nginx가 전담 — 인증서 설정과 갱신을 애플리케이션 코드와 분리
  • 정적 파일을 더 빠르게 서빙 — 이미지, CSS, JS는 Nginx가 직접 처리하면 Tomcat 부담이 줄어듦
  • 여러 애플리케이션 서버로 로드 밸런싱 — 백엔드 인스턴스가 여러 개일 때 트래픽을 분산
  • 무중단 배포에 활용 — 새 버전을 띄운 후 Nginx 설정만 바꿔서 트래픽을 전환

핵심은 Nginx가 "외부에 노출되는 창구"를 맡고, Spring Boot는 내부에서 비즈니스 로직만 처리하는 역할 분리다.


3. 전체 동작 흐름

사용자가 https://myservice.com/api/users로 접속했을 때의 흐름을 보자.

[사용자 브라우저]
    |
    | https://myservice.com/api/users 요청
    ↓
[DNS]
    |
    | myservice.com → EC2의 고정 IP(Elastic IP)로 변환
    ↓
[AWS 보안그룹]
    |
    | 443(HTTPS), 80(HTTP) 포트만 외부에서 접근 허용
    | 8080(Spring Boot)은 외부에서 직접 접근 불가 (내부 통신만 허용)
    ↓
[EC2 서버]
    |
    ↓
[Nginx] (포트 443에서 대기 중)
    |
    | ① HTTPS 인증서로 SSL 처리 (TLS Handshake)
    | ② 요청 경로 확인 → /api/* 는 백엔드로 전달하도록 설정되어 있음
    | ③ proxy_pass로 내부 포트(8080)에 요청 전달
    ↓
[Spring Boot 애플리케이션 - 내장 Tomcat, 포트 8080]
    |
    | ④ DispatcherServlet → Controller → Service → DB 처리
    ↓
[Nginx]
    |
    | ⑤ 애플리케이션의 응답을 받아서 다시 클라이언트로 전달
    ↓
[사용자 브라우저]
    JSON 응답 수신

사용자는 Nginx와만 직접 통신하고, Spring Boot 애플리케이션은 외부에 직접 노출되지 않는다. Nginx가 일종의 관문 역할을 하는 것이다.


4. 핵심 구성 요소

4-1. EC2 인스턴스

실제로 실행되는 가상 서버 한 대를 의미한다. CPU, 메모리, 스토리지 사양에 따라 인스턴스 타입(t3.micro, t3.medium 등)이 나뉜다. 서비스 초기에는 작은 인스턴스로 시작하고, 트래픽이 늘면 더 큰 사양으로 바꾸는 식으로 운영한다.

4-2. 보안 그룹 (Security Group)

EC2 인스턴스에 대한 방화벽 역할을 한다. 어떤 포트를, 어떤 IP에서 오는 요청만 허용할지 정의한다. 일반적으로 80(HTTP), 443(HTTPS), 22(SSH) 정도만 외부에 열어두고, 애플리케이션 포트(8080)는 외부에서 직접 접근하지 못하게 막는다.

4-3. Elastic IP

EC2 인스턴스를 재시작하면 기본 IP가 바뀔 수 있다. Elastic IP는 고정된 IP 주소로, 도메인을 이 IP에 연결해두면 인스턴스를 재시작해도 도메인 연결이 깨지지 않는다.

4-4. Nginx — 리버스 프록시

클라이언트의 요청을 받아서 내부 서버(애플리케이션)로 전달하고, 응답을 다시 클라이언트에게 돌려주는 역할이다. 일반적인 프록시는 클라이언트를 대신해서 외부로 요청을 보내지만, 리버스 프록시는 서버를 대신해서 클라이언트의 요청을 받아준다는 점에서 방향이 반대다.

4-5. Nginx — 로드 밸런서

백엔드 애플리케이션 서버가 여러 대 있을 때, 들어오는 요청을 여러 서버에 분산시켜주는 역할이다. upstream 블록으로 여러 서버를 등록하고 라운드로빈 등의 방식으로 분배한다.


5. Spring Boot에서 어떻게 연결되는가

EC2 위에서 Spring Boot 애플리케이션과 Nginx가 어떻게 배치되는지 구조를 보자.

[EC2 인스턴스]
    |
    ├── Nginx (시스템 서비스 또는 Docker 컨테이너)
    │     - 포트 80, 443에서 대기
    │     - SSL 인증서 보관
    │     - /etc/nginx/sites-available/myservice.conf 설정 파일
    │
    └── Spring Boot 애플리케이션 (Docker 컨테이너 또는 JAR 직접 실행)
          - 포트 8080에서 대기 (외부 비공개)
          - 내장 Tomcat이 요청 처리

Nginx는 80/443으로 외부 요청을 받고, 내부적으로 8080에 있는 Spring Boot로 전달한다. 이 구조 덕분에 Spring Boot 애플리케이션 코드는 HTTPS나 외부 노출에 대해 전혀 신경 쓸 필요가 없다.

배포 흐름은 보통 이렇게 진행된다.

1. 로컬에서 빌드 (또는 CI에서 빌드) → JAR 또는 Docker 이미지 생성
2. EC2로 전송 (scp, Docker 레지스트리 pull 등)
3. EC2에서 애플리케이션 실행 (포트 8080)
4. Nginx는 이미 설정되어 있어서 별도 재설정 불필요
   (단, 무중단 배포 시에는 Nginx 설정 reload가 필요할 수 있음)

6. 간단한 예제 코드

6-1. Nginx 리버스 프록시 기본 설정

# /etc/nginx/sites-available/myservice.conf

server {
    listen 80;
    server_name myservice.com;

    # HTTP 요청을 HTTPS로 강제 리다이렉트
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    server_name myservice.com;

    # SSL 인증서 설정 (Let's Encrypt 등으로 발급)
    ssl_certificate /etc/letsencrypt/live/myservice.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/myservice.com/privkey.pem;

    location / {
        proxy_pass http://localhost:8080;          # Spring Boot로 전달
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

6-2. 정적 파일은 Nginx가 직접 서빙

server {
    listen 443 ssl;
    server_name myservice.com;

    # 정적 파일은 Nginx가 직접 처리 (Spring Boot로 안 넘김)
    location /static/ {
        alias /var/www/myservice/static/;
        expires 30d;
    }

    # API 요청만 Spring Boot로 전달
    location /api/ {
        proxy_pass http://localhost:8080;
        proxy_set_header Host $host;
    }
}

6-3. 여러 서버로 로드 밸런싱

upstream backend_servers {
    server localhost:8080;
    server localhost:8081;
    server localhost:8082;
}

server {
    listen 443 ssl;
    server_name myservice.com;

    location / {
        proxy_pass http://backend_servers; # 세 서버에 분산
        proxy_set_header Host $host;
    }
}

6-4. EC2에서 Spring Boot 애플리케이션 실행 (systemd로 관리)

# /etc/systemd/system/myapp.service

[Unit]
Description=My Spring Boot App
After=network.target

[Service]
User=ec2-user
ExecStart=/usr/bin/java -jar /home/ec2-user/app.jar
Restart=always
RestartSec=10
Environment="SPRING_PROFILES_ACTIVE=prod"

[Install]
WantedBy=multi-user.target
# 서비스 등록 및 시작
sudo systemctl daemon-reload
sudo systemctl enable myapp
sudo systemctl start myapp

# 로그 확인
sudo journalctl -u myapp -f

6-5. Nginx 설정 변경 후 적용

# 설정 문법 오류 확인
sudo nginx -t

# 무중단으로 설정 다시 로드 (재시작 아님)
sudo systemctl reload nginx

7. 자주 헷갈리는 부분

EC2 인스턴스 = 서버 한 대일 뿐, 자동 확장은 안 된다

EC2는 그 자체로 "가상 서버 한 대"다. 트래픽이 늘어도 자동으로 서버가 늘어나지 않는다. 자동 확장이 필요하면 Auto Scaling Group, 로드 밸런서(ALB) 같은 별도 구성이 필요하다. 초기 단계에서는 EC2 한 대 + Nginx로 시작하는 경우가 많지만, 이건 "확장 가능한 구조"가 아니라 "최소 구성"이라는 점을 알아야 한다.

Nginx의 리버스 프록시 vs 포워드 프록시

  • 포워드 프록시 — 클라이언트 앞에서 동작. 클라이언트를 대신해서 외부 서버에 요청 (예: 사내망에서 외부 인터넷 접속 시 거치는 프록시)
  • 리버스 프록시 — 서버 앞에서 동작. 외부 클라이언트의 요청을 받아서 내부 서버에 전달 (Nginx가 이 역할)

이름이 헷갈리지만 "누구를 대신해서 동작하는가"로 구분하면 명확해진다. Nginx는 서버(우리 애플리케이션)를 대신해서 클라이언트 요청을 받아주는 것이다.

Spring Boot 내장 Tomcat과 Nginx 둘 다 웹 서버인데 왜 같이 쓰나

Tomcat은 Java 애플리케이션 코드를 실행하는 데 특화되어 있고, Nginx는 정적 파일 처리, SSL, 로드 밸런싱, 다중 도메인 처리에 특화되어 있다. 각자 잘하는 역할을 분리해서 맡기는 것이지, 같은 일을 중복으로 하는 게 아니다.

보안그룹에서 8080을 막아도 EC2 내부에서는 접근 가능하다

보안 그룹은 외부에서 들어오는 트래픽을 막는 것이다. 같은 EC2 인스턴스 안에서 localhost:8080으로 접근하는 건 보안 그룹과 무관하게 항상 가능하다. Nginx가 proxy_pass http://localhost:8080으로 내부 접근하는 게 가능한 이유다.


8. 실무에서 중요한 포인트

보안 그룹은 최소한으로 열기

필요한 포트만 정확히 열어야 한다. 일반적으로 80, 443은 모든 IP(0.0.0.0/0)에서 허용하고, 22(SSH)는 본인의 IP나 회사 VPN IP로 제한해야 한다. 8080처럼 애플리케이션이 직접 쓰는 포트는 외부에 열 필요가 없다.

SSL 인증서는 Let's Encrypt + Certbot으로 무료 관리

# Certbot 설치 후 인증서 발급 및 Nginx 자동 설정
sudo certbot --nginx -d myservice.com

# 인증서는 90일마다 만료되므로 자동 갱신 설정 (보통 cron으로 등록됨)
sudo certbot renew --dry-run

무중단 배포 — Nginx로 트래픽 전환하기

새 버전을 다른 포트(예: 8081)로 띄운 뒤, 정상 동작을 확인하고 Nginx의 proxy_pass 대상을 바꿔서 트래픽을 전환하는 방식(Blue-Green 배포의 단순화 버전)을 쓸 수 있다.

# 기존: proxy_pass http://localhost:8080;
# 전환: proxy_pass http://localhost:8081;
# 변경 후 reload (재시작 없이 적용)
sudo systemctl reload nginx

로그와 모니터링

Nginx의 액세스 로그(/var/log/nginx/access.log)와 애플리케이션 로그를 함께 보면 어느 요청이 어디서 느려지는지 추적할 수 있다. 실무에서는 CloudWatch 같은 모니터링 도구와 연계해서 EC2 자원 사용량, 애플리케이션 상태를 함께 관찰한다.

EC2 인스턴스 크기 선택은 실측 기반으로

처음부터 큰 인스턴스를 쓸 필요는 없다. 작게 시작해서 CPU/메모리 사용률을 모니터링하며 필요에 따라 사이즈를 조정하는 것이 비용 효율적이다. AWS는 인스턴스 타입 변경이 비교적 쉬운 편이다.


9. 정리

  • EC2는 AWS가 제공하는 가상 서버로, 우리 애플리케이션이 실행되는 물리적 기반이다
  • Nginx는 외부 요청을 받아서 내부 애플리케이션(Spring Boot)으로 전달하는 리버스 프록시 역할을 한다
  • 보안그룹으로 외부에서 접근 가능한 포트를 최소화하고, 80/443만 노출, 애플리케이션 포트는 내부 전용으로 둔다
  • Nginx는 SSL 처리, 정적 파일 서빙, 로드 밸런싱까지 담당해서 애플리케이션 코드의 책임을 줄여준다
  • 리버스 프록시는 서버를 대신해서 클라이언트 요청을 받는 것 — 포워드 프록시와 방향이 반대다
  • EC2 한 대 + Nginx는 최소 구성이며, 트래픽 증가에 대응하려면 Auto Scaling, 로드 밸런서 같은 추가 구성이 필요하다
  • SSL 인증서는 Let's Encrypt로 무료 관리하고, Nginx reload로 무중단 설정 변경이 가능하다

10. 느낀 점

로컬에서 localhost:8080으로만 보던 애플리케이션을 실제로 도메인을 연결해서 인터넷에 띄워보니까 신경 써야 할 게 생각보다 많았다. 보안그룹 설정 하나 놓쳐서 접속이 안 되거나, SSL 설정이 꼬여서 헤맨 적도 있었다.

가장 크게 느낀 건 Spring Boot 애플리케이션이 직접 인터넷과 마주하지 않는다는 점이다. Nginx가 앞에서 받아주고, 애플리케이션은 그 뒤에서 안전하게 자기 일만 한다. 이 역할 분리를 이해하고 나니 왜 포트를 숨기는지, 왜 SSL을 Nginx에서 처리하는지, 왜 정적 파일을 굳이 Nginx가 따로 처리하는지가 전부 하나의 그림으로 연결됐다.

profile
Develop

0개의 댓글