> ## Documentation Index
> Fetch the complete documentation index at: https://wiki.daki.cc/llms.txt
> Use this file to discover all available pages before exploring further.

# Connect a Custom Domain

> Point your own domain at a web app hosted on Daki using Cloudflare as a reverse proxy — with free HTTPS and custom-port forwarding. Includes an advanced Cloudflare Tunnel (cloudflared) alternative.

Want your web app to be reachable at `yourdomain.com` instead of a `hostname:port` address? This guide shows you how to point your own domain at a Daki server using **Cloudflare** as a free reverse proxy. Cloudflare handles HTTPS for you and forwards visitors to the custom port your app listens on.

<Info>
  Custom domains require a **paid plan**. They rely on a publicly accessible port, and only paid plans (Bronze, Silver, Gold, and game server plans) include public ports. Free Tier servers cannot receive inbound connections, so a custom domain cannot work there. See [App & Bot Hosting Plans](./paid-plans) and [Network & Allocations](./network-allocations).
</Info>

## What you'll learn

* Why a custom domain on Daki needs a reverse proxy like Cloudflare
* How to point your domain at your server with a proxied DNS record
* How to enable free HTTPS for your domain
* How to forward traffic to your app's port with an Origin Rule
* An advanced alternative using Cloudflare Tunnel (`cloudflared`)

***

## Prerequisites

