
이미지는 ubuntu 22 버전을 선택해준다.

SSH 키는 다운 받아준다.
private 키는 절대 잃어버리거나 노출되면 안 된다.
내 인스턴스 - 보안 - 보안 목록 생성 - 수신 규칙 허용에 들어간다.

0.0.0.0/0은 외부 모든 IP 허용이다.
소스 포트 범위는 비워두면 된다.
Spring Boot API 서버 - 8080
웹 서비스용 Nginx -80 HTTP 기본 포트
HTTPS (인증서 적용) -443 HTTPS 기본 포트
프론트 Vue 개발 서버 - 5173, 3000, 8081 등 -개발 중 로컬 포트 (배포용 아님)
22 - 내IP주소/32 형태로 입력
위에 포트 번호를 다 열어준다.

이 후 네트워킹 - 서브넷 - 보안으로 가서 설정해둔 보안 목록을 추가한 뒤에
VNIC 에서 - IP 관리 - 보조 IP 주소 지정에서 예약된 퍼블릭 IP를 등록한다.

예약된 퍼블릭 IPv4 주소로 들어가서 설정해준다.
ssh -i {경로/Pprivate 키파일} ubuntu@{public IP주소}
ssh -i /Users/taeheon/Documents/school/capstone/ssh-key-2025-05-16.key ubuntu@131.186.25.199
sudo apt update
sudo apt upgrade
sudo ln -sf /usr/share/zoneinfo/Asia/Seoul /etc/localtime
sudo dd if=/dev/zero of=/swapfile bs=1M count=2048
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
sudo vi /etc/fstab
/swapfile swap swap defaults 0 0
위의 내용을 입력하고 esc :wq!로 저장 후 빠져나온다.
free -m

ubuntu 방화벽에서 HTTP 80 포트 열기
sudo iptables -I INPUT 5 -i ens3 -p tcp --dport 80 -m state --state NEW,ESTABLISHED -j ACCEPT
443 포트 열기
sudo iptables -I INPUT 5 -i ens3 -p tcp --dport 443 -m state --state NEW,ESTABLISHED -j ACCEPT
8080 포트 열기
sudo iptables -I INPUT 5 -i ens3 -p tcp --dport 8080 -m state --state NEW,ESTABLISHED -j ACCEPT
sudo iptables -I INPUT 5 -i ens3 -p tcp --dport 8088 -m state --state NEW,ESTABLISHED -j ACCEPT
sudo iptables -I INPUT 5 -i ens3 -p tcp --dport 8081 -m state --state NEW,ESTABLISHED -j ACCEPT
sudo apt install mysql-server
sudo mysql -u root
USE mysql;
UPDATE user SET plugin='mysql_native_password' WHERE User='root';
FLUSH PRIVILEGES;
exit;
mysqladmin -u root password
위 명령어 입력 후 비밀번호를 입력해준다.

1. 이름 입력
2. Standard TCP/IP over SSH 항목을 선택
3. 오라클 인스턴스 공개키 입력
4. 버튼을 클릭하여 서버의 private key 파일을 선택한다. (다운로드 받은 키 파일)
5. 127.0.0.1
6. 3306
7. root
8. 버튼을 클릭하고 mysql 관리자 비밀번호를 입력한다.
sudo vim /etc/mysql/mysql.conf.d/mysqld.cnf 파일에서 bind-address 수정한다.

재실행
sudo systemctl restart mysql
# MySQL에 접속
sudo mysql -u root -p
CREATE DATABASE freemarket CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
# 애플리케이션용 사용자 생성 (아직 없는 경우)
CREATE USER 'freemarket_user'@'localhost' IDENTIFIED BY '원하는_비밀번호';
# 사용자에게 권한 부여
GRANT ALL PRIVILEGES ON freemarket.* TO 'freemarket_user'@'localhost';
# 권한 적용
FLUSH PRIVILEGES;
# MySQL 종료
EXIT;
비밀번호는 ' 안에 들어가야 된다.
https://www.duckdns.org/에 접속해서 등록한다.

ssh -i 키파일 ubuntu@domain_name
ssh -i /Users/taeheon/Documents/school/capstone/ssh-key-2025-05-16.key ubuntu@freemarket.duckdns.org

