How-to
Migrating from nginx
Move the sites of an nginx server to ScalWS with scalwsctl migrate nginx, test them next
to nginx, switch ports 80 and 443 over, and roll back if needed.
migrate only reads the nginx configuration. Each server block becomes one ScalWS site
file under /etc/scalws/sites.d/; what it cannot convert is written into the file as
# TODO: lines. Which directive becomes what is listed in
nginx directives in ScalWS.
Prerequisites
- ScalWS installed on the nginx host, with
include: sites.d/*.yamlin/etc/scalws/scalws.yaml(see Sites). nginx keeps running. - ScalWS listening on ports nginx does not use. The packaged configuration listens on 80 and 443, so while nginx holds them the package installs ScalWS without starting it. Change its listeners to test ports (step 1 below) before starting it, and keep them until the switch.
- Document roots readable by group
scalws, and not under/root: the units make/homeand/srvread-only for ScalWS and hide/root(see Files and services). - PHP sites: their PHP-FPM pool must let user
scalwsconnect to its socket. On Debian and Ubuntu:sudo usermod -aG www-data scalws, then restart the ScalWS unit. - Migrated sites use PHP-FPM pools and proxy upstreams that already run, so they work
under scalws-controller (the default) as well as under
scalwsd.service.
1. Enable ACME first (for HTTPS sites)
migrate gives HTTPS hosts tls: auto when server.acme is enabled in ScalWS’s
configuration; otherwise they get tls: manual and a TODO naming nginx’s certificate
file. To let ScalWS order its own certificates, enable ACME before migrating, with test
listeners next to nginx:
include: sites.d/*.yaml
server:
listeners:
- {name: http, address: "127.0.0.1:8080"}
- {name: https, address: "127.0.0.1:8443", tls: {acme: true}}
acme:
enabled: true
contact: ["mailto:[email protected]"]
terms_of_service_agreed: truesudo scalwsctl check
sudo scalws-controller reload # under scalws-controller
sudo systemctl restart scalwsd # under scalwsd.service (listener change)Until ScalWS owns port 80, its certificate orders fail (nginx answers the challenges) and are retried later; see step 6. Details: Let’s Encrypt.
2. Dry run
sudo scalwsctl migrate nginx --dry-runIt reads /etc/nginx/nginx.conf and every file it includes (give another path as an
argument) and prints the plan and the files, writing nothing:
nginx: 4 virtual host(s) → 2 site(s)
note: /etc/nginx/sites-enabled/default:1: no domain name (default server?), skipped
write example.com → /etc/scalws/sites.d/example.com.yaml
TODO: add_header X-Frame-Options SAMEORIGIN: not converted (/etc/nginx/sites-enabled/example.com:31)
write shop.example.com → /etc/scalws/sites.d/shop.example.com.yamlWhat is converted:
server_name→domainandaliases;www.<domain>as a name →www: alias; a separatewww.server that only redirects to the domain →www: redirect.- A port-80 server that only redirects to HTTPS (including certbot’s
if ($host = ...)blocks) and the HTTPS server of the same names → one site withhttps_redirect: true. root,index;fastcgi_pass→php:with that pool (unix:ortcp:);try_files ... /index.php?...→front_controller.proxy_pass(anupstreamgroup: its first server) →proxy, or a route for anotherlocation.proxy_set_header Host $hostgivespreserve_host: true; without it the site keeps nginx’s behaviour withpreserve_host: false.return 301 ...andrewrite ... permanent→redirect.
Everything else (add_header, auth_basic, limit_req, rewrite rules, regular
expression locations, if blocks, …) becomes a TODO with its file and line.
A written file looks like this:
# Migrated from nginx: /etc/nginx/sites-enabled/example.com:1, /etc/nginx/sites-enabled/example.com:20
# TODO: add_header X-Frame-Options SAMEORIGIN: not converted (/etc/nginx/sites-enabled/example.com:31)
# Site example.com. Fields: https://scalws.com/docs/sites/
domain: example.com
https_redirect: true
php:
fastcgi: unix:/run/php/php8.3-fpm.sock
front_controller: /index.php
root: /var/www/example.com
tls: auto
www: alias3. Migrate
sudo scalwsctl migrate nginxConflicts. A domain ScalWS already serves is 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:y and n decide for this domain, a overwrites this and every further conflict, o
skips them all. Only a domain’s own site file can be replaced; it is kept as a hidden
.<file>.replaced-<time> next to it. A domain defined in scalws.yaml or in another site
is skipped with a note.
Then migrate asks Write N site file(s)? [y/N], writes the files, checks the whole
configuration like a reload and reloads. If the check or the reload fails, every file of
the run is removed and replaced files are restored.
| Option | Effect |
|---|---|
--dry-run | Print the plan and the files; change nothing. |
--overwrite | Replace conflicting site files without asking. |
--skip-existing | Keep conflicting sites without asking. |
-y, --yes | Do not ask before writing. |
--no-reload | Write and check, but leave the reload to you. |
Without a terminal (a script, a pipe), conflicts must be decided with --overwrite or
--skip-existing; otherwise nothing is written.
4. Work through the TODO lines
Open every file that has # TODO: lines and decide for each:
- rewrite rules: usually into an
.htaccessfile withhtaccess: true(see .htaccess support); add_header,expires: in.htaccess(Header set,ExpiresByType) or in the application;auth_basic,allow/deny: a route withbasic_authorallow/deny, protect the area in the application, or close it withsecurity.deny_paths;limit_req:security.rate_limits;proxy_passwith a URI part: a route sends the original path to the upstream unless it setsrewrite.prefix.
Then check and reload:
sudo scalwsctl check
sudo scalws-controller reload # or: sudo scalwsctl reload5. Test next to nginx
sudo scalwsctl site list
curl -sI -H 'Host: example.com' http://127.0.0.1:8080/
curl -s -H 'Host: shop.example.com' http://127.0.0.1:8080/ | headA site with https_redirect: true answers 301 to https://... here: that confirms the
site matched. Its pages can be checked over HTTPS after the switch.
6. Switch ports 80 and 443
Change the listeners in /etc/scalws/scalws.yaml:
include: sites.d/*.yaml
server:
listeners:
- {name: http, address: "0.0.0.0:80"}
- {name: https, address: "0.0.0.0:443", tls: {acme: true}}
acme:
enabled: true
contact: ["mailto:[email protected]"]
terms_of_service_agreed: trueStop nginx and let ScalWS bind the ports (a short interruption):
sudo scalwsctl check
sudo systemctl stop nginx
sudo scalws-controller reload # under scalws-controller: binds the new addresses
sudo systemctl restart scalwsd # under scalwsd.serviceThen watch the certificates:
scalwsctl certs # under scalwsd.service
scalwsctl --socket /run/scalws/web-0/admin.sock certs # under scalws-controllerThe back-off of failed orders is kept in the server processes’ memory. The restart, or the
controller’s rolling reload that starts new instances, retries at once. When every site
shows ok, disable nginx at boot: sudo systemctl disable nginx.
Rollback
nginx’s configuration was never changed, so going back is quick:
sudo systemctl stop scalws-controller # or: sudo systemctl stop scalwsd
sudo systemctl start nginxBefore starting ScalWS again, set its listeners back to the test ports. To remove the migrated sites:
sudo scalwsctl site remove example.com # kept as .example.com.yaml.removed-<time>A site file that migrate replaced is next to the new one as .<file>.replaced-<time>;
rename it back to restore it, then check and reload.
Common problems
... has no include: for site files: addinclude: sites.d/*.yamltoscalws.yamland create the directory (see Sites).already served by ScalWS: ... run interactively, or pass --overwrite or --skip-existing:migrateran without a terminal; pass one of the two options.the migrated sites do not pass the check; nothing was kept: the message names the file and field, for example a missing document root or certificate. Fix the cause (or the nginx configuration it came from) and runmigrateagain.- A server block is missing: blocks without a usable
server_name(_, regular expressions only) are skipped with a note; a name already used by an earlier block is kept once and reported. Address already in useon port 80 or 443: nginx (or another server) still holds the port:sudo ss -ltnp 'sport = :443'.- 502 on PHP sites:
scalwscannot connect to the PHP-FPM socket; see troubleshooting. stateerrorincerts: DNS or port 80 does not reach ScalWS yet; see Let’s Encrypt.
See also: Sites: moving from nginx or Apache, CLI reference.