본문 바로가기

홈 서버

홈서버 구축기 #4 - Nginx 포워딩 & 웹 페이지 서빙

반응형

개요

이번 시간에는 홈 서버의 호스트에 Nginx를 설치하여, 최초 가비아에서 설정한 서브도메인에 따라 다른 서비스를 응답하도록 할 예정입니다. SSL 설정까지 해보도록 할게요.

 

구현 아키텍처

아래의 2가지 상황에대해서 설명해보도록 할게요.

  1. 프론트엔드(Next.js), 백엔드(Nest.js)
  2. 단순 웹페이지 서빙

 

예를들어 test.kr 도메인을 보유 중이라고 가정하고, blog.test.kr 요청 시, 블로그 서비스랑 연결되도록 해보겠습니다.

DNS 설정

최초 도메인 구매 업체에서, blog.test.kr이라는 도메인에 대한 DDNS 주소로의 CNAME을 설정합니다. (아래 글 참고)

https://hej-tech.tistory.com/10?category=1274391

 

홈서버 구축기 #2 - SSH 원격 접속 설정 (내부/외부)

지난 포스팅에서 홈서버 하드웨어를 구성하고 Ubuntu를 설치했습니다. 이제 원격으로 서버에 접속할 수 있도록 SSH 설정을 해보겠습니다. 내부 네트워크에서 SSH 접속Ubuntu 설치 시 마지막에 OpenSSH

hej-tech.tistory.com

 

CNAME 설정 예시

프론트엔드, 백엔드가 나뉘어져 있는 경우 CNAME 설정

  • (프론트) blog.test.kr, www.blog.test.kr
  • (백엔드) api.blog.test.kr , www.api.blog.test.kr

일반 웹페이지 서빙 시 CNAME 설정 (예시)

  • blog.test.kr, www.blog.test.kr

 

가상머신에서 소스코드 올리기

서버에서 웹 서비스를 하려면, 서버에 프로젝트가 위치해야 합니다. 이번에는 편리하게 진행 하기 위해 블로그 소스코드가 Git에 올라가 있다고 가정하고 진행할게요.

 

ssh 연결로 가상머신 내부에 접근한 후, git clone으로 프로젝트를 가지고 와서 프로젝트 파일을 준비한 후, 백그라운드로 프로젝트를 실행해줍니다. (정적 파일 서빙의 경우 프로젝트 실행은 안해도 됩니다.)

 

간단하게 아래처럼 구조 예시를 들겠습니다.

 

프론트엔드, 백엔드 구조

  • /home/blog/front
    • sudo pnpm run build (빌드)
    • nohup sudo pnpm run start > app.log 2>&1 &  (백그라운드에서 프로젝트 실행 명령어 (프레임워크 별로 명령어 차이 있을 수 있음))
  • /home/blog/backend
    • sudo pnpm run build
    • nohup sudo pnpm run start > app.log 2>&1 &

 

단순 정적 웹페이지 서빙 (파일만 위치 시키면 됨)

  • /home/blog/index.html

 

Nginx 설치

호스트(가상머신이 아닌 물리 서버)에서, 패키지 업데이트 및 Nginx 설치를 진행합니다.

# 패키지 목록 업데이트
sudo apt update
sudo apt install nginx -y

# Nginx 서비스 시작
sudo systemctl start nginx

# 부팅 시 자동 시작 설정
sudo systemctl enable nginx

# 상태 확인
sudo systemctl status nginx

 

가상머신 IP 확인

먼저 생성한 가상머신의 IP를 확인해야 합니다.

# 가상머신 IP 확인
virsh domifaddr ubuntu2404

# 또는 가상머신 내부에서 확인
virsh console ubuntu2404
# 가상머신 내부에서: ip addr show

 

Nginx 설정 변경

Nginx 가상 호스트 설정 파일을 생성합니다.

# blog 서브도메인용 설정 파일 생성
sudo vi /etc/nginx/sites-available/blog.test.kr

# 백엔드가 있는 경우
sudo vi /etc/nginx/sites-available/api.blog.test.kr

 

아래처럼 설정해줍니다.

server {
   listen 80;
   listen [::]:80;
   server_name blog.test.kr www.blog.test.kr;
   
   # Let's Encrypt 인증을 위한 경로
   location /.well-known/acme-challenge/ {
       root /var/www/html;
   }

   # 로깅 설정
   access_log /var/log/nginx/blog.test.kr.access.log;
   error_log /var/log/nginx/blog.test.kr.error.log;

   # 가상머신으로 프록시
   location / {
                proxy_pass http://192.168.122.81:3000; # Next.js
                # proxy_pass http://192.168.122.81:80; # 정적 페이지 서빙인 경우
                proxy_http_version 1.1;
                proxy_set_header Upgrade $http_upgrade;
                proxy_set_header Connection 'upgrade';
                proxy_set_header Host $host;
                proxy_cache_bypass $http_upgrade;
                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;
   }
}


백엔드가 있는 경우 아래와 같이 해주면 됩니다. (예 api.blog.test.kr)