* A **paid plan** server with a public port <Tooltip tip="A public hostname + port pair assigned to your server.">allocation</Tooltip> — see [App & Bot Hosting Plans](./paid-plans)
* A **domain name** you own (registered at any registrar, e.g. Namecheap, GoDaddy, IONOS, Cloudflare Registrar)
* A free [Cloudflare](https://dash.cloudflare.com/sign-up) account
* A **web app** (HTTP/HTTPS) that is running and listening on your server's allocation port

<Warning>
  Your app must bind to `0.0.0.0` (all interfaces) on your allocation port — **not** `127.0.0.1` / `localhost`. An app bound only to localhost is not reachable from outside the container, and the proxy will fail to connect. Most frameworks accept a host argument (e.g. `app.run(host="0.0.0.0", port=...)` in Flask, or `HOST=0.0.0.0` for many Node.js apps).
</Warning>

***

## Why You Need Cloudflare

Daki uses **shared IP addresses** across its nodes, and your app listens on a non-standard **port** (the allocation assigned to your server). Two problems follow:

* Visitors expect to reach `https://yourdomain.com` on the standard HTTPS port (443) — not `yourdomain.com:4129`.
* You want a valid HTTPS certificate without managing one yourself.

Putting Cloudflare in front of your server solves both. Cloudflare acts as a **reverse proxy**: browsers connect to Cloudflare over HTTPS on port 443, and Cloudflare forwards the request to your server on your custom port. You get free SSL and a clean URL, and your server's real IP stays hidden.

<Note>
  This method works for **HTTP/HTTPS web apps** (websites, APIs, dashboards, webhooks). It does **not** work for raw TCP/UDP services such as most game servers — Cloudflare's standard proxy only handles HTTP(S). For non-HTTP services, use the [Cloudflare Tunnel](#advanced-alternative-cloudflare-tunnel-cloudflared) method below or contact [support](./support).
</Note>

***

## Find Your Server's IP and Port

You'll need two values from the **Network** tab in the [Portal](./portal):

| Value               | Where to find it                                                                                                                                                                                | Example        |
| :------------------ | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------- |
| **Server IP**       | The IP address your server's hostname resolves to (shown in the **Network** tab). If only a hostname is shown, you can resolve it — e.g. `ping eu-node1.daki.cc` — or ask [support](./support). | `203.0.113.10` |
| **Allocation port** | The port shown next to your hostname in the **Network** tab. Your app must listen on this port.                                                                                                 | `4129`         |

We'll use these example values throughout. **Replace them with your own** wherever they appear. For more on allocations, see [Network & Allocations](./network-allocations).

***

## Part 1: Cloudflare Setup

<Steps>
  <Step title="Add your domain to Cloudflare">
    1. Create a free account at [dash.cloudflare.com](https://dash.cloudflare.com/sign-up) (or log in).
    2. Click **Add a site** and enter your domain (e.g. `yourdomain.com`).
    3. Select the **Free** plan when prompted.
    4. Cloudflare will give you **two nameservers** (e.g. `xxx.ns.cloudflare.com`).
    5. Log in to your **domain registrar**, find the nameserver / DNS settings for your domain, and replace the existing nameservers with the two provided by Cloudflare.

    <Note>
      Nameserver changes can take anywhere from a few minutes to 24 hours to propagate. Cloudflare emails you once your domain is active. You can continue with the next steps in the meantime.
    </Note>
  </Step>

  <Step title="Create a DNS record">
    In the Cloudflare dashboard for your domain, go to **DNS → Records** and click **Add record**:

    | Field            | Value                                                   |
    | :--------------- | :------------------------------------------------------ |
    | **Type**         | `A`                                                     |
    | **Name**         | `@` (this represents the root domain, `yourdomain.com`) |
    | **IPv4 address** | Your server IP (example: `203.0.113.10`)                |
    | **Proxy status** | **Proxied** (orange cloud ☁️)                           |

    Click **Save**.

    <Tip>
      The **orange cloud (Proxied)** status is essential — it routes traffic through Cloudflare so SSL and the port rewrite in Part 2 can work. A grey cloud (DNS only) would bypass Cloudflare entirely and expose your server IP. To also serve `www.yourdomain.com`, add a second proxied record with the name `www`.
    </Tip>
  </Step>

  <Step title="Configure SSL/TLS">
    1. Go to **SSL/TLS → Overview**.
    2. Set the encryption mode to **Flexible**.

    **Flexible** means traffic between the visitor and Cloudflare is encrypted (HTTPS), while Cloudflare connects to your app over plain HTTP on your custom port. This is the right choice when your app serves plain HTTP — which most apps on a custom port do.

    <Note>
      If your app terminates its own TLS (i.e. it serves **HTTPS** directly on its port), choose **Full** instead. Be aware that **Flexible** does not encrypt the connection between Cloudflare and your server; for sensitive traffic, prefer **Full** with HTTPS on your app.
    </Note>
  </Step>
</Steps>

***

## Part 2: Forward Traffic to Your App's Port

Because your app runs on a custom port (e.g. `4129`) rather than the standard 80/443, you need to tell Cloudflare which port to use when it connects to your server. An **Origin Rule** does exactly that — it rewrites the destination port for incoming requests.

<Steps>
  <Step title="Create an Origin Rule">
    1. In the Cloudflare dashboard, go to **Rules → Origin Rules**.
    2. Click **Create Origin Rule**.
    3. Configure the rule:

    | Setting                          | Value                                                |
    | :------------------------------- | :--------------------------------------------------- |
    | **When incoming requests match** | Hostname **equals** `yourdomain.com`                 |
    | **Then... Rewrite to**           | **Destination Port** → `4129` (your allocation port) |

    4. Click **Save** (or **Deploy**).
  </Step>
</Steps>

<Check>
  Once DNS has propagated, your app is now reachable at **`https://yourdomain.com`** — Cloudflare serves it over HTTPS and forwards traffic to your server on port `4129`.
</Check>

***

## Verify It Works

1. Make sure your server is **running** and your app is listening on the allocation port (check the **Console** tab).
2. Open `https://yourdomain.com` in your browser.
3. You should see your app load over HTTPS with a valid padlock.

If it doesn't load right away, give DNS a little more time to propagate, then see [Common Issues](#common-issues) below.

***

## Advanced Alternative: Cloudflare Tunnel (cloudflared)

Instead of opening a public port and pointing an A record at your server, you can run **`cloudflared`** — Cloudflare's tunnel client — alongside your app. It opens an **outbound** connection to Cloudflare and forwards traffic to your app locally, so your server's IP and port stay hidden and you need neither a DNS A record nor an Origin Rule.

<Note>
  **How the Daki environment shapes this.** The Startup tab gives you two commands — an [**install command**](./startup-settings) (runs once on setup/reinstall) and a **startup command** (runs on every boot) — and you can open a shell by temporarily setting the startup command to `bash`. But your server is a **container, not a full Linux VM**: there is no `apt`, you can't register a system service, and a tunnel you start by hand in the console dies when the server restarts. The reliable approach is therefore to make the tunnel **part of your app's own startup** — download the `cloudflared` binary at <Tooltip tip="The time budget a Free Tier server runs for; extend it by watching ads.">runtime</Tooltip> and launch it as a child process next to your app, controlled by an environment variable.
</Note>

<Steps>
  <Step title="Create the tunnel">
    1. In the Cloudflare **Zero Trust** dashboard, go to **Networks → Tunnels** and click **Create a tunnel**, then choose **Cloudflared**.
    2. Give it a name and **copy the tunnel token** that Cloudflare displays (the long string after `--token` in its install command).
    3. Add a **Public Hostname** to the tunnel:
       * **Domain:** `yourdomain.com`
       * **Service:** `http://localhost:4129` (your app's local port)
  </Step>

  <Step title="Store the token in .env">
    Add the token to your server's `.env` file (see [Environment Variables](./environment-variables)) so it never ends up in your source code:

    ```
    CLOUDFLARE_TUNNEL_TOKEN=your_tunnel_token_here
    ```
  </Step>

  <Step title="Start the tunnel from your code">
    Add a small launcher that downloads `cloudflared` on first run and starts it next to your app, then set that launcher as your **startup command**. It only starts the tunnel when `CLOUDFLARE_TUNNEL_TOKEN` is set, so the same code runs fine locally without one.

    <Tabs>
      <Tab title="Node.js (start.js)">
        Save as `start.js`, then set your startup command to `node start.js`:

        ```js theme={null}
        // 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"]);
        ```
      </Tab>

      <Tab title="Python / other (start.sh)">
        Save as `start.sh`, then set your startup command to `bash start.sh`:

        ```bash theme={null}
        #!/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
        ```
      </Tab>
    </Tabs>

    <Tip>
      To avoid re-downloading on every boot, you can move the download into your [install command](./startup-settings) and keep only the launch logic in the startup command. The examples above are already safe to run every boot — they skip the download once the binary exists.
    </Tip>
  </Step>
</Steps>

**Trade-offs:**

|                                 | Cloudflare Proxy (Parts 1–2) | Cloudflare Tunnel (cloudflared)                 |
| :------------------------------ | :--------------------------- | :---------------------------------------------- |
| Setup difficulty                | Easier (dashboard only)      | More advanced (bundled into your app's startup) |
| Exposes server IP/port          | Hidden behind proxy          | Fully hidden (outbound only)                    |
| Needs public port / Origin Rule | Yes                          | No                                              |
| Uses server resources           | None on your server          | Runs an extra process                           |

***

## Common Issues

<AccordionGroup>
  <Accordion title="The site doesn't load at all">
    DNS may still be propagating. Nameserver changes can take up to 24 hours (usually much less). Confirm in Cloudflare that your domain status is **Active** and that the **A record** exists and is **Proxied** (orange cloud). You can check propagation with a tool like [dnschecker.org](https://dnschecker.org).
  </Accordion>

  <Accordion title="Error 522 / 523 — Connection timed out / Origin unreachable">
    Cloudflare can't reach your app. Check, in order:

    * Your **server is running** and the app has fully started (check the **Console** tab).
    * The **Origin Rule** rewrites the port to your exact **allocation port**.
    * The **A record IP** matches your server's IP.
    * Your app binds to **`0.0.0.0`**, not `127.0.0.1`/`localhost`.
  </Accordion>

  <Accordion title="Too many redirects / ERR_TOO_MANY_REDIRECTS">
    This is usually an SSL mode mismatch. If your app forces HTTPS but Cloudflare connects to it over HTTP, you get a redirect loop. Either set your app to serve plain HTTP (and use **Flexible**), or have your app serve HTTPS and switch Cloudflare to **Full** under **SSL/TLS → Overview**.
  </Accordion>

  <Accordion title="It works on http:// but not https://">
    Make sure your DNS record is **Proxied** (orange cloud) and that **SSL/TLS** mode is set (Flexible or Full). With a grey cloud (DNS only), Cloudflare's SSL is bypassed.
  </Accordion>

  <Accordion title="My game server / non-HTTP service won't work">
    Cloudflare's standard proxy only handles HTTP/HTTPS. For raw TCP/UDP services like most game servers, this method won't apply. Connect to your server using its hostname and port directly (see [Network & Allocations](./network-allocations)), or contact [support](./support) about other options.
  </Accordion>

  <Accordion title="Do I have to use Cloudflare?">
    No — Cloudflare is the simplest free option, but any reverse proxy that can forward to a custom port works (e.g. a tunnel via `cloudflared`, or your own Nginx/Caddy in front of the app). Cloudflare is recommended because it provides free SSL, hides your server IP, and requires no extra software on your server for the proxy method.
  </Accordion>
</AccordionGroup>

***

## Next Steps

* [Network & Allocations](./network-allocations) — Find your hostname and port, and add more allocations.
* [App & Bot Hosting Plans](./paid-plans) — Upgrade to a paid plan to get a public port.
* [Environment Variables](./environment-variables) — Read your port from an env var in code.
* [Getting Help](./support) — Contact support if you get stuck.
