Getting started
Quick start: your first site in five minutes
From a fresh installation to a static site, a PHP (WordPress-style)
site, a Node.js application behind the server, and HTTPS with Let’s Encrypt. Every site
lives in its own short file under /etc/scalws/sites.d/, managed with
scalwsctl site add|list|remove. The full field reference is in
site files.
The examples use example.com and /srv/www/...; replace them with your domains and
directories. Commands that write to /etc/scalws run as root.
0. Before you start
You need:
- the
scalwspackage installed and one of the two server units running:scalws-controller(the default) orscalwsd(for managed runtimes, see the three services); - DNS
A/AAAArecords of your domains pointing at the server (needed for Let’s Encrypt; for local testscurl -H 'Host: …'is enough); - ports 80 and 443 reachable from the internet if you want certificates.
Check that the package’s configuration includes site files. A new installation has this
line at the top of /etc/scalws/scalws.yaml:
include: sites.d/*.yamlOn an older installation, add it and create the directory:
sudo install -d -m 0750 -o root -g scalws /etc/scalws/sites.dWhich reload command?
Changes to scalws.yaml or to site files take effect on a reload. scalwsctl site add
and site remove reload for you (through the controller when it runs). After editing a
file by hand:
| Server unit | Reload |
|---|---|
scalws-controller | sudo scalws-controller reload |
scalwsd | scalwsctl reload (or sudo systemctl reload scalwsd) |
Always run sudo scalwsctl check first: it checks the main file together with all site
files exactly as a reload would.
1. Ports 80 and 443
Nothing to do: the packaged configuration listens on ports 80 and 443 and gets a Let’s
Encrypt certificate for every site you add. http://<server address>/ shows the
welcome page. The first install opens both ports in an active ufw or a running firewalld;
for a firewall you enable later, see firewall.
2. A static site
sudo mkdir -p /srv/www/example.com
echo '<h1>Hello from ScalWS</h1>' | sudo tee /srv/www/example.com/index.html
sudo scalwsctl site add example.com --root /srv/www/example.comsite add:
- refuses the domain if any site file or
scalws.yamlalready serves it; - writes
/etc/scalws/sites.d/example.com.yaml(owner root, groupscalws, mode 0640); - checks the whole configuration as a reload would and removes the file again if the check fails;
- reloads the server; if the server refuses the reload, the file is removed again.
The file it writes:
# Site example.com. Fields: https://scalws.com/docs/sites/
domain: example.com
root: /srv/www/example.com
www: redirectNo runtime given means static files, with index.html as the index. Because
example.com has a single dot, www.example.com is served too and redirects with a 301
to example.com (path and query kept). Choose otherwise with --www none|alias|redirect;
add more names with --alias other.example (repeatable).
Test it:
curl -H 'Host: example.com' http://127.0.0.1/
curl -sI -H 'Host: www.example.com' http://127.0.0.1/some/path # 301 to example.com
sudo scalwsctl site listUseful options: --dry-run prints the file and writes nothing; --no-reload writes and
checks but leaves the reload to you. sudo scalwsctl site remove example.com renames the
file to a hidden backup (.example.com.yaml.removed-<time>), checks and reloads.
The server runs as user scalws and must be able to read the document root. Both units
mount /srv and /home read-only for the server, so a document root can live in either.
Under /home, user scalws needs x on every directory on the way (for example
chmod o+x [private host path]); site add prints a warning with the fix when it is missing.
3. A PHP site (WordPress-style)
How PHP runs depends on the server unit.
Under scalws-controller: an existing PHP-FPM pool
The controller refuses managed PHP-FPM pools (php: true), because every scalwsd
instance would start its own pool (ADR-0036). Use the distribution’s PHP-FPM:
sudo apt install php8.3-fpm php8.3-mysqlThe scalws user must be allowed to connect to the pool’s socket. Debian’s and Ubuntu’s
default pool creates /run/php/php8.3-fpm.sock for user and group www-data, mode 0660;
add scalws to that group, restart the controller so it picks up the group, and roll the
instances (they keep running across a controller restart):
sudo usermod -aG www-data scalws
sudo systemctl restart scalws-controller
sudo scalws-controller reloadPHP-FPM runs your PHP code as its own pool user (www-data by default), so that user
needs read access to the application and write access where the application writes (for
WordPress: wp-content/). ScalWS serves static files itself and only needs read access.
Add the site:
sudo scalwsctl site add blog.example.com --root /srv/www/blog \
--php-fastcgi unix:/run/php/php8.3-fpm.sockThis gives php: unix:/run/php/php8.3-fpm.sock: .php files go to the pool, with
index.php and index.html as index files. WordPress also needs a front controller (pretty
permalinks) and benefits from its profile. Edit
/etc/scalws/sites.d/blog.example.com.yaml:
domain: blog.example.com
root: /srv/www/blog
profile: wordpress
php:
fastcgi: unix:/run/php/php8.3-fpm.sock
front_controller: /index.phpfront_controller is the script that handles requests for paths that do not exist.
profile: wordpress adds the profile’s deny rules (for example /wp-config.php,
/wp-content/uploads/*.php) and its response-cache bypass rules (logged-in cookies,
/wp-admin, …). See applications for profiles and
scalwsctl detect /srv/www/blog to let ScalWS propose a configuration.
Apply:
sudo scalwsctl check
sudo scalws-controller reloadUnder scalwsd.service: a managed PHP-FPM pool
With scalwsd.service, ScalWS can start and supervise PHP-FPM itself (it looks for
php-fpm on PATH, else the newest versioned one such as php-fpm8.4 or Alpine’s php-fpm84):
# /etc/scalws/sites.d/blog.example.com.yaml
domain: blog.example.com
root: /srv/www/blog
profile: wordpress
php:
front_controller: /index.php
fpm:
workers: {min: 2, max: 16}scalwsctl site add --php writes the same site when scalws-controller is not running
(it refuses managed runtimes only while the controller runs):
sudo scalwsctl site add blog.example.com --root /srv/www/blog --phpThe PHP processes run as scalws (or as the tenant’s user, see
security) inside the same sandbox, where /srv is read-only. If the
application must write below /srv, allow that path in a drop-in, for example:
# /etc/systemd/system/scalwsd.service.d/writable.conf
[Service]
ReadWritePaths=/srv/www/blog/wp-contentthen sudo systemctl daemon-reload && sudo systemctl restart scalwsd.
4. A Node.js application or any HTTP upstream
Under scalws-controller: proxy to a process you run
Start the application yourself, for example as its own systemd unit listening on
127.0.0.1:3000, then:
sudo scalwsctl site add app.example.com --proxy http://127.0.0.1:3000# /etc/scalws/sites.d/app.example.com.yaml (written by site add)
domain: app.example.com
proxy: http://127.0.0.1:3000The proxy forwards the client’s Host header (preserve_host: true by default) and
tunnels WebSockets. Upstreams can also be unix:/path.sock. Timeouts and the other
settings are in applications.
Under scalwsd.service: ScalWS runs Node.js
sudo scalwsctl site add app.example.com --root /srv/app --node "node server.js"The command is split on whitespace and run without a shell (use the site file’s list
form for arguments with spaces); the application
must listen on process.env.PORT (by default a Unix socket path under
/run/scalws). Python ASGI applications work the same way with --python main:app
(uvicorn). Worker counts, environment and health checks are in
applications.
5. HTTPS with Let’s Encrypt
ScalWS obtains and renews certificates with ACME HTTP-01, and the packaged configuration
already has everything it needs: the listeners on ports 80 and 443 (acme: true on the
TLS ones) and server.acme with enabled: true. A site you add gets its certificate a few
seconds after the reload, once its DNS points at the server and port 80 is reachable.
To receive Let’s Encrypt’s expiry notices, add a contact address in
/etc/scalws/scalws.yaml:
server:
acme:
enabled: true
contact: ["mailto:[email protected]"]
terms_of_service_agreed: trueserver.acme keys and defaults:
| Key | Default | Meaning |
|---|---|---|
enabled | false | Turn ACME on. |
directory | https://acme-v02.api.letsencrypt.org/directory | ACME directory URL (must be https://). |
contact | [] | Contact URIs; each must be mailto:…. |
terms_of_service_agreed | false | Must be true: the CA’s terms are accepted when the account is created. |
state_dir | /var/lib/scalws/acme | Account, certificates and keys. |
renew_before | 30d (30 days) | Renew when the certificate expires within this time. |
check_interval | 12h | How often expiry is checked; at least 1m. |
ca_bundle | none | Extra root certificate (PEM) for the ACME directory (test CAs). |
With ACME enabled, every site file without a tls: key gets tls: auto: a certificate
for all its names, including www.<domain> when www is alias or redirect (so that
name needs DNS too). Use tls: manual (or --tls manual) for a site whose certificate
comes from the TLS listener’s certificates instead. Wildcard domains, IP addresses and
localhost cannot get ACME certificates.
After a change to scalws.yaml: sudo scalwsctl check && sudo scalwsctl reload.
Follow the certificates:
scalwsctl certscerts shows each application’s names, days left and errors. New certificates are
activated without a restart, and renewal starts 30 days before expiry.
Test against the staging CA first to avoid Let’s Encrypt’s rate limits. Certificates
are kept per site in state_dir and are not re-ordered just because directory changed,
so give the staging test its own state directory:
acme:
enabled: true
directory: https://acme-staging-v02.api.letsencrypt.org/directory
state_dir: /var/lib/scalws/acme-staging
contact: ["mailto:[email protected]"]
terms_of_service_agreed: trueWhen staging works, remove the directory and state_dir lines and reload.
Redirect HTTP to HTTPS
Once the certificate is active, send plain-HTTP visitors to HTTPS. Add to the site file:
https_redirect: true(or use --https-redirect with site add). Plain-HTTP requests then get a 301 to the same
URL over HTTPS; ACME challenges are still answered over HTTP. Check and reload as above.
Where next
- Site files: every field,
migrate nginx|apache, rules. - Configuration:
scalws.yamlin full. - TLS: certificate files, SNI, ACME details.
- Caching, security, applications.
- Files, directories and services and troubleshooting.