Users' guide
Sites: adding a domain
One short file per site, scalwsctl site add|list|remove, and moving sites from nginx or
Apache with scalwsctl migrate.
Each site can live in its own short file under /etc/scalws/sites.d/, the way nginx uses
sites-enabled/ (ADR-0048). The main configuration includes them with one line
(include):
# /etc/scalws/scalws.yaml
include: sites.d/*.yaml
server:
listeners: ...New installations of the package have this line and the directory. On an older installation add the line yourself and create the directory:
sudo install -d -m 0750 -o root -g scalws /etc/scalws/sites.dThe quick way: scalwsctl site add
# Static site in /srv/www/example.com (created with a placeholder page if missing);
# www.example.com redirects to example.com
sudo scalwsctl site add example.com
sudo scalwsctl site add example.com --root /var/www/example # another directory
# --php, --node and --python start managed processes, which need scalwsd.service;
# under scalws-controller (the default unit) use --php-fastcgi or --proxy instead.
# WordPress or another PHP application
sudo scalwsctl site add blog.example.com --root /srv/www/blog --php
sudo scalwsctl site add blog.example.com --root /srv/www/blog --php-fastcgi unix:/run/php/php8.3-fpm.sock
# Node.js (the app listens on $PORT), Python ASGI, or any upstream
sudo scalwsctl site add app.example.com --root /srv/app --node "node server.js"
sudo scalwsctl site add api.example.com --root /srv/api --python main:app
sudo scalwsctl site add shop.example.com --proxy http://127.0.0.1:3000
# A domain that only redirects
sudo scalwsctl site add old-name.example --redirect https://example.com
sudo scalwsctl site list
sudo scalwsctl site remove old-name.exampleWithout --root, a static or PHP site’s files go to /srv/www/<domain>. A missing
document root is created (mode 0755); a static site gets a placeholder index.html that
names the directory, so the domain answers right away. If the check or the reload fails,
both are taken back.
site add refuses a domain that is already served (by any site file or by scalws.yaml
itself), writes sites.d/<domain>.yaml, checks the whole configuration as a reload would
(certificates, document roots, runtimes) and removes the file again if the check fails.
Only then does it reload the server: through scalws-controller when it runs, so every
instance moves to the new configuration. --dry-run prints the file without writing it,
--no-reload writes and checks but leaves the reload to you.
Options: --www none|alias|redirect (default: redirect for a name with one dot such as
example.com, otherwise none), --alias other.example (repeatable), --tls auto|manual,
--tenant NAME (put several sites in one tenant to share its limits), --htaccess,
--https-redirect, --php-fastcgi unix:/run/php/php8.3-fpm.sock (an existing PHP-FPM pool;
under scalws-controller this is the way to run PHP, which refuses managed pools).
site remove renames the file to a hidden backup next to it
(.example.com.yaml.removed-<time>), checks and reloads.
Moving from nginx or Apache: scalwsctl migrate
sudo scalwsctl migrate nginx --dry-run # /etc/nginx/nginx.conf and its includes
sudo scalwsctl migrate nginx
sudo scalwsctl migrate apache # /etc/apache2/apache2.conf or /etc/httpd/conf/httpd.conf
sudo scalwsctl migrate apache /path/to/httpd.conf --skip-existingThe source configuration is only read. Each server block (nginx) or <VirtualHost>
(Apache) becomes one site file:
server_name/ServerName+ServerAlias→domainandaliases;www.<domain>as an alias →www: alias; a separatewww.host that redirects to the domain →www: redirect.- A port-80 host that only redirects to HTTPS (including certbot’s
if ($host = …)andRewriteRule ^ https://%{SERVER_NAME}…) and the HTTPS host of the same names → one site withhttps_redirect: true. HTTPS hosts gettls: autowhenserver.acmeis enabled; otherwise the certificate file is listed as a TODO. root/DocumentRoot,index/DirectoryIndex;AllowOverrideother thanNone→htaccess: true.- PHP:
fastcgi_pass/SetHandler "proxy:unix:…|fcgi://…"/ProxyPassMatch … fcgi://→php: <that PHP-FPM pool>;try_files … /index.php?…→front_controller. proxy_pass/ProxyPass(upstream groups resolved to their first server) →proxy; under another path → a route.proxy_set_header Host $host/ProxyPreserveHost Ondecidepreserve_host, as the source sent it.return 301 …,rewrite … permanent,Redirect→redirect.
Everything else (add_header, auth_basic, rewrite rules, regular-expression locations,
…) is listed per site and written into the file as # TODO: lines with the source file and
line. Rewrite rules can usually move to an .htaccess file (htaccess: true).
Domains ScalWS already serves are never written twice. For each one migrate asks:
shop.example.com is already served (shop.example.com by shop-example-com/shop-example-com (/etc/scalws/sites.d/shop.example.com.yaml)). Overwrite? [y]es / [n]o / [a]ll / n[o]ne:Only a domain’s own site file can be replaced (it is kept as .<name>.replaced-<time>);
a domain defined in scalws.yaml or in another site is skipped with a note. Without a
terminal, --overwrite or --skip-existing decides; otherwise nothing is written. All
files of a run are checked together like a reload; if the check or the reload fails,
every file of the run is removed and replaced files are restored. --dry-run prints the
plan and the files, --yes skips the final question, --no-reload leaves the reload to
you.
Under scalws-controller the migrated PHP sites keep using their existing PHP-FPM pools, and
proxied applications keep running as they did; stop nginx or Apache only after
scalwsctl site list shows every site, then let ScalWS take ports 80 and 443.
The file
# /etc/scalws/sites.d/example.com.yaml
domain: example.com
www: redirect # none (default) | alias | redirect
root: /srv/www/example.com
php: true # or one of the runtimes below; static when none is givenEvery field of a site file, with its syntax, default and reload behaviour: reference. Points to know:
domainis the main domain;aliasesare further domains of the same site.www: redirectanswerswww.<domain>with a 301 todomain, path and query kept.tls: autotakes the certificate from Let’s Encrypt (needsserver.acme).- The runtime is one of the shorthands
static(the default),php(for example{fastcgi: "unix:/run/php/php8.3-fpm.sock"}or{fpm: {workers: {min: 2, max: 16}}, front_controller: /index.php}),node(for example{command: [...], workers: {min: 2, max: 4}}),python(for example{mode: wsgi, app: "app:app", virtualenv: /srv/app/.venv}),proxy({upstream: ..., preserve_host: false, ...}) andredirect({to: ..., status: 302, keep_path: false}), or the fullruntimesyntax ofscalws.yaml. https_redirect: truestill answers Let’s Encrypt challenges over plain HTTP.profile,routes,cache,securityandhtaccesswork as for an application inscalws.yaml(e.g.profile: wordpress).- The
tenantname is derived from the domain by default (shop.example.combecomesshop-example-com).user(the tenant’s Unix account) andresourcesare tenant settings: set them in one file per tenant.
Only one runtime per site. Unknown fields are errors, and every message names the file:
/etc/scalws/sites.d/shop.example.com.yaml: runtime.upstream: <what is wrong with the upstream>A file may instead contain a tenants: list in the full syntax of scalws.yaml, for a
tenant with several applications.
Rules
- Files are read in name order; names starting with
.and files not matching the pattern are ignored (editor backups,site removebackups). - A domain may be served once. Two sites with the same domain, or a
www.name that is another site’s alias, fail the check. - Site files and
sites.dmust belong to root and must not be writable by group or others: whoever can write there can serve any domain from any directory the server can read.scalwsctl site addwrites them as root, mode 0640, groupscalws. scalwsctl checkchecks the main file together with its site files. Changes take effect on reload (scalwsctl reload,scalws-controller reload, or the console).- scalws-controller keeps each generation with the sites expanded into it, so a rollback restores exactly the sites of that generation even after site files changed.