sudo apt install nginx
sudo vi /etc/nginx/sites-available/tomcat.conf
# 80 포트는 HTTPS로 리디렉션
server {
listen 80;
server_name freemarket.duckdns.org;
return 301 https://$host$request_uri;
}
# 443 포트는 실제 서비스
server {
listen 443 ssl;
server_name freemarket.duckdns.org;
# SSL 설정 (certbot 관리)
ssl_certificate /etc/letsencrypt/live/freemarket.duckdns.org/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/freemarket.duckdns.org/privkey.pem;
include /etc/letsencrypt/options-ssl-nginx.conf;
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
# 추가 보안 헤더
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
add_header X-Content-Type-Options nosniff;
add_header X-Frame-Options SAMEORIGIN;
add_header X-XSS-Protection "1; mode=block";
# WebSocket 프록시 설정 (채팅 기능)
location ^~ /ws/chat/ {
proxy_pass http://127.0.0.1:8080/ws/chat/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
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_set_header Authorization $http_authorization;
proxy_cache_bypass $http_upgrade;
proxy_read_timeout 86400;
}
# API 프록시
location /api/ {
proxy_pass http://127.0.0.1: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_set_header Authorization $http_authorization;
}
# OAuth2 관련 경로도 백엔드로 프록시
location /oauth2/ {
proxy_pass http://127.0.0.1:8080/oauth2/;
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_set_header Authorization $http_authorization;
}
location /login/oauth2/ {
proxy_pass http://127.0.0.1:8080/login/oauth2/;
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_set_header Authorization $http_authorization;
}
# Swagger UI 관련 경로 설정
location ~ ^/swagger-ui(.*)$ {
proxy_pass http://127.0.0.1:8080/swagger-ui$1;
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_buffer_size 128k;
proxy_buffers 4 256k;
proxy_busy_buffers_size 256k;
}
# API 문서 경로 설정
location ~ ^/v3/api-docs(.*)$ {
proxy_pass http://127.0.0.1:8080/v3/api-docs$1;
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_buffer_size 128k;
proxy_buffers 4 256k;
proxy_busy_buffers_size 256k;
add_header Content-Type 'application/json';
}
# 정적 프론트엔드 파일
location / {
root /home/ubuntu/freemarket-frontend/dist;
index index.html;
try_files $uri $uri/ /index.html;
}
# 백엔드에서 생성한 이미지 경로
location /images/ {
alias /home/ubuntu/images/;
expires 30d;
}
location /thumbnails/ {
alias /home/ubuntu/thumbnails/;
expires 30d;
}
}
freemarket.duckdns.org로 작성한다.
sudo nginx -t로 테스트 후
sudo systemctl restart nginx로 재시작한다.
sudo ln -s /etc/nginx/sites-available/tomcat.conf /etc/nginx/sites-enabled/
sudo service nginx restart
sudo apt install certbot python3-certbot-nginx
https://freemarket.duckdns.org 설정
sudo certbot --nginx -d freemarket.duckdns.org
이후 이메일 입력 후 다 Y를 입력한다.
https://freemarket.duckdns.org/ 로 접속


IntelliJ에 Remote Host를 클릭해서 사용한다.

... 을 누른 뒤 원하는 이름 입력 후 SFTP를 눌러준다.

... 누른 뒤 + 누른다.

Host는 우리가 할당받은 고정 IP한다.
Username에는 ubuntu를 입력(소문자로만), Authentication Type은 Key pair을 골라준다. private Key file은 인스턴스 생성시에 받아놓은 private 키 파일을 설정해주면 된다.
Test Connection 버튼 눌러서 Success가 뜨면 SSH연결에 성공한 것이다.


