Internals: decision records
ADR-0048: Site files and the `redirect` runtime
Status: accepted, 2026-10-12
Context
Every site lived in scalws.yaml under tenants: → applications:, three levels deep,
next to listeners, limits and the rest of the server. Adding a domain meant editing that
file correctly; a www. redirect needed a second application with its own root and an
.htaccess. Operators coming from nginx or Apache expect one file per site
(sites-enabled/, vhosts.d/), and a tool that adds a site should not have to rewrite
the whole configuration.
Decision
include:(a path or a list) in the main file names site files: a file, or a directory and a file pattern with one*(sites.d/*.yaml), relative to the main file. Matches are read in name order; hidden files are skipped; the main file alone may include. A missing directory is an error, an empty one is not.- A site file holds one site in a short form (
domain,aliases,www: none|alias|redirect,root,tls: auto|manual, one runtime shorthand:static,php,node,python,proxy,redirector the fullruntime, plusprofile,routes,cache,security,htaccess, and the tenant settingstenant,user,resources), or atenants:list in the full syntax. Tenant and application names default to the domain (shop.example.com→shop-example-com). Sites of one tenant share it; its settings may come from one place only.tlsdefaults toautowhenserver.acmeis enabled. - The loader expands site files into ordinary tenants and applications before profile
resolution and validation: the server, the optimizer and the controller see one
Config. Messages about a site name its file (sites.d/a.yaml: runtime.upstream: …); a domain served twice is the existing duplicate-domain error. - scalws-controller stores a configuration with
includeexpanded (one self-contained document) as the generation, so a generation never changes when a site file does. A configuration withoutincludeis still stored byte for byte. - On Unix a site file and its directory must belong to root (or the main file’s owner) and must not be writable by group or others: writing a site file means serving any domain from any directory the server can read.
redirectruntime (to,status301/302/303/307/308,keep_path): the answer carriesLocation: <to><request path and query>; the target is an origin, so the request path cannot change the host.www: redirectuses it.https_redirect: trueon an application (and in site files) answers plain-HTTP requests with a 301 tohttps://<routed host><path and query>after the routing and access checks; ACME HTTP-01 challenges are answered before it. One boolean test per request on the routed application.scalwsctl site add|list|removewrites<domain>.yaml(0640, the main file’s group), refuses a domain that is already served, runs the reload check and removes the file again when it fails, then reloads (through scalws-controller when it runs).removekeeps the file as a hidden backup next to it.scalwsctl migrate nginx|apacheturns the virtual hosts of an existing configuration into site files (read-only on the source); conflicts with served domains are asked about per domain, only a domain’s own site file is ever replaced, and a run that fails the check or the reload is undone as a whole.- The console validates drafts with the site files of the configuration’s directory and
stages the expanded document for
scalwsd --check; its statistics include them.
Consequences
- The package installs
/etc/scalws/sites.d(root:scalws, 0750) and a defaultscalws.yamlwithinclude: sites.d/*.yaml; existing configurations are unchanged (noreplace) and keep working withoutinclude. - Site files are not edited by the console’s forms yet; the console shows their sites and validates against them.
- Data-plane changes: the
redirecthandler (a fixed response with aLocationheader) and thehttps_redirecttest.