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

# Custom domains

> Attach a domain you own to an exposed app: Cloudflare or Direct mode, TXT verification, and apex + www.

Every exposed app ships with a `<install-subdomain>.suji.fr` HTTPS URL out of the box. **Custom domains** let you put a domain you own in front of that app instead, so visitors see `app.example.com` rather than `something.suji.fr`.

Custom domains are configured **per exposed install**, from the app's page in the dashboard. One **primary** domain per install, plus an optional apex ↔ www alias.

## Your custom domain becomes your primary URL

Once your custom domain is **active**, it becomes the app's **canonical** address: the one public URL your app lives at. The original `<install-subdomain>.suji.fr` URL doesn't disappear: it **`301`-redirects** to your custom domain, preserving the path and query string. Old links, bookmarks, SEO, and webhooks pinned to the `*.suji.fr` URL keep working: they simply bounce to the canonical custom domain. This is the same **primary domain** model you'll know from other cloud platforms.

A few things follow from this:

* While your custom domain is still **pending verification**, the app keeps serving on its `*.suji.fr` URL, so nothing goes dark. Your custom domain takes over automatically the moment it verifies and goes active.
* The app's own public-address variables (the values it uses to build webhook and OAuth callback URLs) repoint to your custom domain on cutover, so those resolve to the canonical URL too.
* **Automatic failback:** if an active custom domain later stops working (for example the verification `TXT` record is removed, or a certificate fails to renew), the redirect is dropped and the app **serves on its `*.suji.fr` URL again**, so it's never fully unreachable. You're alerted to fix (or remove) the custom domain, and once it's healthy again it resumes as the primary URL.

So at any time an exposed app has exactly **one canonical public URL**: the custom domain (with the subdomain redirecting to it), or its `*.suji.fr` subdomain on its own, or it's private.

## The two modes

You choose a mode when you add the domain. Both serve your app over HTTPS; they differ in who terminates traffic and what protection sits in front of the VM.

|                   | **Cloudflare** (default)                                 | **Direct**                                          |
| ----------------- | -------------------------------------------------------- | --------------------------------------------------- |
| How you point DNS | `A` record to the VM IP, **proxied** (orange cloud)      | `A` record to the VM IP, **unproxied** (grey cloud) |
| Certificate       | Cloudflare's edge cert; SSL mode set to **Full**         | Automatic **Let's Encrypt** cert issued on the VM   |
| DDoS / WAF        | Yes, your Cloudflare account absorbs and filters traffic | **No**, traffic hits the VM directly                |
| VM IP exposure    | Hidden behind Cloudflare's edge                          | **Public**, the VM IP is reachable directly         |
| Inbound firewall  | Locked to Cloudflare's IP ranges automatically           | Open to the world on the app's port                 |
| Requirement       | A Cloudflare account (free tier is fine)                 | A saved payment method on your org                  |

If you're unsure, choose **Cloudflare**. It keeps the VM's IP behind an edge that filters abusive traffic, and you keep full control through your own Cloudflare dashboard.

## Add a custom domain

1. Open the **exposed app** in the dashboard and go to its **Domains** section.

2. Enter the domain you want to use (e.g. `app.example.com`) and pick a **mode**.

3. The dashboard shows a **`TXT` verification record**. Add it at your DNS provider:

   ```
   _suji-verify.app.example.com   TXT   "<the value shown in the dashboard>"
   ```

4. Add the **`A` record** the dashboard shows, pointing your domain at your VM's IP:

   ```
   app.example.com   A   <your VM's IP>
   ```

   For **Cloudflare** mode the `A` record must be **proxied** (orange cloud). For **Direct** mode it must be **unproxied** (grey cloud).

5. Click **Verify**. Once the `TXT` record resolves, Suji confirms ownership and finishes setup: wiring the in-VM Caddy reverse proxy, requesting a certificate (Direct mode), and locking the firewall to Cloudflare's ranges (Cloudflare mode).

DNS changes can take a few minutes to propagate; if verification doesn't pass immediately, wait and retry.

## Mode specifics

### Cloudflare mode

* In your **own** Cloudflare account, set the zone's **SSL/TLS encryption mode to `Full`** (not Flexible, not Full (strict) unless you've installed a matching origin cert). The VM serves valid HTTPS to Cloudflare, and Cloudflare serves your visitors.
* Keep the `A` record **proxied** (orange cloud) so DDoS protection and the WAF apply.
* Suji automatically restricts the VM's inbound firewall to Cloudflare's published IP ranges, so the origin only accepts traffic that came through the edge.

### Direct mode

* A **Let's Encrypt** certificate is requested and renewed automatically on the VM, with no manual cert handling.
* There is **no DDoS protection or WAF** in front of the VM, and the **VM's public IP is exposed** directly. Any traffic reaching the IP, including attack and scanning traffic, counts toward your VM's egress and is billable. You accept that risk when you choose Direct mode, which is why it requires a saved payment method.
* Use Direct mode when you can't or don't want to route through Cloudflare; otherwise prefer Cloudflare mode.

## Apex and www

You can serve both the apex (`example.com`) and the `www` host (`www.example.com`) from one install. Set one as the **primary** domain and add the other as an **alias**, and Suji redirects the alias to the primary so you don't split traffic across two hostnames.

Apex records have a quirk: many DNS providers don't allow a `CNAME` at the apex. Use an `A` record (or your provider's `ALIAS`/`ANAME` flattening) for the apex, and the dashboard will show the exact record to add.

## Removing a custom domain

Remove a custom domain from the app's **Domains** section. The `*.suji.fr` redirect is dropped, Suji tears down the proxy route and certificate, and the app returns to serving directly on its original `<install-subdomain>.suji.fr` URL, which becomes the canonical address again. Visitors going to the custom domain stop reaching the app, so you'll need to re-add and re-verify the domain to use it later. Delete the `TXT` and `A` records at your DNS provider afterwards to keep your zone tidy.

## Next

<CardGroup cols={2}>
  <Card title="Network" icon="network-wired" href="/manage-vm/network">
    The VM's public IP and tunnel hostname.
  </Card>

  <Card title="Firewall" icon="shield" href="/manage-vm/firewall">
    Inbound rules on the public IP. Cloudflare mode manages these for you.
  </Card>
</CardGroup>
