Nginx 설정 그만: Go와 React를 HTTPS로 배포하는 가장 쉬운 방법

발행: (2026년 2월 11일 오전 10:19 GMT+9)
7 분 소요
원문: Dev.to

Source: Dev.to

Cover image for Stop Configuring Nginx: The Easiest Way to Deploy Go & React with HTTPS

“내 컴퓨터에서는 작동해요” 함정

우리는 모두 그런 경험을 해봤습니다. 몇 주 동안 견고한 애플리케이션을 구축하고, Go 백엔드는 번개처럼 빠르고, React 프론트엔드는 반응이 빠르며, 모든 것이 localhost:8080에서 완벽하게 실행됩니다.

하지만 배포 단계가 찾아오면 상황이 달라집니다. 갑자기 VPS 설정, SSL 인증서, 히에라그램처럼 보이는 Nginx 설정 파일, 그리고 무서운 CORS 오류와 씨름하게 됩니다.

저는 최근에 Go와 PostGIS를 사용한 지리공간 백엔드 서비스 Geo Engine을 만들었습니다. 이를 DigitalOcean Droplet에 커스텀 도메인과 HTTPS로 배포하고 싶었지만, Certbot을 설정하거나 복잡한 Nginx 지시문을 관리하는 데 시간을 들이고 싶지는 않았습니다.

저는 Docker ComposeCaddy(당신의 정신을 구원해 주는 웹 서버)를 사용해 이 문제를 해결했습니다.

아키텍처 🏗️

전문적인 프로덕션 환경을 구축하는 것이 목표였습니다:

  • Frontend: app.geoengine.dev 에 배포되는 React 대시보드 (Vite)
  • Backend: api.geoengine.dev 에 배포되는 Go API (Chi Router + PostGIS)
  • Security: 두 서브도메인 모두 자동 HTTPS 적용
  • Infrastructure: 모든 것을 Docker 컨테이너로 관리

포트 80805173 을 외부에 직접 노출하는 대신 Caddy 를 진입점으로 사용했습니다. Caddy는 리버스 프록시 역할을 하며 SSL 인증서 생성 및 갱신을 자동으로 처리합니다.

“마법” Caddyfile ✨

nginx.conf 파일 때문에 고생해 본 적이 있다면, 이 파일을 정말 좋아하게 될 것입니다. 두 개의 서브도메인에 HTTPS를 적용하기 위해 제가 실제로 사용한 전체 설정은 다음과 같습니다:

# The Dashboard (Frontend)
app.geoengine.dev {
    reverse_proxy dashboard:80
}

# The API (Backend)
api.geoengine.dev {
    reverse_proxy api:8080
}

Caddy는 도메인을 자동으로 감지하고, Let’s Encrypt와 통신하여 인증서를 발급받으며, 트래픽을 라우팅합니다. 크론 작업도 필요 없고, 수동 갱신도 필요 없습니다.

Docker 설정 🐳

다음은 제 docker-compose.yml에 들어 있는 비밀 소스입니다. 서비스들이 호스트 머신에 포트를 노출하지 않는다는 점에 주목하세요 (Caddy를 제외하고); 이들은 geo-net 네트워크 내부에서만 통신합니다.

services:
  # Caddy: The only service exposed to the world
  caddy:
    image: caddy:2-alpine
    restart: always
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile
      - caddy_data:/data
    networks:
      - geo-net
    depends_on:
      - dashboard
      - api

  # Backend API
  api:
    build: ./backend
    expose:
      - "8080" # Only visible to Caddy, not the internet
    environment:
      - ALLOWED_ORIGINS=https://app.geoengine.dev
    networks:
      - geo-net

  # Database
  db:
    image: postgres:15-alpine
    # ... config ...
    networks:
      - geo-net

networks:
  geo-net:
    driver: bridge

도전 과제 (제가 막힌 부분) 🚧

모두 순조롭게 진행된 것은 아니었습니다. 디버깅에 몇 시간을 허비하게 만든 두 가지 “함정”을 공유합니다. 여러분은 겪지 않아도 됩니다:

1. “고아” 마이그레이션 컨테이너

데이터베이스 마이그레이션(golang-migrate)을 실행하기 위해 별도의 컨테이너를 사용했습니다. 그런데 연결 오류 때문에 계속 충돌했습니다.

해결 방법: 유틸리티 컨테이너라도 같은 Docker 네트워크에 있어야 합니다! 마이그레이션 서비스에 networks: - geo-net을 추가하는 것을 깜빡해서 데이터베이스를 “볼 수” 없었습니다.

2. CORS 악당 💀

로컬호스트에서는 CORS에 *(와일드카드)를 허용하면 보통 동작합니다. 하지만 HTTPS가 적용된 프로덕션으로 옮긴 뒤 프론트엔드 요청이 실패하기 시작했습니다. 브라우저는 보안 환경에서 자격 증명(쿠키/헤더)에 대해 매우 엄격합니다. 그래서 rs/cors 라이브러리를 사용해 Go 코드에 정확한 오리진을 지정해야 했습니다.

Go 코드 예시:

// 프로덕션에서는 이렇게 하지 마세요:
// AllowedOrigins: []string{"*"} // ❌

// 대신 이렇게 사용하세요:
AllowedOrigins: []string{"https://app.geoengine.dev"} // ✅

프론트엔드의 정확한 오리진과 일치시킴으로써 브라우저와 보안 프로토콜이 만족했습니다.

결과

변경 사항을 푸시한 후 docker compose up -d를 실행했습니다. 약 30초 만에 Caddy가 내 사이트를 보안 처리했습니다.

실제 데모를 확인하려면 여기에서 확인하세요: https://app.geoengine.dev
또는 GitHub에서 코드를 살펴보세요: Geo Engine Core

사이드 프로젝트를 배포한다면 Caddy를 한 번 사용해 보세요. 마치 속이는 듯하지만 가장 좋은 의미에서 그렇습니다.

행복한 코딩 되세요!

0 조회
Back to Blog

관련 글

더 보기 »

bilingual_pdf, @rudifa가 만든 앱

설명: 다른 인간 언어를 배우고 있다면, 자신이 아는 언어의 텍스트와 그 번역이 포함된 bilingual documents를 만들고 싶을 수도 있습니다...