이제 터미널로 조작이 가능하다.
sudo apt install openjdk-21-jdk
java -version
sudo ufw allow 22 # SSH
sudo ufw allow 80 # HTTP
sudo ufw allow 443 # HTTPS
sudo ufw allow 8080
sudo ufw enable
sudo systemctl stop tomcat9
cd FreeMarket
./gradlew clean build
./gradlew clean build -x test
테스트 단계 없이 빌드
scp -i {key 경로} build/libs/FreeMarket-0.0.1-SNAPSHOT.jar ubuntu@freemarket.duckdns.org:/home/ubuntu/
scp -i /Users/taeheon/Documents/school/capstone/ssh-key-2025-05-16.key build/libs/FreeMarket-0.0.1-SNAPSHOT.jar ubuntu@freemarket.duckdns.org:/home/ubuntu/
기존 jar 파일 제거
rm -f /home/ubuntu/FreeMarket-0.0.1-SNAPSHOT.jar
8080 포트 확인
sudo lsof -nPi :8080
프로세스 종료
sudo kill PID
mkdir -p /home/ubuntu/images
mkdir -p /home/ubuntu/thumbnails
chmod 755 /home/ubuntu/images
chmod 755 /home/ubuntu/thumbnails
ls -la /home/ubuntu/ | grep images
ls -la /home/ubuntu/ | grep thumbnails
# 권한 변경 - 방법 1: 현재 사용자를 소유자로 설정
sudo chown -R ubuntu:ubuntu /home/ubuntu/images
sudo chown -R ubuntu:ubuntu /home/ubuntu/thumbnails
# 권한 변경 - 방법 2: 모든 사용자에게 읽기/쓰기/실행 권한 부여 (덜 안전한 방법)
sudo chmod -R 777 /home/ubuntu/images
sudo chmod -R 777 /home/ubuntu/thumbnails
# 백그라운드로 실행
nohup java -jar /home/ubuntu/FreeMarket-0.0.1-SNAPSHOT.jar --spring.profiles.active=prod > /home/ubuntu/app.log 2>&1 &
# PID 확인 (선택사항)
echo $! > /home/ubuntu/app.pid
cat /home/ubuntu/app.pid
# 빌드
npm run build
# 서버의 기존 dist 폴더 내용 삭제
ssh -i /Users/taeheon/Documents/school/capstone/ssh-key-2025-05-16.key ubuntu@freemarket.duckdns.org "rm -rf /home/ubuntu/freemarket-frontend/dist/*"
# 새 빌드 파일 업로드
scp -i /Users/taeheon/Documents/school/capstone/ssh-key-2025-05-16.key -r dist/* ubuntu@freemarket.duckdns.org:/home/ubuntu/freemarket-frontend/dist/
# 애플리케이션 실행 확인
ps aux | grep java
# 로그 확인
tail -f /home/ubuntu/app.log
# 파일 확인
ls -la /home/ubuntu/freemarket-frontend/dist/
ls -la /home/ubuntu/images/
ls -la /home/ubuntu/thumbnails/



# baseUrl을 서버가 인식하도록 설정
server:
forward-headers-strategy: native
tomcat:
remote-ip-header: X-Forwarded-For
protocol-header: X-Forwarded-Proto
server.forward-headers-strategy: native
Spring Boot가 Nginx, Apache, ELB 같은 리버스 프록시가 전달하는 헤더(X-Forwarded-*) 를 어떻게 처리할지를 지정한다.
server.tomcat.remote-ip-header: X-Forwarded-For
X-Forwarded-For는 Nginx 같은 프록시 서버가 클라이언트의 실제 IP를 전달할 때 사용하는 표준 헤더다.request.getRemoteAddr() 등이 프록시의 IP가 아니라 진짜 사용자 IP를 반환하게 된다.server.tomcat.protocol-header: X-Forwarded-Proto
Nginx 등)는 실제 클라이언트가 http로 접속했는지 https로 접속했는지를 이 헤더로 전달한다.HttpServletRequest.isSecure()가 정확하게 true로 나다.@Configuration
public class WebConfig implements WebMvcConfigurer {
@Value("${file.upload-dir}") private String uploadPath;
@Value("${file.thumbnail-dir}") private String thumbnailPath;
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**") // /api/** 경로에 대해 CORS 적용
.allowedOrigins("http://localhost:8081", "http://127.0.0.1:8081", "https://freemarket.duckdns.org") // 허용할 출처 (Vue 개발 서버 주소)
.allowedMethods("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS") // 허용할 HTTP 메서드
.allowedHeaders("*") // 모든 헤더 허용
.allowCredentials(true) // 인증 정보(쿠키, JWT 등) 허용
.maxAge(3600); // pre-flight 요청 캐시 시간 (초)
// 스웨거 UI 경로에 대한 CORS 설정 추가
registry.addMapping("/swagger-ui/**")
.allowedOrigins("*") // 또는 특정 도메인
.allowedMethods("GET", "POST", "OPTIONS")
.allowedHeaders("*")
.maxAge(3600);
// 필요시 다른 경로 추가 (예: /oauth2/**)
registry.addMapping("/oauth2/**")
.allowedOrigins("http://localhost:8081", "http://127.0.0.1:8081", "https://freemarket.duckdns.org")
.allowedMethods("GET", "POST", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true)
.maxAge(3600);
registry.addMapping("/login/oauth2/code/**")
.allowedOrigins("http://localhost:8081", "http://127.0.0.1:8081", "https://freemarket.duckdns.org")
.allowedMethods("GET", "POST", "OPTIONS")
.allowedHeaders("*")
.allowCredentials(true)
.maxAge(3600);
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/images/**")
.addResourceLocations("file:" + uploadPath + "/"); // 경로 끝 '/' 중요
registry.addResourceHandler("/thumbnails/**")
.addResourceLocations("file:" + thumbnailPath + "/"); // 경로 끝 '/' 중요
}
}
cors 설정에서 https://freemarket.duckdns.org을 추가해준다.
.cors(Customizer.withDefaults()) // WebConfig의 CORS 설정을 사용
# application.yml 또는 application-prod.yml
frontend:
oauth:
# 소셜 로그인 성공 후 토큰을 전달하며 리다이렉션될 Vue 페이지 경로
callback-url: https://freemarket.duckdns.org/oauth/callback
reset-password-url: https://freemarket.duckdns.org/oauth/callback
위의 주소는 프론트 배포 후에 적용한다.