# Quick start: your first site in five minutes

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

From a fresh [installation](https://scalws.com/docs/guide/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](https://scalws.com/docs/sites/).

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 `scalws` package installed and one of the two server units running:
  `scalws-controller` (the default) or `scalwsd` (for managed runtimes, see
  [the three services](https://scalws.com/docs/guide/installation/#the-three-services));
* DNS `A`/`AAAA` records of your domains pointing at the server (needed for Let's
  Encrypt; for local tests `curl -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`:

```yaml
include: sites.d/*.yaml
```

On an older installation, add it and create the directory:

```sh
sudo install -d -m 0750 -o root -g scalws /etc/scalws/sites.d
```

### Which 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](https://scalws.com/docs/guide/installation/#firewall).

## 2. A static site

```sh
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.com
```

`site add`:

1. refuses the domain if any site file or `scalws.yaml` already serves it;
2. writes `/etc/scalws/sites.d/example.com.yaml` (owner root, group `scalws`, mode 0640);
3. checks the whole configuration as a reload would and removes the file again if the
   check fails;
4. reloads the server; if the server refuses the reload, the file is removed again.

The file it writes:

```yaml
# Site example.com. Fields: https://scalws.com/docs/sites/
domain: example.com
root: /srv/www/example.com
www: redirect
```

No 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:

```sh
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 list
```

Useful 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:

```sh
sudo apt install php8.3-fpm php8.3-mysql
```

The `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):

```sh
sudo usermod -aG www-data scalws
sudo systemctl restart scalws-controller
sudo scalws-controller reload
```

PHP-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:

```sh
sudo scalwsctl site add blog.example.com --root /srv/www/blog \
    --php-fastcgi unix:/run/php/php8.3-fpm.sock
```

This 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`:

```yaml
domain: blog.example.com
root: /srv/www/blog
profile: wordpress
php:
  fastcgi: unix:/run/php/php8.3-fpm.sock
  front_controller: /index.php
```

`front_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](https://scalws.com/docs/guide/applications/) for profiles and
`scalwsctl detect /srv/www/blog` to let ScalWS propose a configuration.

Apply:

```sh
sudo scalwsctl check
sudo scalws-controller reload
```

### Under 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`):

```yaml
# /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):

```sh
sudo scalwsctl site add blog.example.com --root /srv/www/blog --php
```

The PHP processes run as `scalws` (or as the tenant's `user`, see
[security](https://scalws.com/docs/guide/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:

```ini
# /etc/systemd/system/scalwsd.service.d/writable.conf
[Service]
ReadWritePaths=/srv/www/blog/wp-content
```

then `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:

```sh
sudo scalwsctl site add app.example.com --proxy http://127.0.0.1:3000
```

```yaml
# /etc/scalws/sites.d/app.example.com.yaml (written by site add)
domain: app.example.com
proxy: http://127.0.0.1:3000
```

The 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](https://scalws.com/docs/guide/applications/).

### Under scalwsd.service: ScalWS runs Node.js

```sh
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](https://scalws.com/docs/guide/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`:

```yaml
server:
  acme:
    enabled: true
    contact: ["mailto:ops@example.com"]
    terms_of_service_agreed: true
```

`server.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:

```sh
scalwsctl certs
```

`certs` 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:

```yaml
  acme:
    enabled: true
    directory: https://acme-staging-v02.api.letsencrypt.org/directory
    state_dir: /var/lib/scalws/acme-staging
    contact: ["mailto:ops@example.com"]
    terms_of_service_agreed: true
```

When 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:

```yaml
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](https://scalws.com/docs/sites/): every field, `migrate nginx|apache`, rules.
* [Configuration](https://scalws.com/docs/guide/configuration/): `scalws.yaml` in full.
* [TLS](https://scalws.com/docs/guide/tls/): certificate files, SNI, ACME details.
* [Caching](https://scalws.com/docs/guide/caching/), [security](https://scalws.com/docs/guide/security/), [applications](https://scalws.com/docs/guide/applications/).
* [Files, directories and services](https://scalws.com/docs/guide/files-and-services/) and
  [troubleshooting](https://scalws.com/docs/guide/troubleshooting/).
