Search

Spring Boot λ°°ν¬ν•˜κΈ°

Spring Boot μ•± Nginx둜 λ°°ν¬ν•˜κΈ°

κ°œμš”

Spring Boot 앱은 λ‚΄μž₯ ν†°μΊ£μ—μ„œ μ‹€ν–‰λ˜λ©°, NginxλŠ” λ¦¬λ²„μŠ€ ν”„λ‘μ‹œλ‘œμ„œ μ•žλ‹¨μ—μ„œ μš”μ²­μ„ λ°›μ•„ Spring Boot에 μ „λ‹¬ν•œλ‹€.
ReactλŠ” 정적 νŒŒμΌμ„ 직접 μ„œλΉ™ν•˜λ©΄ λμ§€λ§Œ, Spring BootλŠ” λ‹€λ¦…λ‹ˆλ‹€! Spring BootλŠ” 자기 μžμ‹ μ΄ μ›Ή μ„œλ²„(λ‚΄μž₯ ν†°μΊ£)λ₯Ό κ°€μ§€κ³  μžˆμ–΄μ„œ 슀슀둜 HTTP μš”μ²­μ„ μ²˜λ¦¬ν•  수 μžˆμ–΄μš”.
그런데 μ™œ Nginxλ₯Ό μ•žμ— λ‘λƒκ³ μš”? Nginxκ°€ μ•žμ—μ„œ SSL 처리, λ‘œλ“œ λ°ΈλŸ°μ‹±, 정적 파일 캐싱, λ³΄μ•ˆμ„ λ‹΄λ‹Ήν•΄μ£Όλ©΄, Spring BootλŠ” λΉ„μ¦ˆλ‹ˆμŠ€ λ‘œμ§μ—λ§Œ 집쀑할 수 μžˆκ±°λ“ μš”. 마치 μ‹λ‹Ήμ—μ„œ 홀 직원(Nginx)이 μ†λ‹˜ μ‘λŒ€λ₯Ό ν•˜κ³ , μ£Όλ°©μž₯(Spring Boot)은 μš”λ¦¬μ—λ§Œ μ§‘μ€‘ν•˜λŠ” 것과 κ°™μ•„μš”!

배포 μ•„ν‚€ν…μ²˜

graph LR
    A[πŸ‘€ μ‚¬μš©μž<br/>λΈŒλΌμš°μ €] -->|"<https://api.example.com><br/>포트 443"| B[πŸ–₯️ Nginx<br/>λ¦¬λ²„μŠ€ ν”„λ‘μ‹œ]
    B -->|"<http://localhost:8080><br/>λ‚΄λΆ€ 톡신"| C[πŸƒ Spring Boot<br/>λ‚΄μž₯ ν†°μΊ£]
    C --> D[πŸ—„οΈ Database<br/>MySQL/PostgreSQL]

    style B fill:#90EE90
    style C fill:#87CEEB
Mermaid
볡사
flowchart TD
    A["πŸ’» 둜컬 개발"] -->|"./gradlew build<br/>λ˜λŠ” mvn package"| B["πŸ“¦ JAR 파일 생성<br/>app.jar"]
    B -->|"scp λ˜λŠ” CI/CD"| C["πŸ–₯️ μ„œλ²„μ— μ—…λ‘œλ“œ"]
    C --> D["πŸƒ java -jar app.jar<br/>Spring Boot μ‹€ν–‰"]
    D --> E["βš™οΈ Nginx λ¦¬λ²„μŠ€ ν”„λ‘μ‹œ<br/>μ„€μ • + SSL"]
    E --> F["🌐 μ„œλΉ„μŠ€ μ˜€ν”ˆ!"]

    style B fill:#FFD700
    style E fill:#90EE90
Mermaid
볡사

Step 1: Spring Boot μ•± λΉŒλ“œ

# Gradle ν”„λ‘œμ νŠΈ ./gradlew clean build -x test # κ²°κ³Ό: build/libs/myapp-0.0.1-SNAPSHOT.jar # Maven ν”„λ‘œμ νŠΈ mvn clean package -DskipTests # κ²°κ³Ό: target/myapp-0.0.1-SNAPSHOT.jar
Bash
볡사

Step 2: μ„œλ²„μ— μ—…λ‘œλ“œ 및 μ‹€ν–‰

# JAR 파일 μ—…λ‘œλ“œ scp build/libs/myapp-0.0.1-SNAPSHOT.jar ubuntu@μ„œλ²„IP:/home/ubuntu/app/ # μ„œλ²„μ—μ„œ μ‹€ν–‰ cd /home/ubuntu/app java -jar myapp-0.0.1-SNAPSHOT.jar
Bash
볡사

Systemd μ„œλΉ„μŠ€λ‘œ 등둝 (ꢌμž₯!)

