# Domains, routing and default certificates

URL: https://scalws.com/docs/guide/domains/
Source: `docs/guide/domains.md` in the ScalWS repository

This page explains how ScalWS picks the application for a request from its host name, how
it picks the TLS certificate for a connection from the SNI name, and how the two relate.

Two separate lookups are involved:

* **Routing** uses the request's host (the `Host` header or the request target's
  authority) and the [`domains`](https://scalws.com/docs/reference/applications/#tenants-.applications-.domains)
  of all applications. It decides which application answers.
* **Certificate selection** uses the SNI name of the TLS handshake and the
  [`server_names`](https://scalws.com/docs/reference/server-listeners/#server.listeners-.tls.certificates-.server_names)
  of the listener's certificates (plus ACME certificates). It decides which certificate the
  client sees.

A certificate only has to be valid for the name; it does not choose the application.

## Configuring domains

```yaml
tenants:
  - name: shop
    applications:
      - name: storefront
        domains: [shop.example.com, www.shop.example.com]
        root: /srv/www/shop
        runtime: {type: static}
      - name: customers
        domains: ["*.customers.example.com"]
        runtime: {type: proxy, upstream: "http://127.0.0.1:8080"}
```

Every application needs at least one domain (`at least one domain is required`). Domains
are global: routing does not depend on the listener, so an application answers on every
listener (plain HTTP and TLS) for its domains. Use
[`https_redirect`](https://scalws.com/docs/reference/applications/#tenants-.applications-.https_redirect) to
send plain-HTTP requests to HTTPS.

### Normalisation

Configured names are normalised when the configuration is loaded:

* lower-cased, and a trailing dot removed (`Example.COM.` is `example.com`);
* each label is 1 to 63 characters of `a-z`, `0-9` and `-`, and does not start or end with
  `-`; the whole name is at most 253 characters;
* internationalised names must be written in punycode (`xn--...`);
* an IPv4 address such as `127.0.0.1` is a valid domain (the packaged configuration serves
  `localhost` and `127.0.0.1`); IPv6 addresses are not.

An invalid name fails the check with
`invalid domain "...": labels must be 1-63 chars of a-z, 0-9 and '-' (use punycode for IDNs)`.

Request hosts are normalised the same way before the lookup:

* the port is removed (`example.com:8443` is `example.com`), so routing does not depend on
  the port;
* lower-cased, a trailing dot removed;
* an IPv6 literal is written in its canonical form (`[0::1]` and `[::0:1]` are both `[::1]`);
* a host with an empty label, a character other than letters, digits, `-` and `_`, a label
  longer than 63 characters, an invalid port, or more than 255 characters in all is refused
  with `400`.

### Exact names and wildcards

* An exact name matches only itself.
* `*.example.com` covers exactly **one** extra label: it matches `a.example.com`, but not
  `example.com` and not `a.b.example.com`. List `example.com` separately if it should be
  served too.
* The suffix of a wildcard must have at least two labels: `*.com` fails with
  `wildcard "*.com" must cover a multi-label suffix`. The `*` may only be the whole first
  label (`foo.*.com` is invalid).
* `**.example.com` covers **one or more** extra labels: it matches `a.example.com` and
  `a.b.example.com`, never `example.com` itself. Its suffix needs two labels as well
  (`**.com` is invalid) and it cannot be combined with `acme: true` (it would need DNS-01).
* The lookup order is: the exact name, `*.` (one label), `**.` (the longest matching suffix
  first, so `**.b.example.com` wins over `**.example.com`), then the catch-all `"*"`.
* An exact name wins over a wildcard, whatever the order in the configuration:

  ```yaml
  domains: ["*.example.com"]        # application A
  # ...
  domains: [api.example.com]        # application B: api.example.com goes here
  ```

### Duplicate domains

A normalised name may be used by one application only, across all tenants and all
[site files](https://scalws.com/docs/sites/). A second use fails the check, for example:

```text
tenants[1].applications[0].domains[0]: domain "example.com" is already used by blog/blog
```

`Example.com`, `example.com` and `example.com.` are the same name. `*.example.com` and
`a.example.com` are different names and may belong to different applications (the exact
name wins).

### Unknown hosts

A request whose host matches no domain gets `404 Not Found`. It is counted with tenant and
application `_unmatched` in the request metrics and the access log. There is no catch-all
application; to answer such requests differently, add the names you expect (for example the
server's IP address) to an application's `domains`.

A request without a host, with more than one `Host` header, or with a `Host` that differs
from the authority of the request target gets `400`. The full order of checks is in
[How a request is processed](https://scalws.com/docs/guide/request-processing/).

## Site files and `www`

A [site file](https://scalws.com/docs/sites/) in `sites.d/` describes one site with a main
[`domain`](https://scalws.com/docs/reference/site-file/#domain), optional
[`aliases`](https://scalws.com/docs/reference/site-file/#aliases) and a [`www`](https://scalws.com/docs/reference/site-file/#www)
setting. It is expanded into applications with the same rules as above:

| `www` | Result |
|---|---|
| `none` (the default in the file) | `www.<domain>` is not served (unless another application lists it). |
| `alias` | `www.<domain>` is added to the site's `domains`. |
| `redirect` | A second application `<name>-www` serves `www.<domain>` with a `301` redirect to `<domain>`, path and query kept. The target is `https://` when the site uses ACME or the server has a TLS listener, `http://` otherwise. |

`scalwsctl site add` uses `--www redirect` by default for a domain with one dot such as
`example.com`, otherwise `none`. A `www.` name that is already another site's domain or
alias is a duplicate and fails the check.

## Certificates and SNI

Each TLS listener has its own certificate table, built from its
[`certificates`](https://scalws.com/docs/reference/server-listeners/#server.listeners-.tls.certificates)
and, with [`acme: true`](https://scalws.com/docs/reference/server-listeners/#server.listeners-.tls.acme), the
ACME certificates of the applications.

```yaml
server:
  listeners:
    - name: https
      address: "0.0.0.0:443"
      tls:
        certificates:
          - cert: /etc/scalws/tls/example.com/fullchain.pem
            key: /etc/scalws/tls/example.com/privkey.pem
            server_names: [example.com, "*.example.com"]
            default: true
          - cert: /etc/scalws/tls/example.net/fullchain.pem
            key: /etc/scalws/tls/example.net/privkey.pem
            server_names: [example.net]
```

For each new TLS connection the certificate is chosen from the SNI name:

1. an exact entry in `server_names` (or an ACME certificate for that name);
2. otherwise a wildcard entry, with the same one-label rule as for domains;
3. otherwise the listener's [`default`](https://scalws.com/docs/reference/server-listeners/#server.listeners-.tls.certificates-.default)
   certificate;
4. otherwise the handshake fails. It is counted in `scalws_rejections_total` with
   `reason="tls_handshake"`.

A client that sends no SNI (for example a request to an IP address) gets the `default`
certificate, or a failed handshake when there is none.

Rules checked when the configuration is loaded:

* every name in `server_names` is normalised like a domain and must be covered by the
  certificate (`certificate is not valid for server name "..."`);
* a name may be assigned to one certificate per listener
  (`server name "..." is assigned to more than one certificate`);
* at most one certificate per listener may be `default`
  (`at most one certificate may be marked default`);
* a TLS listener needs at least one certificate or `acme: true`
  (`a TLS listener needs at least one certificate (or acme: true)`);
* when a configured certificate and an ACME certificate cover the same name, the configured
  one wins. ACME certificates are exact names only: with
  [`acme: true`](https://scalws.com/docs/reference/applications/#tenants-.applications-.acme) on an
  application, wildcard domains, IP addresses and `localhost` are refused.

Until an application has its first ACME certificate, handshakes for its names get the
listener's `default` certificate, or fail without one. Marking a certificate `default`
therefore keeps HTTPS answering (with a certificate warning in browsers) while orders are
pending. More in [tls.md](https://scalws.com/docs/guide/tls/#how-a-certificate-is-chosen-sni).

## Strict SNI and `421`

Browsers may reuse one HTTP/2 connection for several host names when the certificate
covers them all (connection coalescing). The request's host then differs from the SNI name
of the connection, and ScalWS routes it by the host as usual.

With [`strict_sni: true`](https://scalws.com/docs/reference/server-listeners/#server.listeners-.strict_sni)
on a TLS listener, such a request gets `421 Misdirected Request`, and the client retries on
a new connection whose SNI matches. The comparison uses the normalised host: for an exact
domain the configured name, otherwise the request host. Requests on connections without SNI
are not affected, and a host that matches no application still gets `404` (the host lookup
comes first). Refusals are counted in `scalws_security_blocked_total` with
`reason="misdirected"`.

`strict_sni` on a listener without TLS fails the check with `only applies to TLS listeners`.

## See also

* [request-processing.md](https://scalws.com/docs/guide/request-processing/): where the host lookup and strict SNI sit
  in the order of checks.
* [tls.md](https://scalws.com/docs/guide/tls/): certificates, ACME, TLS versions and HTTP/3.
* [../sites.md](https://scalws.com/docs/sites/): site files.
* [applications.md](https://scalws.com/docs/guide/applications/#domains): applications and their domains.
