AI 탐구노트

[라즈베리파이] 서로 다른 백엔드 지원 + 서브 도메인 설정 방법 본문

기술 팁

[라즈베리파이] 서로 다른 백엔드 지원 + 서브 도메인 설정 방법

42morrow 2026. 3. 27. 10:46

 

최근 예전에 만들어 둔 퀴즈 게임들을 한곳에서 서비스하도록 만들고 있는 중입니다. 그러다보니 자연스레 서버가 필요하게 되었습니다. 저렴한 서버 호스팅, 예를 들면 iwinv 같은 서비스를 사용하는 것도 손쉬운 방편일텐데 어쩌다보니 집에 굴러다니는 라즈베리파이가 떠올랐습니다. 손바닥만한 장비에서 제대로 서비스가 될까 싶기도 했지만 한편으론, 뭐 대단한 걸 돌리는 것도 아닌데 테스트나 해 보자 싶은 생각이 더 컸습니다. 그래서, 삽질을 했죠.

 

이번 글에서는 라즈베리파이를 서버로 바꾸는 과정을 간단히 정리해 봅니다. 


 

1.개요 

이번 작업은 라즈베리파이 장비를 서버로 사용할 수 있도록 구성하는 것입니다.

이렇게 하는 이유는 라즈베리파이가 

  • 저렴한 가격 : 예전에는 대략 5만원 이내였던 것 같은데 요샌 어떻게 되려나...)
  • 저전력 소모 : 일반 2~3W, 부하 시 5~6W. 단 전원공급은 5V, 3A 이상 권장

 

 

최종적으로 라즈베리파이에 적용한 사항은 다음과 같습니다. 

  • 개인 도메인 이용
    • 십수년째 사용은 안하고 매년 갱신비만 내고 있던터라 아까워서 사용했습니다.
  • 도메인 기반 인증서 적용
    • Let's Encrypt 인증서 적용 (3개월마다 갱신이 필요합니다)
    • Nginx를 이용하면 바로 발급 가능합니다. 그런데 제 경우 예전에 이미 받아둔 것이 있어서 그것을 사용했습니다.  
  • Nginx 리버스 프록시 서버 구성
    • 참고) 클라이언트(사용자)와 백엔드 서버 사이에서 요청 처리를 대리하는 중개 역할 수행 (트래픽 분산, 보안강화, 정적파일 캐싱, SSL 인증서 처리, 등등)
  • 서비스 엔진  (리버스 프록시 서버 아래 구성)
    • gamePortal (nginx + Next.js)
    • 독서기록 서버 (FastAPI + SQLite)
    • 다국어 Chatbot 서버 (FastAPI + Redis + SQLite) 

 

2.구성 시 고려사항 

 

라즈베리파이OS를 이용했는데 이 또한 리눅스 배포본의 하나(데비안 기반)이기 때문에 서버로 만드는 것은 큰 어려움은 없어 보였습니다. 그런데... 한가지 해결해야 하는 문제가 있었는데 그것은 다양한 백엔드가 지원되어야 한다는 것이었죠.

  • Python 기반의 FastAPI : AI 서비스가 포함되거나 Python 모듈을 이용한 API 서비스 
  • Nginx : 주로 정적인 서비스
  • Node 기반 : React, Next.js 등이 적용된 서비스

 

그래서 선택한 방식은 서브 도메인을 두는 것이었습니다. ({도메인명}은 사용한 개인 도메인을 이렇게 표현했습니다. 오해 없으시길)

  • chat.{도메인명} / book.{도메인명} / game.{도메인명}
  • 이 가운데 일부는 FastAPI, 어떤 것은 React/Next.js 를 기반으로 합니다. 

 

3.작업 과정

  • 도메인 인증서 발급
    • nginx에서 해도 되고, 별도로 certbot을 이용해 생성해도 됨 (참고글)
  • 서브 도메인 추가 
    • DNS 정보에 A 타입 등록합니다. 둘 다 공유기 외부 IP 로 연결되도록 설정. 두 IP가 동일한지 확인 필요 (이것도 위의 링크 글을 참고하시면 됩니다)
      • 공인 IP 확인 : hostname -I
      • DNS가 가리키는 IP 확인 : nslookup {도메인명}
  • 공유기 NAT 포트 포워딩
    • 443 / https 를 라즈베리파이 IP 및 내부  서비스 포트로 연결
  • 라즈베리파이 서버
    • nginx 동작 확인 : 리버스 라우터 설정
      • FastAPI
        • chatbot은 localhost / 8000 포트로 기동 (uvicorn 명령)
        • book은 localhost / 8000 포트로 기동 (uvicorn 명령)
      • React/Next.js 또는 정적 html Path 설정
    • 방화벽 허용: ufw 이용 확인 (sudo ufw status)
      • 443/tcp
      • 8000/tcp, 8001/tcp
    • 서버 서비스 구동 확인
      • FastAPI : system 서비스로 등록해서 실행 
        • 확인 : sudo systemctl status multilingual-*.service redis
      • Nginx : system 서비스 등록 (자동)
        • 확인 : sudo systemctl status nginx

 

 

4.세부 작업 과정

 

1️⃣ Nginx 설정 등록

sudo vi /etc/nginx/sites-available/{domain}

 

리버스 프록시 서버 구성은 대략 다음과 같습니다.  (각자의 도메인명을 적용해서 사용하시면 됩니다)

특징으로는 다음과 같은 것이 있습니다. 

  • 접속 시 마이크/카메라 접속 시 거부되지 않도록 설정 (STT/카메라 이용한 게임 때문)
  • STT 사용 시 스트리밍 응답 즉시 전달 설정
  • 정적 html에 대해서는 rate limit 없고 캐시 적용하도록 설정
  • http 접속 시 https로 리디렉션 
  • websocket 지원을 위한 http 업그레이드 설정