server {
   listen 80;
   listen [::]:80;
   server_name api.blog.test.kr www.api.blog.test.kr;

   # Let's Encrypt 인증을 위한 경로
   location /.well-known/acme-challenge/ {
       root /var/www/html;
   }

   # 로깅 설정
   access_log /var/log/nginx/api.blog.test.kr.access.log;
   error_log /var/log/nginx/api.blog.test.kr.error.log;

   # 가상머신으로 프록시
   location / {
                proxy_pass http://192.168.122.81:8080; # Nest.js
                proxy_http_version 1.1;
                proxy_set_header Upgrade $http_upgrade;
                proxy_set_header Connection 'upgrade';
                proxy_set_header Host $host;
                proxy_cache_bypass $http_upgrade;
                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;
   }
}


해당 사이트 설정을 활성화 해줍니다.

# 심볼릭 링크 생성 (사이트 활성화)
sudo ln -s /etc/nginx/sites-available/blog.test.kr /etc/nginx/sites-enabled/

# 백엔드가 있는 경우
sudo ln -s /etc/nginx/sites-available/api.blog.test.kr /etc/nginx/sites-enabled/

# 기본 사이트 비활성화 (선택사항)
# sudo rm /etc/nginx/sites-enabled/default


설정 파일 문법 검사 및 재시작

# Nginx 설정 문법 검사
sudo nginx -t

# 설정이 올바르면 리로드
sudo systemctl reload nginx

 

포트포워딩 설정

공유기가 있다면 통신사별 공유기 포트 포워딩 페이지에서, 80, 443 포트를 내부의 서버 IP에 연결해줍니다.

 

여기까지 진행했다면, 가상머신에서 동작중인 Next.js가 동작하여 웹페이지가 서빙될거예요.

http://blog.test.kr, http://api.blog.test.kr 등에 연결이 될겁니다.

 

가상머신에 웹 서빙용 Nginx 설치 및 설정

하지만 단순 html 페이지 서빙의 경우에는 가상머신에 Nginx를 한 단계 더 구현해야 해요.


가상머신의 콘솔로 접속한다. (ssh 가능)

# 가상머신 콘솔 접속
virsh console ubuntu2404


가상머신에 Nginx 설치

# 패키지 업데이트
sudo apt update

# Nginx 설치
sudo apt install nginx -y

# Nginx 서비스 시작 및 활성화
sudo systemctl start nginx
sudo systemctl enable nginx

# 상태 확인
sudo systemctl status nginx


마찬가지로 Nginx 설정 파일을 생성 및 수정합니다.

sudo vi /etc/nginx/sites-available/blog.test.kr

   
다음 내용으로 수정합니다.

server {
   listen 80;
   server_name blog.test.kr;
   
   root /home/blog;
   index index.html;
   
   location / {
       try_files $uri $uri/ /index.html;
   }
}

 

nginx 활성화

# 가상머신 내부에서 실행
sudo ln -s /etc/nginx/sites-available/blog.test.kr /etc/nginx/sites-enabled/
sudo rm /etc/nginx/sites-enabled/default  # 기본 사이트 비활성화

 

nginx 테스트 및 재시작

# 설정 테스트
sudo nginx -t

# nginx 재시작
sudo systemctl reload nginx

 

위 과정까지 진행하면 정적 페이지도 정상적으로 응답됩니다.

 

SSL 인증서 발급

다시 호스트(물리서버)로 돌아와서  인증서 발급 전, 인증서 발급을 위한 Certbot을 설치합니다.

# Certbot 설치 (SSL 인증서 발급 전에 추가)
sudo apt update
sudo apt install certbot python3-certbot-nginx -y

 

SSL 인증서 발급을 진행합니다.

# Letsencrypt SSL 인증서 발급
sudo certbot certonly \
  --nginx \
  --server https://acme-v02.api.letsencrypt.org/directory \
  --email test@test.kr \
  --agree-tos \
  -d blog.test.kr \
  -d www.blog.test.kr
  
  
  # 백엔드
sudo certbot certonly \
  --nginx \
  --server https://acme-v02.api.letsencrypt.org/directory \
  --email test@test.kr \
  --agree-tos \
  -d api.blog.test.kr \
  -d www.api.blog.test.kr

 

위 명령어로 인증서 발급후, 아래처럼 443연결로 수정하면 됩니다.

# HTTP를 HTTPS로 리다이렉트 (보안상 필수)
server {
    listen 80;
    listen [::]:80;
    server_name blog.test.kr www.blog.test.kr;
    
    # Let's Encrypt 인증을 위한 경로 (certbot 사용시 필요)
    location /.well-known/acme-challenge/ {
        root /var/www/html;
    }
    
    # 모든 HTTP 요청을 HTTPS로 리다이렉트
    location / {
        return 301 https://$server_name$request_uri;
    }
}

