# 커스텀 도메인 연결하기

> Cloudflare를 리버스 프록시로 사용해 Daki에서 호스팅하는 웹 앱에 내 도메인을 연결하세요 — 무료 HTTPS와 사용자 지정 포트 전달 포함. 고급 Cloudflare Tunnel(cloudflared) 대안도 설명합니다.

웹 앱을 `hostname:port` 주소 대신 `yourdomain.com`에서 접근할 수 있게 하고 싶으신가요? 이 가이드에서는 무료 리버스 프록시인 **Cloudflare**를 사용해 내 도메인을 Daki 서버에 연결하는 방법을 알려 드려요. Cloudflare가 HTTPS를 처리하고, 방문자의 요청을 앱이 수신하는 사용자 지정 포트로 전달해 줘요.

> **정보:** 커스텀 도메인은 **유료 플랜**에서만 사용할 수 있어요. 공개적으로 접근 가능한 포트가 필요한데, 공개 포트는 유료 플랜(Bronze, Silver, Gold 및 게임 서버 플랜)에만 포함돼요. Free Tier 서버는 외부 연결을 받을 수 없으므로 거기서는 커스텀 도메인이 작동하지 않아요. [앱 & 봇 호스팅 플랜](https://daki.cc/ko/wiki/guides/paid-plans)과 [네트워크 & 할당](https://daki.cc/ko/wiki/guides/network-allocations)을 참고하세요.

## 배울 내용

- Daki에서 커스텀 도메인에 Cloudflare 같은 리버스 프록시가 필요한 이유
- 프록시가 적용된 DNS 레코드로 도메인을 서버에 연결하는 방법
- 도메인에 무료 HTTPS를 활성화하는 방법
- Origin Rule로 앱 포트에 트래픽을 전달하는 방법
- Cloudflare Tunnel(`cloudflared`)을 사용하는 고급 대안

---

## 준비물

- 공개 포트 할당이 있는 **유료 플랜** 서버 — [앱 & 봇 호스팅 플랜](https://daki.cc/ko/wiki/guides/paid-plans)을 참고하세요
- 내가 소유한 **도메인 이름**(어떤 등록기관에서든 등록 가능, 예: Namecheap, GoDaddy, IONOS, Cloudflare Registrar)
- 무료 [Cloudflare](https://dash.cloudflare.com/sign-up) 계정
- 서버의 할당 포트에서 실행 중이며 수신 대기 중인 **웹 앱**(HTTP/HTTPS)

> **주의:** 앱은 할당 포트에서 `0.0.0.0`(모든 인터페이스)에 바인딩해야 해요 — `127.0.0.1` / `localhost`가 **아니에요**. localhost에만 바인딩된 앱은 컨테이너 외부에서 접근할 수 없어서, 프록시가 연결에 실패해요. 대부분의 프레임워크는 호스트 인수를 받아요(예: Flask에서는 `app.run(host="0.0.0.0", port=...)`, 많은 Node.js 앱에서는 `HOST=0.0.0.0`).

---

## Cloudflare가 필요한 이유

Daki는 노드 전체에서 **공유 IP 주소**를 사용하고, 앱은 표준이 아닌 **포트**(서버에 할당된 포트)에서 수신해요. 그래서 다음 두 가지 문제가 생겨요:

- 방문자는 `yourdomain.com:4129`가 아니라 표준 HTTPS 포트(443)에서 `https://yourdomain.com`에 접속하길 기대해요.
- 직접 관리하지 않고도 유효한 HTTPS 인증서를 쓰고 싶으시죠.

서버 앞에 Cloudflare를 두면 두 문제가 모두 해결돼요. Cloudflare는 **리버스 프록시** 역할을 해요. 브라우저는 443 포트의 HTTPS로 Cloudflare에 연결하고, Cloudflare가 요청을 사용자 지정 포트의 서버로 전달해요. 무료 SSL과 깔끔한 URL을 얻을 수 있고, 서버의 실제 IP는 숨겨진 상태로 유지돼요.

> **참고:** 이 방법은 **HTTP/HTTPS 웹 앱**(웹사이트, API, 대시보드, 웹훅)에 적합해요. 대부분의 게임 서버처럼 순수 TCP/UDP 서비스에는 **작동하지 않아요** — Cloudflare의 기본 프록시는 HTTP(S)만 처리해요. HTTP가 아닌 서비스라면 아래의 [Cloudflare Tunnel](#advanced-alternative-cloudflare-tunnel-cloudflared) 방법을 사용하거나 [지원팀](https://daki.cc/ko/wiki/guides/support)에 문의하세요.

---

## 서버 IP와 포트 찾기

[Portal](https://daki.cc/ko/wiki/guides/portal)의 **Network** 탭에서 두 가지 값을 확인해야 해요:

| 값 | 확인 위치 | 예시 |
| :--- | :--- | :--- |
| **서버 IP** | 서버 호스트 이름이 변환되는 IP 주소(**Network** 탭에 표시돼요). 호스트 이름만 표시된다면 `ping eu-node1.daki.cc` 같은 명령으로 확인하거나 [지원팀](https://daki.cc/ko/wiki/guides/support)에 문의하세요. | `203.0.113.10` |
| **할당 포트** | **Network** 탭에서 호스트 이름 옆에 표시되는 포트. 앱이 이 포트에서 수신해야 해요. | `4129` |

이 가이드 전체에서 예시 값을 사용해요. 등장하는 곳마다 **본인의 값으로 바꾸세요.** 할당에 대한 자세한 내용은 [네트워크 & 할당](https://daki.cc/ko/wiki/guides/network-allocations)을 참고하세요.

---

## 1부: Cloudflare 설정

**단계 1: Cloudflare에 도메인 추가하기**

1. [dash.cloudflare.com](https://dash.cloudflare.com/sign-up)에서 무료 계정을 만드세요(또는 로그인하세요).
2. **Add a site**를 클릭하고 도메인(예: `yourdomain.com`)을 입력하세요.
3. 안내가 나오면 **Free** 플랜을 선택하세요.
4. Cloudflare가 **네임서버 두 개**(예: `xxx.ns.cloudflare.com`)를 알려 줘요.
5. **도메인 등록기관** 사이트에 로그인하고 도메인의 네임서버 / DNS 설정을 찾은 다음, 기존 네임서버를 Cloudflare가 준 두 개로 바꾸세요.

> **참고:** 네임서버 변경이 반영되는 데는 몇 분에서 최대 24시간까지 걸릴 수 있어요. 도메인이 활성화되면 Cloudflare가 이메일로 알려 줘요. 그동안 다음 단계를 계속 진행해도 돼요.

**단계 2: DNS 레코드 만들기**

도메인의 Cloudflare 대시보드에서 **DNS → Records**로 이동한 뒤 **Add record**를 클릭하세요:

| 필드 | 값 |
| :--- | :--- |
| **Type** | `A` |
| **Name** | `@` (루트 도메인 `yourdomain.com`을 의미해요) |
| **IPv4 address** | 서버 IP (예: `203.0.113.10`) |
| **Proxy status** | **Proxied** (주황색 구름 ☁️) |

**Save**를 클릭하세요.

> **팁:** **주황색 구름(Proxied)** 상태가 필수예요 — 트래픽이 Cloudflare를 거쳐야 SSL과 2부의 포트 변경이 작동해요. 회색 구름(DNS only)으로 두면 Cloudflare를 완전히 우회하게 되어 서버 IP가 노출돼요. `www.yourdomain.com`도 제공하려면 이름을 `www`로 한 프록시 레코드를 하나 더 추가하세요.

**단계 3: SSL/TLS 설정하기**

1. **SSL/TLS → Overview**로 이동하세요.
2. 암호화 모드를 **Flexible**로 설정하세요.

**Flexible**은 방문자와 Cloudflare 사이의 트래픽은 암호화(HTTPS)되지만, Cloudflare가 사용자 지정 포트의 앱에 일반 HTTP로 연결한다는 뜻이에요. 사용자 지정 포트에서 일반 HTTP를 제공하는 대부분의 앱에 알맞은 선택이에요.

> **참고:** 앱이 자체 TLS를 처리한다면(즉, 포트에서 **HTTPS**를 직접 제공한다면) 대신 **Full**을 선택하세요. **Flexible**은 Cloudflare와 서버 사이의 연결을 암호화하지 않으니, 민감한 트래픽에는 앱에서 HTTPS를 사용하는 **Full**을 권장해요.

---

## 2부: 앱 포트로 트래픽 전달하기

앱은 표준 80/443이 아닌 사용자 지정 포트(예: `4129`)에서 실행되므로, Cloudflare가 서버에 연결할 때 어떤 포트를 사용할지 알려 줘야 해요. **Origin Rule**이 바로 그 역할을 해요 — 들어오는 요청의 대상 포트를 바꿔 줘요.

**단계 1: Origin Rule 만들기**

1. Cloudflare 대시보드에서 **Rules → Origin Rules**로 이동하세요.
2. **Create Origin Rule**을 클릭하세요.
3. 규칙을 설정하세요:

| 설정 | 값 |
| :--- | :--- |
| **When incoming requests match** | 호스트 이름이 `yourdomain.com`과 **equals**일 때 |
| **Then... Rewrite to** | **Destination Port** → `4129` (할당 포트) |

4. **Save**(또는 **Deploy**)를 클릭하세요.

> **완료:** DNS가 전파되면 앱은 이제 **`https://yourdomain.com`**에서 접근할 수 있어요 — Cloudflare가 HTTPS로 제공하고, 트래픽을 포트 `4129`의 서버로 전달해요.

---

## 작동 확인하기

1. 서버가 **실행 중**이고 앱이 할당 포트에서 수신하고 있는지 확인하세요(**Console** 탭에서 확인할 수 있어요).
2. 브라우저에서 `https://yourdomain.com`을 여세요.
3. 앱이 유효한 자물쇠 아이콘과 함께 HTTPS로 로드되어야 해요.

바로 로드되지 않으면 DNS가 전파될 시간을 조금 더 주고, 아래의 [자주 발생하는 문제](#common-issues)를 확인하세요.

---

## 고급 대안: Cloudflare Tunnel (cloudflared)

공개 포트를 열고 서버를 가리키는 A 레코드를 만드는 대신, Cloudflare의 터널 클라이언트인 **`cloudflared`**를 앱과 함께 실행할 수 있어요. 이 방식은 Cloudflare로 **아웃바운드** 연결을 열고 트래픽을 로컬의 앱으로 전달하므로, 서버의 IP와 포트가 숨겨진 상태로 유지되고 DNS A 레코드도 Origin Rule도 필요 없어요.

> **참고:** **Daki 환경이 이 방식에 주는 영향.** **Startup** 탭에는 두 가지 명령이 있어요 — [**설치 명령**](https://daki.cc/ko/wiki/guides/startup-settings)(설정/재설치 시 한 번 실행)과 **시작 명령**(부팅할 때마다 실행). 시작 명령을 `bash`로 임시 설정하면 셸을 열 수도 있어요. 하지만 서버는 **전체 Linux VM이 아니라 컨테이너**예요: `apt`가 없고, 시스템 서비스를 등록할 수 없으며, 콘솔에서 직접 시작한 터널은 서버가 재시작되면 사라져요. 그래서 가장 안정적인 방법은 터널을 **앱 자체의 시작 과정에 포함**하는 거예요 — 런타임에 `cloudflared` 바이너리를 내려받고, 환경 변수로 제어되는 자식 프로세스로 앱 옆에서 실행하는 거죠.

**단계 1: 터널 만들기**

1. Cloudflare **Zero Trust** 대시보드에서 **Networks → Tunnels**로 이동하고 **Create a tunnel**을 클릭한 다음 **Cloudflared**를 선택하세요.
2. 이름을 정하고, Cloudflare가 표시하는 **터널 토큰**을 복사하세요(설치 명령의 `--token` 뒤에 있는 긴 문자열이에요).
3. 터널에 **Public Hostname**을 추가하세요:
   - **Domain:** `yourdomain.com`
   - **Service:** `http://localhost:4129` (앱의 로컬 포트)

**단계 2: .env에 토큰 저장하기**

토큰을 서버의 `.env` 파일에 추가하세요([환경 변수](https://daki.cc/ko/wiki/guides/environment-variables) 참고). 그래야 소스 코드에 노출되지 않아요:

```

CLOUDFLARE_TUNNEL_TOKEN=your_tunnel_token_here

```

**단계 3: 코드에서 터널 시작하기**

처음 실행할 때 `cloudflared`를 내려받고 앱 옆에서 시작하는 작은 런처를 추가한 다음, 그 런처를 **시작 명령**으로 설정하세요. `CLOUDFLARE_TUNNEL_TOKEN`이 설정되어 있을 때만 터널을 시작하므로, 같은 코드를 토큰 없이 로컬에서 실행해도 문제없어요.

**Node.js (start.js)**

`start.js`로 저장한 다음, 시작 명령을 `node start.js`로 설정하세요:

```js

// start.js — starts your app and, if a token is set, a Cloudflare tunnel.

require("dotenv").config();



const fs = require("fs");

const path = require("path");

const { spawn, spawnSync } = require("child_process");



const CLOUDFLARED_URL =

  "https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64";

const binaryPath = path.join(__dirname, "cloudflared");

const children = [];



// The container has no apt — download the binary on first run, reuse it after.

function ensureCloudflared() {

  if (fs.existsSync(binaryPath)) return binaryPath;

  console.log("[tunnel] downloading cloudflared...");

  const dl = spawnSync("curl", ["-fsSL", "-o", binaryPath, CLOUDFLARED_URL], {

    stdio: "inherit",

  });

  if (dl.status !== 0) return null;

  fs.chmodSync(binaryPath, 0o755);

  return binaryPath;

}



// Spawn a child process; if any child exits, stop the rest and quit.

function start(name, command, args) {

  const child = spawn(command, args, { stdio: "inherit", env: process.env });

  child.on("exit", (code) => {

    console.error(`[${name}] exited (${code}); shutting down`);

    for (const c of children) if (!c.killed) c.kill("SIGTERM");

    process.exit(code ?? 1);

  });

  children.push(child);

}



const token = process.env.CLOUDFLARE_TUNNEL_TOKEN?.trim();

if (token) {

  const binary = ensureCloudflared();

  if (binary) start("tunnel", binary, ["tunnel", "run", "--token", token]);

} else {

  console.log("[tunnel] CLOUDFLARE_TUNNEL_TOKEN not set; skipping tunnel");

}



// Start your app — change this to your real entry point.

start("app", "node", ["index.js"]);

```

**Python / 기타 (start.sh)**

`start.sh`로 저장한 다음, 시작 명령을 `bash start.sh`로 설정하세요:

```bash

#!/usr/bin/env bash

# Load variables from .env into the environment.

set -a; [ -f .env ] && . ./.env; set +a



# The container has no apt — download cloudflared on first run, reuse it after.

if [ ! -x ./cloudflared ]; then

  curl -fsSL -o ./cloudflared \

    https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64

  chmod +x ./cloudflared

fi



# Start the tunnel in the background only if a token is set.

if [ -n "$CLOUDFLARE_TUNNEL_TOKEN" ]; then

  ./cloudflared tunnel run --token "$CLOUDFLARE_TUNNEL_TOKEN" &

fi



# Start your app in the foreground — change this to your real command.

exec python main.py

```

> **팁:** 매번 다시 다운로드하지 않으려면 다운로드 과정을 [설치 명령](https://daki.cc/ko/wiki/guides/startup-settings)으로 옮기고, 시작 명령에는 실행 로직만 남겨 둘 수 있어요. 위 예시는 바이너리가 이미 있으면 다운로드를 건너뛰므로, 매 부팅마다 실행해도 안전해요.

**장단점:**

| | Cloudflare 프록시 (1–2부) | Cloudflare Tunnel (cloudflared) |
| :--- | :--- | :--- |
| 설정 난이도 | 더 쉬움 (대시보드만 사용) | 더 고급 (앱의 시작 과정에 포함) |
| 서버 IP/포트 노출 | 프록시 뒤에 숨겨짐 | 완전히 숨겨짐 (아웃바운드 전용) |
| 공개 포트 / Origin Rule 필요 여부 | 필요 | 필요 없음 |
| 서버 리소스 사용 | 서버에서는 사용 안 함 | 추가 프로세스 실행 |

---

## 자주 발생하는 문제

**사이트가 전혀 열리지 않아요**

DNS가 아직 전파되는 중일 수 있어요. 네임서버 변경은 최대 24시간이 걸릴 수 있어요(보통은 훨씬 빨라요). Cloudflare에서 도메인 상태가 **Active**인지, **A 레코드**가 있고 **Proxied**(주황색 구름)인지 확인하세요. 전파 상태는 [dnschecker.org](https://dnschecker.org) 같은 도구로 확인할 수 있어요.

**Error 522 / 523 — 연결 시간 초과 / 원본에 도달할 수 없음**

Cloudflare가 앱에 연결할 수 없어요. 다음 순서로 확인하세요:
- **서버가 실행 중**이고 앱이 완전히 시작되었는지 확인하세요(**Console** 탭에서 확인할 수 있어요).
- **Origin Rule**이 포트를 정확한 **할당 포트**로 바꾸고 있는지 확인하세요.
- **A 레코드의 IP**가 서버 IP와 일치하는지 확인하세요.
- 앱이 `127.0.0.1`/`localhost`가 아니라 **`0.0.0.0`**에 바인딩되어 있는지 확인하세요.

**리다이렉트가 너무 많아요 / ERR_TOO_MANY_REDIRECTS**

보통 SSL 모드가 맞지 않아서 생기는 문제예요. 앱이 HTTPS를 강제하는데 Cloudflare가 HTTP로 연결하면 리다이렉트 루프가 생겨요. 앱이 일반 HTTP를 제공하도록 바꾸고 **Flexible**을 사용하거나, 앱이 HTTPS를 제공하도록 하고 **SSL/TLS → Overview**에서 Cloudflare를 **Full**로 바꾸세요.

**http://에서는 되는데 https://에서는 안 돼요**

DNS 레코드가 **Proxied**(주황색 구름)인지, 그리고 **SSL/TLS** 모드(Flexible 또는 Full)가 설정되어 있는지 확인하세요. 회색 구름(DNS only)이면 Cloudflare의 SSL이 우회돼요.

**게임 서버 / 비HTTP 서비스가 작동하지 않아요**

Cloudflare의 기본 프록시는 HTTP/HTTPS만 처리해요. 대부분의 게임 서버처럼 순수 TCP/UDP 서비스에는 이 방법이 적용되지 않아요. 서버의 호스트 이름과 포트로 직접 연결하거나([네트워크 & 할당](https://daki.cc/ko/wiki/guides/network-allocations) 참고), 다른 옵션은 [지원팀](https://daki.cc/ko/wiki/guides/support)에 문의하세요.

**Cloudflare를 꼭 써야 하나요?**

아니요 — Cloudflare가 가장 간단한 무료 옵션이지만, 사용자 지정 포트로 전달할 수 있는 리버스 프록시라면 무엇이든 작동해요(예: `cloudflared` 터널, 또는 앱 앞단에 둔 직접 설치한 Nginx/Caddy). Cloudflare를 권장하는 이유는 무료 SSL을 제공하고, 서버 IP를 숨기며, 프록시 방식에서는 서버에 추가 소프트웨어가 필요 없기 때문이에요.

---

## 다음 단계

- [네트워크 & 할당](https://daki.cc/ko/wiki/guides/network-allocations) — 호스트 이름과 포트를 확인하고, 할당을 추가하세요.
- [앱 & 봇 호스팅 플랜](https://daki.cc/ko/wiki/guides/paid-plans) — 공개 포트를 얻으려면 유료 플랜으로 업그레이드하세요.
- [환경 변수](https://daki.cc/ko/wiki/guides/environment-variables) — 코드에서 환경 변수로 포트를 읽어 보세요.
- [도움 받기](https://daki.cc/ko/wiki/guides/support) — 막히면 지원팀에 문의하세요.

---
Source: https://daki.cc/ko/wiki/guides/custom-domain