# =========================
# 사용자 정의 변수(치환용 표기)
# =========================
# ${PRIMARY_DOMAIN}        -> 예: example.com
# ${API_DOMAIN}            -> 예: api.example.com
# ${STATIC_DOMAIN}         -> 예: app.example.com
# ${CERT_BASE_DIR}         -> 예: /home/${SERVER_USER}/.letsencrypt/live/${CERT_DOMAIN}
# ${CERT_DOMAIN}           -> 인증서가 발급된 기준 도메인
# ${SERVER_USER}           -> 예: 사용자 계정
# ${FASTAPI_UPSTREAM}      -> 예: http://127.0.0.1:8000
# ${STATIC_ROOT}           -> 예: /var/www/appname

# HTTP 접속은 모두 HTTPS로 리디렉션
server {
    listen 80;
    listen [::]:80;
    server_name ${API_DOMAIN} ${STATIC_DOMAIN};

    return 301 https://$host$request_uri;
}

# =========================
# ${API_DOMAIN} -> FastAPI
# =========================
server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name ${API_DOMAIN};

    ssl_certificate     ${CERT_BASE_DIR}/fullchain.pem;
    ssl_certificate_key ${CERT_BASE_DIR}/privkey.pem;

    # 서버 정보 숨기기
    server_tokens off;

    # 보안 헤더
    add_header X-Content-Type-Options    "nosniff"         always;
    add_header X-Frame-Options           "SAMEORIGIN"      always;
    add_header Referrer-Policy           "strict-origin"   always;
    add_header Permissions-Policy "geolocation=(), camera=(self), microphone=(self)" always;

    # 기본 업로드/요청 크기 필요 시 조정
    client_max_body_size 50M;

    location / {
        proxy_pass ${FASTAPI_UPSTREAM};
        proxy_http_version 1.1;
        
        proxy_buffering off;        # 스트리밍 응답 즉시 전달

        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;

        # websocket 대비용
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";

        proxy_read_timeout 300;
        proxy_send_timeout 300;
        
        proxy_cache_bypass $http_upgrade;
    }
}

# ====================================
# ${STATIC_DOMAIN} -> 정적 React/Vite 앱
# ====================================
server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name ${STATIC_DOMAIN};

    ssl_certificate     ${CERT_BASE_DIR}/fullchain.pem;
    ssl_certificate_key ${CERT_BASE_DIR}/privkey.pem;
    
    # 서버 정보 숨기기
    server_tokens off;

    # 보안 헤더
    add_header X-Content-Type-Options    "nosniff"         always;
    add_header X-Frame-Options           "SAMEORIGIN"      always;
    add_header Referrer-Policy           "strict-origin"   always;
    add_header Permissions-Policy        "geolocation=(), camera=(self), microphone=(self)" always;


    # 기본 업로드/요청 크기 필요 시 조정
    client_max_body_size 50M;

    # 정적 파일 - rate limit 없음, 캐시 적용
    location /_next/static/ {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        add_header Cache-Control "public, max-age=31536000, immutable";
    }
    
	# 인증/API만 rate limit 적용
    location /api/ {
        limit_req zone=gameportal burst=10 nodelay;
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;
        proxy_buffering off;
        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 / {
        proxy_pass http://127.0.0.1:3000;
        proxy_http_version 1.1;

        proxy_buffering off;        # 스트리밍 응답 즉시 전달

        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;

        # websocket 대비용
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;

        proxy_read_timeout 300;
        proxy_send_timeout 300;

        proxy_cache_bypass $http_upgrade;
    }
}

 

 

 

 

2️⃣ 서비스 등록 예시 

 

서비스 등록 예시입니다. (변수로 설정된 부분은 각자 사용하는 값을 적용하시면 됩니다)

[Unit]
Description=Multilingual Chat Uvicorn Server
After=network.target redis-server.service
Wants=redis-server.service

[Service]
Type=simple
User=sol
WorkingDirectory={실행코드_PATH}

Environment="REDIS_HOST=localhost"
Environment="REDIS_PORT=6379"
Environment="REDIS_DB=0"
Environment="REDIS_PASSWORD={REDIS_PASSWORD}"
Environment="REDIS_USE_SSL=0"
Environment="ROOM_TTL_SEC=300"
Environment="ADMIN_TOKEN={ADMIN_TOKEN}"

ExecStart={실행코드_PATH}/run_server.sh
Restart=always
RestartSec=3
Environment=PYTHONUNBUFFERED=1

[Install]
WantedBy=multi-user.target

 


 

현재  라즈베리파이 서버는 잘 작동 중입니다. 대부분의 서비스가 Javascript로 로컬에서 구동되는 것이다보니 서버 자체에 대한 부하도 크지 않아서 개인적인 목적으로 돌리는 것에는 전혀 무리가 없습니다. 게다가 24시간 주구장창 돌아도 전기세 걱정이 없고, 서버 구독비용이나 유지비용도 들지 않으니 정말 최고의 선택이었던 것 같습니다. 

 

여기에 만들어둔 게임포털에는 원격에서 바이브코딩으로 하나씩 만들어서 커맨드 한줄로 배포하고, 백업도 커맨드 한줄로 처리하고... 햐... 이렇게 활용할 수 있는 것을 이제껏 몰랐다니... 하는 아쉬움이 들 정도로 만족감이 큽니다. 

 

혹시나 집에 굴러다니는 라즈베리파이 하나 있으시면 저처럼 멋쟁이로 한번 변모를 시켜보시기 바랍니다. ^^