<!-- MIT License. Copyright (c) 2026 Decentralized Science Labs LLC. -->
<!-- summary: The main host and its aliases, the 301 redirects, the Let's Encrypt certificate and its renewals, adding or removing a name, unknown names and HSTS. -->
# Domains, redirects and certificates

A COLLAB server answers for one main host and any number of other names, its aliases, which
redirect to it. This page is for the operator: what answers each request, the one certificate that
covers the names, how it renews, how to add, remove or swap names later, what happens with names
nobody configured, and what HSTS commits you to.

## The main host and its aliases

By default the site is **collab.genesisl1.com**, and **dao.genesisl1.com**, **collab.descifi.org**
and **dao.descifi.org** are its aliases. The installer's `--main`, `--alias` and `--no-aliases`
choose others ([Before you install](before-you-install.md#choosing-the-main-host)), and it
remembers them for later runs.

The main host is the site's one origin: its canonical links, sitemap, preview images and admin page
all use it, and visitors' settings and wallet connections belong to it. The aliases only redirect.

The documentation is the exception. Its pages under `/docs/` ship prebuilt in the release, and
their canonical link and `og:url` name `https://collab.genesisl1.com` on every server, because the
prerender job rewrites only the app's entry pages. With another main host, search engines credit
those pages to collab.genesisl1.com.

## What answers each request

| Request | Answer |
|---|---|
| `http://` any of the names | `301` to `https://<main host>`, same path and query |
| `http://` any of the names, `/.well-known/acme-challenge/…` | the Let's Encrypt challenge file, for issuing and renewing the certificate |
| `https://` the main host | the site |
| `https://` an alias the certificate covers | `301` to `https://<main host>`, same path and query |
| `https://` an alias the certificate does not cover yet | no COLLAB server names it, so it is answered as an unknown name: see [Unknown names](#unknown-names-on-ports-80-and-443) |

Every redirect goes to the main host fixed in nginx's configuration, never to the host a request
names. Until the main host has a certificate, port 80 answers every other request with `503` and a
short notice instead: "COLLAB is being set up on this server, HTTPS first. Please come back in a few
minutes."

## The certificate

The server has one Let's Encrypt certificate, named `collab`, for every name whose DNS points at the
server when the installer runs. certbot keeps it in `/etc/letsencrypt/live/collab/`. It is issued
with `certbot certonly --webroot`: Let's Encrypt fetches a challenge file over HTTP, on port 80, from
`/var/www/collab/acme`, for each name.

On each run, the installer asks certbot for a certificate when the main name points at the server
and the certificate is missing, does not cover every name that points here, or ends in less than
30 days. Otherwise it keeps it: "The certificate covers … and is valid for 30 days and more; certbot
renews it". A name that does not point at the server is left out, with a warning and, in the
summary, "Not covered:" and what to do.

Status shows each name with its expiry and days left, and the names without a certificate. See
[The admin page](admin.md#https-certificate).

### Renewals and the reload hook

certbot renews the certificate by itself, from its own timer (`certbot.timer` of the Debian and
Ubuntu package, or the snap's `snap.certbot.renew.timer`), about 30 days before it ends. The
installer enables that timer and gives certbot a deploy hook, `collab renewed`, which runs after
each renewal:
1. `nginx -t`, then a reload of nginx, so the new certificate is served;
2. a new `shared/status/certificates.json`, which Status reads.

To look at certbot's side:

```bash
sudo certbot certificates          # the certificate, its names and its expiry
systemctl list-timers '*certbot*'  # when certbot next checks (the package's or the snap's timer)
sudo journalctl -u certbot         # what it did (the Debian and Ubuntu package)
```

A renewal that keeps failing shows in Status as the days left run down: amber below 21 days, red
below 8. [Troubleshooting](troubleshooting.md#certbot-cannot-get-or-renew-the-certificate) lists the
usual causes.

## Adding a name later

1. Point the name's DNS at the server, and check it with `dig` (see
   [Before you install](before-you-install.md#checking-with-dig)).
2. Run the installer again with the full list of aliases. `--alias` replaces the list, so name every
   alias you keep:

   ```bash
   sudo /var/www/collab/current/server/install.sh \
     --alias dao.genesisl1.com --alias collab.descifi.org --alias dao.descifi.org --alias new.example.org
   ```
3. The installer checks that no other site on this nginx claims the name, checks its DNS, asks
   certbot for a certificate that covers it too, and adds it to the redirect servers. The summary
   lists it under "Redirected:".

A name that is already an alias but was left without a certificate, because its DNS was not ready
at the install, needs no option: once its DNS points here, run the same command again.

## Removing a name

Run the installer again with the aliases that stay (or `--no-aliases`). nginx stops answering for the
removed name at once.

The certificate keeps naming it until a new one is issued, and from then on certbot's renewals
fail, because COLLAB no longer answers Let's Encrypt's challenge for that name. Renewals start 30
days before the certificate ends, and the installer asks for a new certificate in the same window:
when Status shows fewer than 30 days left, run the installer once more, and it asks certbot for a
certificate of the names that remain.

## Changing the main host

The installer remembers the aliases, so `--main` alone would keep the old aliases and drop the old
main name. Name every other name with `--alias`, the old main host included:

```bash
sudo /var/www/collab/current/server/install.sh --main dao.genesisl1.com \
  --alias collab.genesisl1.com --alias collab.descifi.org --alias dao.descifi.org
```

What follows:
- `config.js` gets the new site address, and the canonical links, the sitemap, the `Sitemap:` line
  of `robots.txt` and the preview image addresses follow at the next prerender run, which the
  installer requests; nothing needs editing by hand. The documentation's pages keep theirs (see
  [The main host and its aliases](#the-main-host-and-its-aliases));
- the old main host answers `301` to the new one;
- the admin page works on the new main host only: sign in again there;
- visitors' settings and wallet connections belong to each origin, so visitors start fresh on the
  new one.

## Unknown names on ports 80 and 443

A request for a name nobody configured, or for the server's IP address, goes, port by port, to the
site marked `default_server` on that port, or else to the first site nginx loaded that listens on
it. COLLAB marks none, and changes nothing of the other sites:
- **Port 80.** Debian's and Ubuntu's default site is marked `default_server` there, so it keeps
  answering those requests. Only if no site listens on port 80 before COLLAB does COLLAB's port-80
  server answer them: Let's Encrypt's challenges, and otherwise a redirect to the site (before there
  is a certificate, the "being set up" notice).
- **Port 443.** The default site does not listen there. If another site already does, the first of
  them (or the one marked `default_server`) keeps answering those requests. If none does, COLLAB's
  site is the first on port 443 and answers every HTTPS request that reaches the server. A browser
  asking for another name then gets COLLAB's certificate, warns that it does not match, and shows
  COLLAB's site if the visitor goes on.

To refuse such HTTPS requests instead, add a default server of your own for port 443, for example
`/etc/nginx/sites-enabled/00-tls-default.conf`:

```nginx
server {
    listen 443 ssl default_server;
    listen [::]:443 ssl default_server;   # only if nginx listens on IPv6
    ssl_reject_handshake on;              # nginx 1.19.4 or later: Debian 12 and Ubuntu 24.04 have it
}
```

Then `sudo nginx -t && sudo systemctl reload nginx`. COLLAB keeps answering its own names either
way; this file is yours, and the installer never touches it.

Before nginx 1.25.1, Debian 12's and Ubuntu 24.04's versions included, add `http2` to both
`listen` lines: `listen 443 ssl http2 default_server;`. HTTP/2 there belongs to the port, and the
next installer run turns it off for COLLAB when another site on port 443 lacks it. From 1.25.1 on,
leave `http2` out of `listen`.

## HSTS

The main host sends `Strict-Transport-Security: max-age=63072000; includeSubDomains` with every
HTTPS response, and the aliases' HTTPS redirect sends `max-age=63072000`. That is two years:
- a browser that has seen the header uses HTTPS only for that name for two years, and does not let
  its user click through a certificate error;
- `includeSubDomains` covers names under the main host, such as `x.collab.genesisl1.com`, not
  `genesisl1.com` or its other names;
- the header asks for no preload list.

So keep HTTPS working on these names for good. Do not test with `--self-signed` on a public name
browsers have visited, and do not move one of these names to a site without HTTPS.

## Behind Cloudflare

If the names are on Cloudflare, keep them on **DNS only** (the grey cloud) at least until the
certificate is issued. With the proxy on afterwards, set SSL/TLS to **Full (strict)**. Every visitor
then reaches the server from Cloudflare's addresses, which share the admin page's sign-in limits
unless nginx's `real_ip` module is set up for them (see
[The admin page](admin.md#sign-in-limits)).

## Related

- [Before you install: server, DNS and names](before-you-install.md)
- [Installing COLLAB on a server](INSTALL.md)
- [Troubleshooting](troubleshooting.md)
- [Operator security checklist](server-security.md)
