[Winection(2)] 인프라 구축(로컬 서버, nginx, SSL인증서 발급, 프론트 백 https)

Woo·2025년 3월 15일

Winection Project

목록 보기
2/6
post-thumbnail

이 글을 쓰고 있는 지금도 아직 프로젝트가 완성이 안되어서 내가 구성한 서버 인프라에 대해서 먼저 소개하려고 한다.


이번 글은
1. 내가 가지고 있는 컴퓨터를 사용해 이걸 로컬 서버 PC로 만든다.
2. 만든 PC의 IP에 도메인을 발급받는다.
3. nginx를 사용하여 80번, 443번 포트로 접근하면 이걸 프론트가 실행중인 내부 3000번 포트로 리버스 프록시 설정한다.
4. SSL 인증서 발급을 받은 후 해당 도메인을 https로 접근 할 수 있도록 바꿔준다.
에 대해서 다뤄본다.


해당 프로젝트는 금전적으로 지원이 나와서 진행하였는데 클라우드 관련해선 금전적으로 지원이 나오지 않게 되었다. 그래서 안쓰는 본체를 사용해서 이걸 로컬 환경에서 구축하고, 평소에 클라우드 쓰듯이 도전을 해봤는데 이로 인해 클라우드를 왜 사용하는지를 알게되었다.

로컬 환경에서는 라우터 설정 뿐만 아니라 이 환경을 학교에서 시도해봐서 교내 방화벽 허용 요청을 승인 받아야 서버로 가동될 수 있었다.

Server : 로컬 머신에서 운영(클라우드 대신 개인 PC에서 직접 운영)
OS : Ubuntu Server 24.04.1 LTS
웹서버 : Nginx
SSL 인증서 : Let's Encrypt(Certbot 사용)

📚 개인PC 서버 만들기


이왕 시작하는거 제대로 시작하고 싶어서 OS를 우분투 서버로 설치하여 GUI 없이 도전하게 되었다.
이 PC는 랜선이 공유기에 연결되어 있는데 이를 제대로 활용하기 위해 공유기 고정 IP 설정 및 포트포워딩을 해야한다.

공유기 설정