ν„°λ―Έλ„μ—μ„œ 직접 java -jar둜 μ‹€ν–‰ν•˜λ©΄, SSH 연결이 λŠμ–΄μ§€λ©΄ 앱도 같이 μ£½μ–΄μš”. κ·Έλž˜μ„œ systemd μ„œλΉ„μŠ€λ‘œ 등둝해야 ν•΄μš”!
sudo nano /etc/systemd/system/myapp.service
Bash
볡사
[Unit] Description=My Spring Boot Application After=network.target [Service] User=ubuntu ExecStart=/usr/bin/java -jar /home/ubuntu/app/myapp-0.0.1-SNAPSHOT.jar SuccessExitStatus=143 Restart=always RestartSec=5 StandardOutput=journal StandardError=journal Environment=SPRING_PROFILES_ACTIVE=prod Environment=SERVER_PORT=8080 [Install] WantedBy=multi-user.target
Plain Text
볡사
# μ„œλΉ„μŠ€ 등둝 및 μ‹œμž‘ sudo systemctl daemon-reload sudo systemctl start myapp sudo systemctl enable myapp # μƒνƒœ 확인 sudo systemctl status myapp # 둜그 확인 sudo journalctl -u myapp -f
Bash
볡사
ν•­λͺ©
μ„€λͺ…
After=network.target
λ„€νŠΈμ›Œν¬κ°€ μ€€λΉ„λœ ν›„ μ‹œμž‘
Restart=always
앱이 죽으면 μžλ™ μž¬μ‹œμž‘
RestartSec=5
μž¬μ‹œμž‘ μ „ 5초 λŒ€κΈ°
Environment
ν™˜κ²½ λ³€μˆ˜ μ„€μ • (ν”„λ‘œνŒŒμΌ, 포트 λ“±)

Step 3: Nginx λ¦¬λ²„μŠ€ ν”„λ‘μ‹œ μ„€μ •

