← 블로그 목록
CloudflareTunnelDockerHTTPS보안배포CloudflarePages1인개발

Cloudflare Tunnel Docker 설정 가이드 — 공인 IP 없이 HTTPS 서버 운영하기

2026년 4월 16일

투자킹 서버는 AWS Lightsail에서 운영합니다.

초기에는 Lightsail 공인 IP를 Cloudflare DNS에 A 레코드로 등록해서 사용했습니다.

api.twojaking.com → A → 13.xx.xx.xx (Lightsail 공인 IP)

이 방식은 동작하지만 세 가지 문제가 있었습니다.

  • SSL 인증서 직접 관리: Let's Encrypt + Certbot으로 발급하고, 90일마다 갱신
  • 공인 IP 노출: 서버 IP가 외부에 노출되어 DDoS 등 직접 공격 가능
  • Nginx HTTPS 설정 복잡도: 인증서 경로, HTTP→HTTPS 리디렉션 등

Cloudflare Tunnel로 전환하면 이 세 가지가 모두 사라집니다.


Cloudflare Tunnel 동작 원리

Cloudflare Tunnel은 서버에서 Cloudflare 쪽으로 아웃바운드 연결을 맺는 방식입니다.

기존 방식은 외부에서 서버 IP로 들어오는 인바운드 방식이었습니다. Tunnel은 반대입니다.

기존: 외부 클라이언트 → (공인 IP) → 서버
터널: 서버 → Cloudflare ← 외부 클라이언트

서버가 먼저 Cloudflare에 연결을 열어두고, 외부 요청은 그 연결을 통해 전달됩니다.

서버의 공인 IP를 외부에 열지 않아도 됩니다. 외부에서는 Cloudflare IP만 보입니다.


1단계: Cloudflare Dashboard에서 Tunnel 생성

Cloudflare 대시보드에서 아래 경로로 이동합니다.

Zero Trust → Networks → Tunnels → Create a tunnel

터널 이름을 지정하고 생성하면 Tunnel Token이 발급됩니다. 이 토큰은 나중에 사용하므로 별도로 저장해 둡니다.

이어서 Public Hostname을 설정합니다. cloudflared 컨테이너가 Docker 내부 네트워크에서 Nginx와 통신하므로, 서비스 주소로 컨테이너 이름을 그대로 사용합니다.

Subdomain : api
Domain    : twojaking.com
Service   : http://nginx:80

2단계: docker-compose.yml에 cloudflared 서비스 추가

아래는 실제 운영 중인 docker-compose.yml의 cloudflared 서비스 섹션입니다.

cloudflared:
  image: cloudflare/cloudflared@sha256:6b599ca3e974349ead3286d178da61d291961182ec3fe9c505e1dd02c8ac31b0  # pinned 2026-04-10
  container_name: twojaking-cloudflared
  restart: always
  env_file: ../.env
  environment:
    - TUNNEL_TOKEN=${CLOUDFLARE_TUNNEL_TOKEN}
  command: tunnel --no-autoupdate --protocol quic run
  depends_on:
    - api-0
    - api-1
  networks:
    - backend

설정 항목별 의미는 다음과 같습니다.

이미지 pinning (sha256)

latest 태그는 언제 이미지가 교체될지 알 수 없습니다. sha256 해시로 고정하면 의도치 않은 업데이트를 방지할 수 있습니다. 업데이트가 필요할 때만 해시를 명시적으로 교체합니다.

--no-autoupdate

cloudflared 바이너리의 자동 업데이트를 비활성화합니다. 컨테이너 환경에서 자동 업데이트는 예측 불가능한 동작을 유발할 수 있으므로, 이미지 교체 방식으로 업데이트를 관리합니다.

--protocol quic

Cloudflare 엣지 서버와의 연결 프로토콜을 QUIC(HTTP/3)으로 지정합니다. 이 설정의 의미는 아래 QUIC 섹션에서 자세히 설명합니다.

networks: backend

Nginx, API 서버와 동일한 Docker 내부 네트워크에 연결합니다. cloudflared가 http://nginx:80으로 요청을 전달하려면 같은 네트워크에 있어야 합니다.


3단계: .env에 Tunnel Token 추가

1단계에서 발급받은 Tunnel Token을 .env 파일에 추가합니다.

CLOUDFLARE_TUNNEL_TOKEN=eyJhI...