내가 사용하는 공유기는 netis 모델으로 되어있다.
해당 공유기 설정 페이지(netis 기준 http://192.168.1.1/)으로 이동하여 설정을 진행한다.

고정 IP 설정

네트워크 설정 -> 인터넷 설정으로 가면 IP 방식이 정해져 있다.
서버를 열었는데 IP가 바뀌지 않게 하기 위해 고정 IP 방식을 선택했다.
여기서 나오는 IP는 외부IP로 내가 계속 사용할 IP 주소이다.
이는 라우터 IP이며, PC에 할당되어 있는 내부 IP(사설 IP)도 다르기에 이도 고정으로 바꿔야한다.

네트워크 설정 -> 고정 IP 주소 등록으로 가면 컴퓨터 내부의 IP를 고정으로 설정할 수 있다.

규칙 이름은 자기가 원하는 이름으로 설정해두면 된다. 나는 server로 입력하였다.

MAC 주소는 서버 컴퓨터에서 ifconfig | grep ether 명령어를 입력하여 나오는 MAC 주소를 입력하면 된다.

IP 주소는 서버 컴퓨터에서 ip a 명령어를 입력하여 나오는 IP를 입력한다.

포트포워딩

고정 IP 설정을 했다고 공유기 설정이 끝난 것이 아니다. 포트포워딩도 같이 진행해줘야한다.

규칙 이름은 편하게 자기가 이 포트를 사용 용도에 대해 적어준다.

내부 IP 주소(서버 PC)는 위의 고정 IP 설정에서 고정시킨 사설 IP 주소를 적어준다.

프로토콜은 원하는 프로토콜을 적어주면 되는데 대부분의 통신 프로토콜은 TCP이다. 만약에 자신의 통신 프로토콜을 모른다면 ALL로 적어줘도 된다. 나는 나중에 컴퓨터 방화벽 설정으로 컨트롤 하기 쉽게 하기 위해 그냥 ALL로 사용했다.

포트번호(외부)는 외부에서 접근하는 포트, 포트번호(내부)는 내부로 통하게 되는 포트이다.
편하게 기억하기 위해 외부와 내부를 통일시켜 적었다.
예를 들어 8000번 포트를 사용하는 경우 외부 8000~8000, 내부 8000으로 하였다.

이때 누락되는 포트는 실제로 서버로 사용을 못하게 되니 모두 다 적어둬야한다.

내가 사용한 포트는
nginx 포트(80), https 포트(443), 프론트엔드 포트(3000), 백엔드 포트(8000), cicd 포트(8080), 웹소켓 포트(9090), ssh 포트(원격에서 쉽게 서버에 접근하기 위함, 10000번대 이후 임의 포트 설정) 이렇게 사용했다.

이러면 라우터 설정은 마치게 된다.

서버PC 설정

방화벽 해제

이제 서버PC에서 방화벽을 해제해야한다.
sudo ufw allow 80/tcp와 같이 사용하는 포트를 다 allow 해줘야한다.
sudo ufw status 명령어를 통해 어떤 방화벽이 활성화 되어있는지 확인할 수 있다.

포트 LISTEN 상태 확인

서버에서 프로젝트를 실행시키면 포트가 사용되고 있는지 확인할 수 있다.
sudo netstat -tulnp 명령어를 통해 포트의 실행 여부를 파악할 수 있고, LISTEN으로 뜨면 제대로 실행중인 포트이다. 인프라 초기 구축 단계에서는 확인할 필요는 없지만 추후 서버에서 프로젝트를 실행했는지 볼 수 있다.

이 과정까지 왔으면 제대로 외부에서 IP:포트를 입력했을 때 제대로 접근이 가능하다.
다만 나는 이 과정에서 접근이 불가능해져서 이유를 찾아봤었다.

그 이유는 내가 교내 인터넷을 사용중인데 학교 NAT에서는 외부에서 해당 ip로 접근하는 것을 막아버린다. 즉, 하나의 방화벽이 더 있었던 것이다. 이를 위해 교내 사이트에서 방화벽 신청 요청을 했고, 필요한 포트가 있을 때 마다 추가로 신청하고 있다.

이는 학교마다 다르니깐 교내에서 서버를 작동시키는 사람은 전산팀에 물어봐야한다.


내 프로젝트는 수어 번역 화상채팅 웹인데 수어를 번역하기 위해서는 프론트엔드를 실행시켰을 때 https로 구현이 되어야한다. 크롬에서는 카메라 및 마이크 사용하기 위해서는 localhost가 아닌 이상 https로 되어야지 제대로 실행이 된다.

물론, 테스트를 위해 url을 미리 설정해놔 허용시킬 수 있다.
https로 만들기 위해서는 ssl 인증서가 필요한데 이는 과정이 좀 복잡하니 임시로 내 컴퓨터에서는 허용을 시켰었다. 지금은 https로 바꾸어서 이 방법이 필요없긴한데 아래의 과정대로 하면 해당 기기에서는 자유롭게 사용이 가능하다.

chrome://flags/#unsafely-treat-insecure-origin-as-secure를 크롬 주소창에 입력한다.
들어가면 영어로 되어있는 이상한 설정들이 있는데 Insecure origins treated as secure를 사용 가능으로 바꾸고 텍스트 입력란에 내가 허용시켜야하는 외부ip:프론트엔드 포트를 입력하고 다시 시작 버튼을 누르면 임시로 카메라 및 마이크를 사용할 수 있다.

📚 ssh 접근


이 과정을 마치면 서버 PC에서 직접 터미널을 사용하는 것이 아닌 다른 PC에서도 ssh로 접근해 사용할 수 있다. ssh 접근 관련해서는 다른 사이트에 더 좋은 글들이 많으니 이런 설정을 했다는 것만 알고 넘어가보겠다.

지금 맥을 사용하는 나로서는 ssh를 통해 서버 PC에 접근하는게 편했었다. 그리고 vscode에서도 ssh 접근을 통해 vi보다 더 편한 텍스트 편집기로 작용했다.

📚 도메인 발급


도메인 발급 시에 이 프로젝트는 소규모로 시작하는 토이 프로젝트이기에 무료 도메인을 찾게 되었다. 여러 무료 도메인을 알게되었는데 이 과정에서 내도메인.한국이 내가 원하는 형식의 도메인을 가지고 있었다. 그렇기에 winection.kro.kr 이라는 도메인을 받아냈다.

일반 도메인 검색에서 winection을 검색해줬다. 검색하면 사용 가능한 도메인이 나오는데 이중 우리는 kro.kr을 사용했다. 원하는 도메인 선택 후 등록하기 버튼을 누르면 로그인 페이지가 나온다. 로그인을 마치면 도메인 설정 페이지로 이동할 수 있다.

고급설정(DNS)를 보면 IP연결(A)가 있는데 여기서 winection.kro.kr, www.winection.kro.kr의 ip 주소를 입력한다. 이때 ip 주소는 192로 시작하는 사설 ip 주소가 아닌 처음 공유기 설정 때 고정 IP로 만들었던 IP 주소(외부 IP)를 입력한다. 보안 코드를 입력하고 완료버튼을 누르면 도메인 설정이 끝난다.

주소창에 이 도메인을 입력하면 80번 포트로 이동하게 된다. (아무 포트번호를 입력 안했을 경우 자동으로 80번 포트로 가게된다)

📚 Nginx 설정


보통 리액트로 만들게 되는 프론트는 80번 포트를 사용하는게 아니라 3000번 포트로 사용한다. 그럼 우리는 이걸 포트포워딩을 통해 3000번 포트로 보내야하나? 아니다. nginx의 리버스 프록시를 설정해 80번 포트로 접근하면 3000번 포트로 보내버리는 것이다.

원래 처음에는 나도 포트포워딩으로 보낼 생각을 하고 있었는데 이를 해보니깐 SSL 인증서 발급에서 문제가 생기게 된다. 그래서 리버스 프록시를 설정하니 인증서 발급에서 성공하게 되었다.

다시 서버 PC로 가보자

sudo apt update
sudo apt install nginx -y

을 통해 nginx를 설정했다.
이제 내 도메인을 주소창에 입력하면 nginx 관련 페이지가 뜨는 것을 볼 수 있다.

우선 기본 설정들을 지워버리기 위해 아래 명령어를 사용한다.

sudo rm /etc/nginx/sites-available/default
sudo rm /etc/nginx/sites-enabled/default

여기서 협업자가 만들어 놓은 프론트엔드(리액트) 서버를 도커 컨테이너에서 3000번 포트로 실행시키고 해당 과정을 진행했다.
이제 내 도메인에 접근하면 3000번 포트로 보내버릴 것이다.

sudo vi /etc/nginx/nginx.conf에서 원래 내용을 지워버리고 아래와 같이 구성했다.

server {
    listen 80;
    server_name winection.kro.kr www.winection.kro.kr;

    location /.well-known/acme-challenge/ {
        root /var/www/html/winection;
        default_type "text/plain";
        allow all;
        try_files $uri / =404;
    }

    location / {
        proxy_pass http://localhost:3000;
    }
}

이렇게 입력하고

sudo nginx -t
sudo systemctl reload nginx

을 통해 nginx를 다시 불러온다.

이제는 도메인(80번 포트)을 입력하면 우리의 프론트엔드(3000번 포트)로 접근이 가능해졌다.
해당 코드는 아래 https 설정을 위해 추가로 설정한 것도 있다.

📚 https 설정


SSL 인증서 발급

https 설정을 위해 ssl인증서를 발급받아야한다.
그 중 무료 ssl 인증서 발급으로는 Let's Encrypt가 제일 유명하다. 우리도 이걸 사용해 인증서를 발급받아 볼 것이다. 이는 무료 인증서로 90일 유효기간이 있지만 자동 갱신이 가능해서 편리하다.

sudo apt update
sudo apt install certbot python3-certbot-nginx -y

서버 PC에서 certbot을 설치한다. certbot은 Let's Encrypt를 사용하게 해준다.

sudo certbot --nginx -d winection.kro.kr -d www.winection.kro.kr 명령어를 입력하여 certbot을 실행한다. -d를 더 입력해 원하는 도메인을 더 추가할 수 있다.

Successfully received certificate.
Certificate is saved at: /etc/letsencrypt/live/winection.kro.kr/fullchain.pem
Key is saved at: /etc/letsencrypt/live/winection.kro.kr/privkey.pem
이런식으로 나오면 인증서 발급에 성공한 것이다. 나는 kro.kr을 썼는데 이 도메인은 요청하는 인원이 많아서 일정 utc 시간 이후에 다시 시도해보라는 경고문이 뜰 때도 있다.

이 과정에서 나는 학교 방화벽 문제, 포트포워딩 문제로 여러번의 실패를 겪어서 좀 애를 많이 먹었었다.

https 사용해보기

이제 ssl 인증서를 nginx에 적용해보자.
sudo vi /etc/nginx/nginx.conf에 다시 들어가서 코드를 다시 수정해준다.

user www-data;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;

events {}

http {
  upstream app {
    server 127.0.0.1:3000;
  }

  underscores_in_headers on;

  # Redirect all traffic to HTTPS
  server {
    listen 80;
    server_name winection.kro.kr;
    return 301 https://$host$request_uri;
  }

  server {
    listen 443 ssl;
    server_name winection.kro.kr;

    ssl_certificate /etc/letsencrypt/live/winection.kro.kr/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/winection.kro.kr/privkey.pem;

    # Enable only secure protocols
    ssl_protocols TLSv1.2 TLSv1.3;

    ssl_prefer_server_ciphers on;
    ssl_ciphers ECDH+AESGCM:ECDH+AES256:ECDH+AES128:DH+3DES:!ADH:!AECDH:!MD5;

    # Enable HSTS
    add_header Strict-Transport-Security "max-age=31536000" always;

    # SSL sessions
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;

    location / {
      proxy_pass http://app;
      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;
    }
  }
}

여러번의 시행 착오 끝에 보안성까지 추가하여 이 코드가 나오게 되었다. 이걸 사용하는 사람은 도메인을 자신의 도메인으로 수정해서 사용하면 될 것이다.

작성이 완료되었으면

sudo nginx -t
sudo systemctl restart nginx

nginx를 재시작해준다.

이제 winection.kro.kr에 접근해보면 제대로 https가 적용되어있다.

이 사진과 같이 나오면 적용이 된 것이다.
추가로 http로 접근하면 카메라와 마이크 사용이 안되기에 http로 접근하면 https로 보내버리도록 하였다.

백엔드 https 적용

여태까지는 프론트엔드에 https 적용을 했었고, 백엔드와 통신을 위해서는 백엔드 또한 적용을 해줘야한다.

내 도메인.한국에서 도메인을 하나 더 추가해준다.

나는 api.winection.kro.kr에 백엔드 엔드 포인트를 달아줄 예정이다.
옆의 ip주소도 프론트와 똑같이 적어준다.

nginx의 리버스 프록시를 사용해 api.winection.kro.kr에 접근하는 도메인은 백엔드 포트로 보내버릴 것이다.

이제 nginx의 설정 파일을 다시 수정해주자.

sudo vi /etc/nginx/nginx.conf

nginx.conf파일을 수정해준다.

user www-data;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;

events {}

http {
    upstream web {
        server 127.0.0.1:3000;
    }
    upstream api {
        server 127.0.0.1:8000;
    }

    server {
        listen 80;
        server_name winection.kro.kr www.winection.kro.kr;
        return 301 https://winection.kro.kr$request_uri;
    }

    server {
        listen 80;
        server_name api.winection.kro.kr;
        return 301 https://api.winection.kro.kr$request_uri;
    }

    server {
        listen 443 ssl;
        server_name winection.kro.kr www.winection.kro.kr;

        ssl_certificate /etc/letsencrypt/live/winection.kro.kr/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/winection.kro.kr/privkey.pem;

        ssl_protocols TLSv1.2 TLSv1.3;
        ssl_prefer_server_ciphers on;
        add_header Strict-Transport-Security "max-age=31536000" always;

        ssl_session_cache shared:SSL:10m;
        ssl_session_timeout 10m;

        location / {
            proxy_pass http://web;
            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;
        }
    }

    server {
        listen 443 ssl;
        server_name api.winection.kro.kr;

        ssl_certificate /etc/letsencrypt/live/winection.kro.kr/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/winection.kro.kr/privkey.pem;

        ssl_protocols TLSv1.2 TLSv1.3;
        ssl_prefer_server_ciphers on;
        add_header Strict-Transport-Security "max-age=31536000" always;

        ssl_session_cache shared:SSL:10m;
        ssl_session_timeout 10m;

        location / {
            proxy_pass http://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;
        }

        location /ws/ {
            proxy_pass http://api/ws/;
            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_read_timeout 86400;
            proxy_send_timeout 86400;
        }
    }
}

나는 추가로 웹 소켓까지 달았기에 웹소켓을 활용할 수 있도록 /ws/라는 엔드포인트를 추가했다.
이제 api.winection.kro.kr로 향하면 백엔드에 연결이 된다.

profile
다덤벼

0개의 댓글