sudo nano /etc/nginx/sites-available/spring-app
Bash
볡사
server { listen 80; server_name api.example.com; # ⭐ λ¦¬λ²„μŠ€ ν”„λ‘μ‹œ β€” Spring Boot둜 전달 location / { proxy_pass <http://localhost:8080>; # 원본 μš”μ²­ 정보 전달 (ν•„μˆ˜!) 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; # νƒ€μž„μ•„μ›ƒ μ„€μ • proxy_connect_timeout 60s; proxy_send_timeout 60s; proxy_read_timeout 60s; # WebSocket 지원 (ν•„μš”μ‹œ) proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; } # 큰 파일 μ—…λ‘œλ“œ ν—ˆμš© client_max_body_size 10M; }
Plain Text
볡사

ν”„λ‘μ‹œ 헀더가 μ™œ ν•„μš”ν•΄?

sequenceDiagram
    participant μ‚¬μš©μž as πŸ‘€ μ‚¬μš©μž<br/>IP: 203.0.113.50
    participant Nginx as πŸ–₯️ Nginx<br/>IP: 10.0.0.1
    participant Spring as πŸƒ Spring Boot<br/>IP: 127.0.0.1

    μ‚¬μš©μž->>Nginx: μš”μ²­ (IP: 203.0.113.50)

    Note over Nginx: 헀더 없이 μ „λ‹¬ν•˜λ©΄<br/>Spring은 Nginx IP만 λ³΄μž„

    Nginx->>Spring: μš”μ²­ 전달
    Note over Nginx,Spring: X-Real-IP: 203.0.113.50<br/>X-Forwarded-For: 203.0.113.50<br/>X-Forwarded-Proto: https

    Note over Spring: 이제 μ§„μ§œ μ‚¬μš©μž IPλ₯Ό<br/>μ•Œ 수 μžˆμ–΄μš”!
Mermaid
볡사
헀더
μ—­ν• 
Host
μ›λž˜ μš”μ²­λœ 도메인 이름
X-Real-IP
μ‹€μ œ ν΄λΌμ΄μ–ΈνŠΈ IP
X-Forwarded-For
ν”„λ‘μ‹œ 체인을 거친 IP λͺ©λ‘
X-Forwarded-Proto
μ›λž˜ ν”„λ‘œν† μ½œ (http/https)
이 헀더가 μ—†μœΌλ©΄ Spring BootλŠ” λͺ¨λ“  μš”μ²­μ΄ 127.0.0.1(Nginx)μ—μ„œ 온 κ²ƒμœΌλ‘œ μΈμ‹ν•΄μš”. μ‚¬μš©μžμ˜ μ§„μ§œ IPλ₯Ό μ•Œ 수 μ—†κ²Œ λ˜λŠ” κ±°μ£ !

Step 4: ν™œμ„±ν™” 및 SSL 적용

# μ‚¬μ΄νŠΈ ν™œμ„±ν™” sudo ln -s /etc/nginx/sites-available/spring-app /etc/nginx/sites-enabled/ # ν…ŒμŠ€νŠΈ 및 적용 sudo nginx -t && sudo systemctl reload nginx # SSL 적용 sudo certbot --nginx -d api.example.com
Bash
볡사

μ΅œμ’… μ„€μ • (HTTPS 포함)

# μ—…μŠ€νŠΈλ¦Ό μ •μ˜ (λ‘œλ“œ λ°ΈλŸ°μ‹± λŒ€λΉ„) upstream spring_backend { server localhost:8080; } server { listen 443 ssl http2; server_name api.example.com; ssl_certificate /etc/letsencrypt/live/api.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/api.example.com/privkey.pem; # λ¦¬λ²„μŠ€ ν”„λ‘μ‹œ location / { proxy_pass http://spring_backend; 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; } # Health Check μ—”λ“œν¬μΈνŠΈ (λ‘œκΉ… μ œμ™Έ) location = /actuator/health { proxy_pass http://spring_backend; access_log off; } client_max_body_size 10M; } server { listen 80; server_name api.example.com; return 301 https://$host$request_uri; }
Plain Text
볡사

Spring Boot application.yml μ„€μ •

server: port: 8080 forward-headers-strategy: framework # ⭐ ν”„λ‘μ‹œ 헀더 인식 (ν•„μˆ˜!) # Actuator (Health Check) management: endpoints: web: exposure: include: health
YAML
볡사
forward-headers-strategy: framework 섀정을 κΌ­ ν•΄μ£Όμ„Έμš”! 이걸 μ•ˆ ν•˜λ©΄ Spring Bootκ°€ Nginxκ°€ λ³΄λ‚΄μ£ΌλŠ” X-Forwarded-* 헀더λ₯Ό λ¬΄μ‹œν•΄μš”.

자주 λ°œμƒν•˜λŠ” 문제

문제
원인
ν•΄κ²°
502 Bad Gateway
Spring Bootκ°€ μ•ˆ λŒμ•„κ°€κ³  있음
systemctl status myapp으둜 확인
504 Gateway Timeout
응닡 μ‹œκ°„ 초과
proxy_read_timeout 늘리기
CORS μ—λŸ¬
ν”„λ‘ νŠΈ-λ°± 도메인 닀름
Spring Boot에 CORS μ„€μ • or Nginxμ—μ„œ 처리
λ¦¬λ‹€μ΄λ ‰νŠΈ λ¬΄ν•œ 루프
X-Forwarded-Proto λ―Έμ„€μ •
ν”„λ‘μ‹œ 헀더 μ„€μ • 확인
파일 μ—…λ‘œλ“œ μ‹€νŒ¨
크기 μ œν•œ
client_max_body_size 늘리기

μ‹€μŠ΅

μ‹€μŠ΅ ν™˜κ²½

ν•­λͺ©
κ°’
둜컬 ν”„λ‘œμ νŠΈ
Spring Boot + Gradle
λΉŒλ“œ κ²°κ³Όλ¬Ό
build/libs/APP.war
μ„œλ²„
alohaserver4.cafe24.com
WAR 배포 경둜
/var/www/krules/backend/
μ œμ–΄ 슀크립트
start.sh / stop.sh / restart.sh
SSH 접속
Host alias alohaserver4 μ‚¬μš©
Spring BootλŠ” 파일 λ³΅μ‚¬λ§ŒμœΌλ‘œ λλ‚˜μ§€ μ•Šμ•„μš”. APP.war μ—…λ‘œλ“œ β†’ restart.sh 원격 μ‹€ν–‰ μˆœμ„œλ‘œ 배포해야 ν•΄μš”!

배포 파일 ꡬ쑰

πŸ“Β ν”„λ‘œμ νŠΈ 루트/ β”œβ”€β”€ πŸ“„Β deploy.bat ← λΉŒλ“œ + μ—…λ‘œλ“œ + μž¬μ‹œμž‘ μžλ™ν™” β”œβ”€β”€ πŸ“„Β start.bat ← μ„œλ²„ μ•± 원격 μ‹œμž‘ β”œβ”€β”€ πŸ“„Β stop.bat ← μ„œλ²„ μ•± 원격 μ’…λ£Œ └── πŸ“„Β restart.bat ← μ„œλ²„ μ•± 원격 μž¬μ‹œμž‘ πŸ“Β μ„œλ²„ /var/www/krules/backend/ β”œβ”€β”€ πŸ’»Β APP.war ← 배포된 μ• ν”Œλ¦¬μΌ€μ΄μ…˜ β”œβ”€β”€ πŸ“œΒ start.sh ← APP.war λ°±κ·ΈλΌμš΄λ“œ μ‹€ν–‰ (둜그 μ €μž₯) β”œβ”€β”€ πŸ“œΒ stop.sh ← APP.war ν”„λ‘œμ„ΈμŠ€ μ’…λ£Œ β”œβ”€β”€ πŸ“œΒ restart.sh ← stop.sh β†’ start.sh μˆœμ„œλ‘œ μž¬μ‹œμž‘ πŸ“Β μ„œλ²„ /var/www/krules/log/ └── πŸ“°Β appwar_20260422_120000.log ← μ‹€ν–‰ 둜그 (start.sh μžλ™ 생성)
Plain Text
볡사

사전 μ€€λΉ„: μ„œλ²„ 디렉토리 생성 (졜초 1회)

# μ„œλ²„ 접속 ssh alohaserver4 # 배포 경둜 생성 mkdir -p /var/www/krules/backend mkdir -p /var/www/krules/log
Bash
볡사

start.sh

#!/bin/bash # ν˜„μž¬ μŠ€ν¬λ¦½νŠΈκ°€ μœ„μΉ˜ν•œ 경둜둜 이동 cd "$(dirname "$0")" # 둜그 디렉토리 생성 LOG_DIR="../log" mkdir -p "$LOG_DIR" LOG_FILE="$LOG_DIR/appwar_$(date +%Y%m%d_%H%M%S).log" # JAVA_HOME이 μ„€μ •λ˜μ–΄ μžˆμ§€ μ•ŠμœΌλ©΄ java λͺ…λ Ήμ–΄ μ‚¬μš© JAVA_CMD=${JAVA_HOME:-}/bin/java if [ ! -x "$JAVA_CMD" ]; then JAVA_CMD=java fi # JVM λ©”λͺ¨λ¦¬ μ œν•œ (μ΅œμ†Œ 128MB, μ΅œλŒ€ 256MB) JAVA_OPTS="-Xms128m -Xmx256m" # Spring ν”„λ‘œνŒŒμΌ λͺ…μ‹œ (server = μ‹€μ„œλ²„ μ„€μ •, local = 둜컬 개발) SPRING_OPTS="--spring.profiles.active=server" # APP.war νŒŒμΌμ„ λ°±κ·ΈλΌμš΄λ“œμ—μ„œ μ‹€ν–‰ν•˜κ³  둜그 μ €μž₯ $JAVA_CMD $JAVA_OPTS -jar APP.war $SPRING_OPTS > "$LOG_FILE" 2>&1 &
JavaScript
볡사

stop.sh

#!/bin/bash # APP.war ν”„λ‘œμ„ΈμŠ€ μ’…λ£Œ 슀크립트 # ν˜„μž¬ 경둜의 APP.war ν”„λ‘œμ„ΈμŠ€λ§Œ μ’…λ£Œ cd "$(dirname "$0")" APP_PATH="$(pwd)/APP.war" PIDS=$(ps -ef | grep '[j]ava.*-jar' | grep "$APP_PATH" | awk '{print $2}') if [ -z "$PIDS" ]; then echo "μ‹€ν–‰ 쀑인 APP.war ν”„λ‘œμ„ΈμŠ€κ°€ μ—†μŠ΅λ‹ˆλ‹€." exit 0 fi for PID in $PIDS; do echo "APP.war ν”„λ‘œμ„ΈμŠ€ μ’…λ£Œ: $PID" kill $PID sleep 1 if ps -p $PID > /dev/null; then echo "κ°•μ œ μ’…λ£Œ: $PID" kill -9 $PID fi echo "μ’…λ£Œ μ™„λ£Œ: $PID" sleep 1 done echo "λͺ¨λ“  APP.war ν”„λ‘œμ„ΈμŠ€κ°€ μ’…λ£Œλ˜μ—ˆμŠ΅λ‹ˆλ‹€."
JavaScript
볡사

restart.sh (ν”„λ‘œμ νŠΈ λ£¨νŠΈμ— μ €μž₯)

start.sh와 stop.shλ₯Ό μˆœμ„œλŒ€λ‘œ ν˜ΈμΆœν•˜λŠ” μž¬μ‹œμž‘ μŠ€ν¬λ¦½νŠΈμ˜ˆμš”.
#!/bin/bash # APP.war μž¬μ‹œμž‘ 슀크립트 (stop.sh β†’ start.sh μˆœμ„œλ‘œ μ‹€ν–‰) cd "$(dirname "$0")" echo "=== APP.war μž¬μ‹œμž‘ μ‹œμž‘ ===" # κΈ°μ‘΄ ν”„λ‘œμ„ΈμŠ€ μ’…λ£Œ bash stop.sh # ν”„λ‘œμ„ΈμŠ€ μ™„μ „ μ’…λ£Œ λŒ€κΈ° sleep 2 # μƒˆ ν”„λ‘œμ„ΈμŠ€ μ‹œμž‘ bash start.sh echo "=== APP.war μž¬μ‹œμž‘ μ™„λ£Œ ==="
Bash
볡사

1단계: μˆ˜λ™ 배포

flowchart TB
    A["πŸ’» 둜컬<br>gradlew clean bootWar"] -->|"APP.war 생성"| B["πŸ“¦ APP.war"]
    B -->|"scp μ—…λ‘œλ“œ<br>WAR + sh 파일"| C["πŸ–₯️ μ„œλ²„<br>/var/www/krules/backend/"]
    C -->|"plink restart.sh"| D["πŸƒ Spring Boot μž¬μ‹œμž‘"]
    D --> E["🌐 λΈŒλΌμš°μ € 확인"]
Mermaid
볡사
# 1. λ‘œμ»¬μ—μ„œ WAR λΉŒλ“œ ./gradlew clean bootWar # λΉŒλ“œ κ²°κ³Ό 확인 ls build/libs/ # APP.war 파일 생성됨 # 2. μ„œλ²„μ— APP.war + sh 파일 μ—…λ‘œλ“œ scp build/libs/APP.war alohaserver4:/var/www/krules/backend/APP.war scp start.sh stop.sh restart.sh alohaserver4:/var/www/krules/backend/ # 3. sh 파일 μ‹€ν–‰ κΆŒν•œ λΆ€μ—¬ (졜초 1회) ssh alohaserver4 "chmod +x /var/www/krules/backend/*.sh" # 4. restart.sh 원격 μ‹€ν–‰ ssh alohaserver4 "cd /var/www/krules/backend && bash restart.sh" # 5. 둜그 확인 ssh alohaserver4 "tail -f /var/www/krules/log/appwar_*.log"
Bash
볡사

2단계: ν”„λ‘ νŠΈμ—”λ“œ .env ν™˜κ²½λ³€μˆ˜ μ„€μ •

λ°±μ—”λ“œ API μ£Όμ†Œλ₯Ό ν™˜κ²½μ— 따라 λΆ„λ¦¬ν•΄μ„œ κ΄€λ¦¬ν•΄μš”. Vite ν”„λ‘œμ νŠΈλŠ” VITE_ 접두사, CRAλŠ” REACT_APP_ 접두사λ₯Ό λΆ™μ—¬μ•Ό μΈμ‹ν•΄μš”.
# .env.local (둜컬 개발용 β€” .gitignore에 μΆ”κ°€ ꢌμž₯) VITE_API_URL=http://localhost:8080/api # VITE_API_URL=http://192.168.30.19:8080/api # 같은 λ„€νŠΈμ›Œν¬ λ‹€λ₯Έ PC ν…ŒμŠ€νŠΈ # .env.production (배포용) # 방식 A (μ„œλΈŒλ„λ©”μΈ): api.κ΅­λ£°.com β†’ Spring Boot # VITE_API_URL=http://xn--3e0b91t.com/api # HTTP 적용 μ „ # VITE_API_URL=https://api.xn--3e0b91t.com/api # HTTPS 적용 ν›„ # 방식 B (λ¦¬λ²„μŠ€ ν”„λ‘μ‹œ): κ΅­λ£°.com/api β†’ Spring Boot # VITE_API_URL=http://xn--3e0b91t.com/api # HTTP 적용 μ „ # VITE_API_URL=https://xn--3e0b91t.com/api # HTTPS 적용 ν›„
Bash
볡사
React μ½”λ“œμ—μ„œ μ΄λ ‡κ²Œ μ‚¬μš©ν•΄μš”:
// ν™˜κ²½λ³€μˆ˜λ‘œ API μ£Όμ†Œλ₯Ό μ½μ–΄μ˜΄ const res = await fetch(`${import.meta.env.VITE_API_URL}/products`);
JavaScript
볡사

3단계: Nginx λ¦¬λ²„μŠ€ ν”„λ‘μ‹œ μ„€μ • β€” 방식 선택

두 κ°€μ§€ 방식이 μžˆμ–΄μš”. 상황에 맞게 ν•˜λ‚˜λ₯Ό μ„ νƒν•˜μ„Έμš”!
graph TD
    A["🌐 μ‚¬μš©μž μš”μ²­"] --> B{방식 선택}
    B -->|"방식 A<br>μ„œλΈŒλ„λ©”μΈ"| C["api.κ΅­λ£°.com<br>β†’ Spring Boot :8080"]
    B -->|"방식 B<br>λ¦¬λ²„μŠ€ ν”„λ‘μ‹œ"| D["κ΅­λ£°.com/api/**<br>β†’ Spring Boot :8080"]
    C --> E["πŸƒ Spring Boot"]
    D --> E
Mermaid
볡사
비ꡐ
방식 A β€” μ„œλΈŒλ„λ©”μΈ
방식 B β€” 경둜 기반 λ¦¬λ²„μŠ€ ν”„λ‘μ‹œ
API URL
api.κ΅­λ£°.com/api/...
κ΅­λ£°.com/api/...
DNS μ„€μ •
api μ„œλΈŒλ„λ©”μΈ Aλ ˆμ½”λ“œ μΆ”κ°€ ν•„μš”
μΆ”κ°€ DNS λΆˆν•„μš”
SSL μΈμ¦μ„œ
api μ„œλΈŒλ„λ©”μΈ μΈμ¦μ„œ 별도 λ°œκΈ‰
κΈ°μ‘΄ 도메인 μΈμ¦μ„œ 곡유
Nginx 파일 수
2개 (ν”„λ‘ νŠΈ + λ°±μ—”λ“œ 각각)
1개 (ν•˜λ‚˜μ˜ server 블둝에 톡합)
μΆ”μ²œ 상황
API μ„œλ²„λ₯Ό λ…λ¦½μ μœΌλ‘œ 관리할 λ•Œ
도메인 ν•˜λ‚˜λ‘œ ν’€μŠ€νƒμ„ λ‹¨μˆœν•˜κ²Œ ꡬ성할 λ•Œ

방식 A β€” μ„œλΈŒλ„λ©”μΈ (api.κ΅­λ£°.com)

ssh alohaserver4 nano /etc/nginx/conf.d/krules-backend.conf
Bash
볡사
# λ°±μ—”λ“œ μ „μš© μ„œλ²„ 블둝 (api μ„œλΈŒλ„λ©”μΈ) server { listen 80; server_name api.xn--3e0b91t.com; location / { proxy_pass <http://localhost:8080>; 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; proxy_connect_timeout 60s; proxy_read_timeout 60s; } client_max_body_size 10M; }
Plain Text
볡사
ν”„λ‘ νŠΈμ—”λ“œ .env.production:
VITE_API_URL=https://api.xn--3e0b91t.com/api
Bash
볡사

방식 B β€” 경둜 기반 λ¦¬λ²„μŠ€ ν”„λ‘μ‹œ (κ΅­λ£°.com/api)

ν”„λ‘ νŠΈμ—”λ“œμ™€ λ°±μ—”λ“œλ₯Ό ν•˜λ‚˜μ˜ Nginx μ„œλ²„ λΈ”λ‘μ—μ„œ μ²˜λ¦¬ν•΄μš”. 도메인 ν•˜λ‚˜λ‘œ ν’€μŠ€νƒ ꡬ성할 λ•Œ νŽΈν•΄μš”!
ssh alohaserver4 nano /etc/nginx/conf.d/krules.conf
Bash
볡사
server { listen 80; server_name xn--3e0b91t.com www.xn--3e0b91t.com; # ── ν”„λ‘ νŠΈμ—”λ“œ (React Vite 정적 파일) ────────────────── root /var/www/krules/frontend; index index.html; location / { try_files $uri $uri/ /index.html; } location /assets/ { expires 1y; add_header Cache-Control "public, immutable"; } # ── λ°±μ—”λ“œ (Spring Boot λ¦¬λ²„μŠ€ ν”„λ‘μ‹œ) ───────────────── # /api/ 둜 μ‹œμž‘ν•˜λŠ” μš”μ²­μ€ Spring Boot둜 전달 location /api/ { proxy_pass <http://localhost:8080>; # ← /api/ 경둜 κ·ΈλŒ€λ‘œ 전달 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; proxy_connect_timeout 60s; proxy_read_timeout 60s; client_max_body_size 10M; } gzip on; gzip_types text/plain text/css application/json application/javascript text/xml; }
Plain Text
볡사
proxy_pass <http://localhost:8080>; β€” 끝에 / λ₯Ό 뢙이지 μ•ŠμœΌλ©΄ /api/ κ²½λ‘œκ°€ κ·ΈλŒ€λ‘œ Spring Boot에 μ „λ‹¬λΌμš”. Spring Boot의 RequestMapping도 /api/... 둜 λ§žμΆ°μ•Ό ν•΄μš”!
ν”„λ‘ νŠΈμ—”λ“œ .env.production:
VITE_API_URL=https://xn--3e0b91t.com/api
Bash
볡사
nginx -t && systemctl reload nginx
Bash
볡사

3단계: κ°œλ³„ μ œμ–΄ bat 파일 (μ„œλ²„ κ΄€λ¦¬μš©)

PuTTY의 plink.exe(원격 λͺ…λ Ή), pscp.exe(파일 볡사)λ₯Ό μ‚¬μš©ν•΄μš”. μ„€μΉ˜: https://www.putty.org/ β†’ λ‹€μš΄λ‘œλ“œ ν›„ PATH에 μΆ”κ°€ν•˜κ±°λ‚˜ bat 파일과 같은 폴더에 λ‘μ„Έμš”.

start.bat

@echo off chcp 65001 > nul set REMOTE_USER=root set REMOTE_HOST=alohaserver4.cafe24.com set REMOTE_PATH=/var/www/krules/backend :: πŸ” λΉ„λ°€λ²ˆν˜Έ μž…λ ₯ (μ•ˆ 보이게) for /f "delims=" %%p in ('powershell -Command "$p = Read-Host ''SSH Password'' -AsSecureString; $BSTR=[System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($p); [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)"') do set REMOTE_PASS=%%p echo [START] Spring Boot μ•± μ‹œμž‘ 쀑... plink -pw %REMOTE_PASS% %REMOTE_USER%@%REMOTE_HOST% "cd %REMOTE_PATH% && bash start.sh" echo μ™„λ£Œ! pause
Shell
볡사

stop.bat

@echo off chcp 65001 > nul set REMOTE_USER=root set REMOTE_HOST=alohaserver4.cafe24.com set REMOTE_PATH=/var/www/krules/backend :: πŸ” λΉ„λ°€λ²ˆν˜Έ μž…λ ₯ (μ•ˆ 보이게) for /f "delims=" %%p in ('powershell -Command "$p = Read-Host ''SSH Password'' -AsSecureString; $BSTR=[System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($p); [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)"') do set REMOTE_PASS=%%p echo [STOP] Spring Boot μ•± μ’…λ£Œ 쀑... plink -pw %REMOTE_PASS% %REMOTE_USER%@%REMOTE_HOST% "cd %REMOTE_PATH% && bash stop.sh" echo μ™„λ£Œ! pause
Shell
볡사

restart.bat

@echo off chcp 65001 > nul set REMOTE_USER=root set REMOTE_HOST=alohaserver4.cafe24.com set REMOTE_PATH=/var/www/krules/backend :: πŸ” λΉ„λ°€λ²ˆν˜Έ μž…λ ₯ (μ•ˆ 보이게) for /f "delims=" %%p in ('powershell -Command "$p = Read-Host ''SSH Password'' -AsSecureString; $BSTR=[System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($p); [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)"') do set REMOTE_PASS=%%p echo [RESTART] Spring Boot μ•± μž¬μ‹œμž‘ 쀑... plink -pw %REMOTE_PASS% %REMOTE_USER%@%REMOTE_HOST% "cd %REMOTE_PATH% && bash restart.sh" echo μ™„λ£Œ! pause
Shell
볡사

4단계: 톡합 배포 μžλ™ν™” (deploy.bat)

clean bootWar β†’ APP.war + sh 파일 μ—…λ‘œλ“œ β†’ restart.sh 원격 μ‹€ν–‰κΉŒμ§€ ν•œ λ²ˆμ—!

deploy.bat

@echo off chcp 65001 > nul echo. echo ============================================= echo Spring Boot bootWar Build ^& Deploy echo ============================================= echo. :: ──────────────────────────────────────────── :: μ„€μ •κ°’ :: ──────────────────────────────────────────── set PROJECT_DIR=%~dp0 set REMOTE_USER=root set REMOTE_HOST=alohaserver4.cafe24.com set REMOTE_PATH=/var/www/krules/backend :: πŸ” λΉ„λ°€λ²ˆν˜Έ μž…λ ₯ (μ•ˆ 보이게) for /f "delims=" %%p in ('powershell -Command "$p = Read-Host ''SSH Password'' -AsSecureString; $BSTR=[System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($p); [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($BSTR)"') do set REMOTE_PASS=%%p echo. :: ──────────────────────────────────────────── :: [1] bootWar λΉŒλ“œ :: ──────────────────────────────────────────── echo [1/3] bootWar λΉŒλ“œ 쀑... cd /d %PROJECT_DIR% call gradlew.bat clean bootWar if errorlevel 1 ( echo. echo [였λ₯˜] λΉŒλ“œ μ‹€νŒ¨! μ—λŸ¬λ₯Ό ν™•μΈν•˜μ„Έμš”. pause exit /b 1 ) echo λΉŒλ“œ μ™„λ£Œ - APP.war 생성됨 echo. :: ──────────────────────────────────────────── :: [2] 파일 μ—…λ‘œλ“œ :: ──────────────────────────────────────────── echo [2/3] 파일 μ—…λ‘œλ“œ 쀑... pscp -pw %REMOTE_PASS% build\libs\APP.war %REMOTE_USER%@%REMOTE_HOST%:%REMOTE_PATH%/APP.war if errorlevel 1 ( echo. echo [였λ₯˜] WAR μ—…λ‘œλ“œ μ‹€νŒ¨! pause exit /b 1 ) pscp -pw %REMOTE_PASS% start.sh stop.sh restart.sh %REMOTE_USER%@%REMOTE_HOST%:%REMOTE_PATH%/ echo μ—…λ‘œλ“œ μ™„λ£Œ echo. :: ──────────────────────────────────────────── :: [3] μ„œλ²„ μž¬μ‹œμž‘ :: ──────────────────────────────────────────── echo [3/3] μ„œλ²„ μž¬μ‹œμž‘ 쀑... plink -pw %REMOTE_PASS% %REMOTE_USER%@%REMOTE_HOST% "chmod +x %REMOTE_PATH%/*.sh && cd %REMOTE_PATH% && bash restart.sh" if errorlevel 1 ( echo. echo [였λ₯˜] μž¬μ‹œμž‘ μ‹€νŒ¨! pause exit /b 1 ) :: ──────────────────────────────────────────── :: [4] μ™„λ£Œ :: ──────────────────────────────────────────── echo. echo ============================================= echo 배포 μ™„λ£Œ! echo 둜그 확인: echo ssh %REMOTE_USER%@%REMOTE_HOST% echo tail -f /var/www/krules/log/appwar_*.log echo ============================================= echo. pause
Shell
볡사
μ‹€ν–‰ 방법: deploy.bat 더블클릭 β†’ bootWar λΉŒλ“œ β†’ WAR + sh μ—…λ‘œλ“œ β†’ restart.sh μ‹€ν–‰κΉŒμ§€ μžλ™!

(선택) SSH ν‚€ 인증으둜 λΉ„λ°€λ²ˆν˜Έ 없이 배포

# 1. SSH ν‚€ 생성 (이미 있으면 μƒλž΅) ssh-keygen -t ed25519 -C "deploy-key" # 2. κ³΅κ°œν‚€λ₯Ό μ„œλ²„μ— 등둝 ssh-copy-id alohaserver4
Bash
볡사
ν‚€ 등둝 ν›„ 각 bat νŒŒμΌμ—μ„œ pscp -pw / plink -pw λŒ€μ‹  ν‘œμ€€ λͺ…λ ΉμœΌλ‘œ ꡐ체:
:: WAR μ—…λ‘œλ“œ (pscp β†’ scp) scp build\\libs\\APP.war alohaserver4:%REMOTE_PATH%/APP.war scp start.sh stop.sh restart.sh alohaserver4:%REMOTE_PATH%/ :: 원격 μ‹€ν–‰ (plink β†’ ssh) ssh alohaserver4 "chmod +x %REMOTE_PATH%/*.sh && cd %REMOTE_PATH% && bash restart.sh"
Shell
볡사

체크리슀트

~/.ssh/config 에 alohaserver4 Host μ„€μ • μ™„λ£Œ?
μ„œλ²„μ— /var/www/krules/backend/ 및 /var/www/krules/log/ 디렉토리 생성?
ν”„λ‘œμ νŠΈ λ£¨νŠΈμ— start.sh / stop.sh / restart.sh 파일 쑴재?
./gradlew clean bootWar β†’ build/libs/APP.war 생성 확인?
scp APP.war + *.sh μ—…λ‘œλ“œ 성곡?
μ„œλ²„μ—μ„œ chmod +x /var/www/krules/backend/*.sh μ™„λ£Œ?
ssh alohaserver4 "cd /var/www/krules/backend && bash restart.sh" μ‹€ν–‰ 확인?
tail -f /var/www/krules/log/appwar_*.log 둜 정상 기동 둜그 확인?
Nginx conf.d/krules-backend.conf λ¦¬λ²„μŠ€ ν”„λ‘μ‹œ μ„€μ •?
nginx -t 톡과 및 systemctl reload nginx μ™„λ£Œ?
start.bat / stop.bat / restart.bat κ°œλ³„ λ™μž‘ 확인?
deploy.bat 톡합 배포 ν…ŒμŠ€νŠΈ μ™„λ£Œ?

핡심 정리

Spring Boot = λ‚΄μž₯ ν†°μΊ£μœΌλ‘œ 자체 μ‹€ν–‰ β†’ NginxλŠ” λ¦¬λ²„μŠ€ ν”„λ‘μ‹œ μ—­ν• 
systemd μ„œλΉ„μŠ€ λ“±λ‘μœΌλ‘œ μ•± μžλ™ μ‹œμž‘/μž¬μ‹œμž‘ 보μž₯
ν”„λ‘μ‹œ 헀더(X-Real-IP, X-Forwarded-For λ“±) μ„€μ • ν•„μˆ˜
Spring Boot forward-headers-strategy: framework μ„€μ • ν•„μˆ˜
Certbot으둜 HTTPS 적용 + HTTPβ†’HTTPS λ¦¬λ‹€μ΄λ ‰νŠΈ