docker-compose.yml에서 env_file: ../.envTUNNEL_TOKEN=${CLOUDFLARE_TUNNEL_TOKEN} 조합으로 컨테이너에 주입됩니다.


4단계: Nginx에서 SSL 설정 제거

Cloudflare Tunnel 도입 이전에는 Nginx에서 SSL을 직접 처리했습니다.

# 이전 설정
server {
    listen 443 ssl;
    ssl_certificate /etc/letsencrypt/live/api.twojaking.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/api.twojaking.com/privkey.pem;
    ...
}

Tunnel을 사용하면 외부 HTTPS는 Cloudflare가 처리합니다. Nginx는 내부 네트워크에서 HTTP만 받으면 됩니다.

아래는 현재 운영 중인 nginx/twojaking.conf의 서버 블록입니다.

# HTTP 서버 (Cloudflare Tunnel이 SSL 처리)
server {
    listen 80;
    listen [::]:80;
    server_name api.twojaking.com;

    # SSE 전용: 버퍼링 비활성화 (코인/포트폴리오 스트림)
    location ~ ^/api/v1/(coins/stream|user/portfolio/stream|...) {
        proxy_pass http://twojaking_backend;
        proxy_buffering off;
        proxy_cache off;
        proxy_read_timeout 3600s;
        ...
    }

    location / {
        proxy_pass http://twojaking_backend;
        ...
    }
}

SSL 관련 설정이 전혀 없습니다. listen 80만 남아 있고, Certbot 컨테이너도 삭제했습니다.


5단계: QUIC 프로토콜 최적화

cloudflared 실행 명령의 --protocol quic은 Cloudflare 엣지 서버와의 연결을 TCP 대신 QUIC(HTTP/3)으로 맺도록 지정합니다.

QUIC은 UDP 기반이라 TCP보다 핸드쉐이크가 빠르고, 패킷 손실 시 회복도 빠릅니다. 투자킹은 실시간 코인 가격을 SSE(서버 전송 이벤트)로 스트리밍하기 때문에 장시간 연결을 유지하는 데 유리한 QUIC을 선택했습니다.

Linux UDP 버퍼 크기 설정

QUIC은 UDP를 사용하므로, UDP 소켓 버퍼 크기를 늘려야 성능이 제대로 나옵니다. 기본값은 약 212KB로, QUIC이 권장하는 수준보다 작습니다.

docker/scripts/deploy.sh에 아래 코드가 포함되어 있습니다.

# Linux에서 QUIC 성능을 위한 UDP 버퍼 크기 설정
if [[ "$(uname)" == "Linux" ]]; then
    sysctl -w net.core.rmem_max=7340032 > /dev/null 2>&1 || true
    sysctl -w net.core.wmem_max=7340032 > /dev/null 2>&1 || true
fi

rmem_max는 UDP 수신 버퍼, wmem_max는 UDP 전송 버퍼입니다. 각각 7MB(7,340,032바이트)로 설정합니다.

sysctl -w는 재부팅 시 초기화됩니다. 영구 적용하려면 /etc/sysctl.conf에 추가해야 하지만, 배포 스크립트에서 매번 설정해도 실용적으로는 문제없습니다.


변경 후 달라진 아키텍처

이전

외부 클라이언트
  → HTTPS (공인 IP)
  → Nginx (SSL 종료 + 로드밸런싱)
  → API 서버

현재

외부 클라이언트
  → Cloudflare (HTTPS 처리)
  → Cloudflare Tunnel (QUIC)
  → Nginx (HTTP 로드밸런싱)
  → API 서버

없어진 것들

  • Certbot 컨테이너
  • Let's Encrypt 인증서 파일과 갱신 cron
  • Nginx의 SSL 블록, HTTPS 리디렉션 설정
  • Lightsail의 80/443 인바운드 포트 규칙
  • 공인 IP 노출

추가된 것들

  • cloudflared 컨테이너 하나
  • .envCLOUDFLARE_TUNNEL_TOKEN 변수 하나

인증서 갱신 실패로 서비스가 중단되는 상황이 없어졌습니다. Cloudflare의 DDoS 방어와 CDN도 함께 적용됩니다.


서버 인바운드 포트 정리

Lightsail 방화벽에서 80, 443 포트를 닫아도 됩니다.

cloudflared가 아웃바운드로 연결을 열기 때문에 인바운드 포트는 불필요합니다. SSH(22)만 열어두면 충분합니다.

0

댓글 0

Ctrl+Enter