# HTTPS 서버 블록 (메인 설정)
server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name blog.test.kr www.blog.test.kr;
    
    # SSL 인증서 경로 (Let's Encrypt)
    ssl_certificate /etc/letsencrypt/live/blog.test.kr/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/blog.test.kr/privkey.pem;
    
    # SSL 보안 설정 (강화된 보안)
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers off;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
    
    # SSL 세션 캐싱 (성능 향상)
    ssl_session_timeout 1d;
    ssl_session_cache shared:MozSSL:10m;
    ssl_session_tickets off;
    
    # 보안 헤더
    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Referrer-Policy "no-referrer-when-downgrade" always;
    
    # 로깅 설정
    access_log /var/log/nginx/blog.test.kr.access.log;
    error_log /var/log/nginx/blog.test.kr.error.log;
    
    # 파일 업로드 크기 제한 (필요시 조정)
    client_max_body_size 10M;
    
    # gzip 압축 설정
    gzip on;
    gzip_vary on;
    gzip_min_length 1024;
    gzip_types text/plain text/css text/xml application/json application/javascript application/xml+rss application/rss+xml application/atom+xml image/svg+xml text/javascript application/vnd.ms-fontobject application/x-font-ttf font/opentype;
    
    # 가상머신 Next.js로 프록시
    location / {
        proxy_pass http://192.168.122.81:3000;
        # proxy_pass http://192.168.122.81:80; # 정적 페이지 서빙의 경우
        proxy_http_version 1.1;
        
        # WebSocket 지원
        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 https;  # HTTPS임을 명시
        proxy_set_header X-Forwarded-Host $host;
        proxy_set_header X-Forwarded-Port 443;
        
        # 캐시 설정
        proxy_cache_bypass $http_upgrade;
        
        # 타임아웃 설정
        proxy_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
        
        # 버퍼 설정 (대용량 응답 처리)
        proxy_buffer_size 128k;
        proxy_buffers 4 256k;
        proxy_busy_buffers_size 256k;
    }
    
    # Next.js 정적 파일 최적화
    location /_next/static {
        proxy_pass http://192.168.122.81:3000;
        proxy_cache_valid 60m;
        add_header Cache-Control "public, max-age=31536000, immutable";
    }
    
    # 이미지 등 정적 자원 캐싱
    location ~* \.(jpg|jpeg|png|gif|ico|css|js|svg|woff|woff2|ttf|eot)$ {
        proxy_pass http://192.168.122.81:3000;
        expires 30d;
        add_header Cache-Control "public, immutable";
    }
}

 

백엔드의 경우

# HTTP를 HTTPS로 리다이렉트 (보안상 필수)
server {
    listen 80;
    listen [::]:80;
    server_name api.blog.test.kr www.api.blog.test.kr;
    
    # Let's Encrypt 인증을 위한 경로 (certbot 사용시 필요)
    location /.well-known/acme-challenge/ {
        root /var/www/html;
    }
    
    # 모든 HTTP 요청을 HTTPS로 리다이렉트
    location / {
        return 301 https://$server_name$request_uri;
    }
}

# HTTPS 서버 블록 (메인 설정)
server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name api.blog.test.kr www.api.blog.test.kr;
    
    # SSL 인증서 경로 (Let's Encrypt)
    ssl_certificate /etc/letsencrypt/live/api.blog.test.kr/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/api.blog.test.kr/privkey.pem;
    
    # SSL 보안 설정 (강화된 보안)
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers off;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384;
    
    # SSL 세션 캐싱 (성능 향상)
    ssl_session_timeout 1d;
    ssl_session_cache shared:MozSSL:10m;
    ssl_session_tickets off;
    
    # 보안 헤더
    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Referrer-Policy "no-referrer-when-downgrade" always;
    
    # 로깅 설정
    access_log /var/log/nginx/api.blog.test.kr.access.log;
    error_log /var/log/nginx/api.blog.test.kr.error.log;
    
    # 파일 업로드 크기 제한 (필요시 조정)
    client_max_body_size 10M;
    
    # gzip 압축 설정
    gzip on;
    gzip_vary on;
    gzip_min_length 1024;
    gzip_types text/plain text/css text/xml application/json application/javascript application/xml+rss application/rss+xml application/atom+xml image/svg+xml text/javascript application/vnd.ms-fontobject application/x-font-ttf font/opentype;
    
    # 가상머신 Nest.js로 프록시
    location / {
        proxy_pass http://192.168.122.81:8080;
        proxy_http_version 1.1;
        
        # WebSocket 지원
        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 https;  # HTTPS임을 명시
        proxy_set_header X-Forwarded-Host $host;
        proxy_set_header X-Forwarded-Port 443;
        
        # 캐시 설정
        proxy_cache_bypass $http_upgrade;
        
        # 타임아웃 설정
        proxy_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
        
        # 버퍼 설정 (대용량 응답 처리)
        proxy_buffer_size 128k;
        proxy_buffers 4 256k;
        proxy_busy_buffers_size 256k;
    }
    
}

 

nginx 문법 검사 후, reload 해 줍니다.

# Nginx 설정 문법 검사
sudo nginx -t

# 설정이 올바르면 리로드
sudo systemctl reload nginx

 

테스트

마지막으로 외부에서 http://blog.test.kr, http://api.blog.test.kr로 접속하여 https로 리다이렉트가 되면서 프로젝트가 잘 열리는지 확인합니다.

반응형