How-to
nginx directives in ScalWS
This page maps the nginx directives people use most to their ScalWS equivalent and says which ones scalwsctl migrate nginx converts for you.
For the migration itself, see Migrating from nginx.
How to read the table
The column “Converted by scalwsctl migrate” uses four values:
- yes: the migration turns the directive into a site file key.
- ignored (not needed): the migration skips it without a note, because ScalWS does this by itself or has no setting for it.
- reported as TODO: the directive is not converted; the migration writes a
# TODO:line with the source file and line into the site file. - no: the migration does not read the directive at all, and there is no note.
What the migration reads:
- It follows
includeeverywhere and reads everyserver { }block and everyupstream { }block. Directives in thehttp { },events { }and main contexts are not read, so a directive that is only set there (for exampleclient_max_body_sizeinhttp { }) gets no note. The rows below describe a directive inside aserverorlocationblock unless they say otherwise. - Inside a
location, directives whose name starts withproxy_orfastcgi_are skipped without a note, apart fromproxy_pass,proxy_set_header Hostandfastcgi_pass, which are converted. - Every
serverblock becomes one virtual host; hosts with the same main name are merged into one site (seelistenandreturn).
Links in the “ScalWS equivalent” column go to the configuration reference.
Site keys are shown as they appear in a site file; the same keys exist under
tenants[].applications[] in the full configuration form.
Server, names and files
| Directive | ScalWS equivalent | Converted by scalwsctl migrate | Notes |
|---|---|---|---|
listen | server.listeners | yes | Listeners are server-wide, not per site, so ports are not copied. A listen with ssl, quic, 443 or :443 marks the virtual host as HTTPS, which sets tls. An HTTP host that only redirects to HTTPS and the HTTPS host with the same name become one site. |
server_name | domain, aliases, www | yes | The first name becomes domain, the others aliases. www.<domain> becomes www: alias; a separate server for www.<domain> that only redirects to the bare domain becomes www: redirect. Names that are not domains (_, regular expressions) are dropped with a note; a server with no usable name (a default server) is skipped. |
root | root | yes | Only root at server level. root inside a location is skipped without a note. |
index | static.index, php.index | yes | Written to php.index for a PHP site when a name ends in .php, otherwise to static.index when it differs from the default index.html. |
include | include | yes | Followed during the migration (paths relative to the directory of nginx.conf, * and ? wildcards), up to 2000 files and 16 levels. ScalWS’s own include is a list of files merged into the main configuration. |
sendfile | server.sendfile | ignored (not needed) | On by default for large static files on plain-TCP HTTP/1 connections (Linux). |
worker_processes | server.event_loops | no | Main context. Default: one event loop per CPU. |
worker_connections | server.limits.max_connections | no | events context. ScalWS limits open client connections across all listeners (default 10000). |
Locations and routing
| Directive | ScalWS equivalent | Converted by scalwsctl migrate | Notes |
|---|---|---|---|
location /prefix/ | routes | yes | A prefix location with proxy_pass, fastcgi_pass or a redirect return becomes a route with that runtime (trailing / removed). A proxy_pass with a URI part, / included, under a prefix other than / is reported as TODO: nginx replaces the prefix, while a ScalWS route sends the full request path unless it sets rewrite.prefix. location / sets the site’s default runtime. A location without such a directive (only root, try_files, expires and the like) gives no route. Routes match whole path segments, longest first. Nested locations are reported as TODO. |
location = /path | routes | yes | Converted like a prefix location: ScalWS has no exact-match routes, so the route also matches paths below it. |
location ~ regex / ~* | routes[].path_type: regex | reported as TODO | A route with path_type: regex matches a regular expression anchored at the start of the path ((?i) for ~*); the migration does not write such routes. A regex location with fastcgi_pass and php in its pattern (the usual \.php$ block) makes the site a PHP site. location ~ /\.ht is skipped (ScalWS never serves dotfiles) and so is any acme-challenge location. Other regex locations with a handler are reported; for rules, use .htaccess RewriteRule with htaccess. |
try_files | php.front_controller | yes | Only when the last argument is a .php file (/index.php?$args becomes front_controller: /index.php). Other try_files in a location are skipped without a note; at server level they are reported. For a single-page app fallback use a RewriteRule in .htaccess. |
return 301|302|303|307|308 URL | https_redirect, redirect | yes | return 301 https://$host$request_uri (or the same host by name) on a port-80 server becomes https_redirect: true on the HTTPS site. A redirect to another origin becomes a redirect runtime; $request_uri in the target sets redirect.keep_path. |
return with other codes | runtime of type: respond | no | return 404 and return 444 (certbot’s port-80 servers) and other non-redirect codes are dropped without a note. A fixed response is a respond runtime with status, body and content_type. |
rewrite | redirect, .htaccess RewriteRule | yes | At server level, only rewrite … permanent or redirect is converted, and then into a redirect of the whole site to the target: the pattern is not kept. Check such sites. Other server-level rewrites (last, break, no flag) and every rewrite inside a location are reported as TODO. Internal rewrites work in .htaccess with htaccess: true. |
if | .htaccess RewriteCond | reported as TODO | Only certbot’s if ($host = …) { return 301 https://$host$request_uri; } is converted, into https_redirect. Everything else is reported. |
error_page | .htaccess ErrorDocument | reported as TODO | With htaccess: true, ErrorDocument 404 /404.html serves a local document with the original status, a URL redirects, and text is sent as is. |
Upstreams, proxying and PHP
| Directive | ScalWS equivalent | Converted by scalwsctl migrate | Notes |
|---|---|---|---|
proxy_pass | proxy.upstream | yes | http://, https:// and unix: upstreams. A URI part in the target (proxy_pass http://app/api/) is noted: the migration does not rewrite the path; a route’s rewrite (prefix or path) can. |
proxy_set_header Host | proxy.preserve_host | yes | proxy_set_header Host $host or $http_host gives preserve_host: true; without it the migration sets preserve_host: false (ScalWS’s own default is true). Other proxy_set_header lines are skipped without a note. ScalWS removes client-supplied X-Forwarded-* and sends its own X-Forwarded-For. |
upstream | proxy.upstream, php.fastcgi | yes | An upstream name used by proxy_pass or fastcgi_pass is replaced by the first server of the group; a group with more servers is reported as TODO (“only the first is used”); weights and keepalive are dropped. For load balancing, define a pool in the main configuration’s upstreams and use it from proxy.backends (see load balancing). |
proxy_redirect, proxy_*_timeout, proxy_buffer* | proxy.connect_timeout, proxy.response_timeout | ignored (not needed) | Buffering is not configurable. ScalWS does not rewrite Location headers from the upstream. |
fastcgi_pass | php.fastcgi | yes | unix:/run/php/php8.3-fpm.sock stays unix:…; 127.0.0.1:9000 becomes tcp:127.0.0.1:9000. Instead of an existing pool, ScalWS can also run PHP-FPM itself with php.fpm. |
fastcgi_param, fastcgi_index, fastcgi_split_path_info, fastcgi_read_timeout | php.response_timeout | ignored (not needed) | ScalWS sends the FastCGI parameters itself. |
proxy_cache / fastcgi_cache | cache, server.cache | reported as TODO | Reported at server level; inside a location they are skipped without a note (proxy_/fastcgi_ prefix), and proxy_cache_path / fastcgi_cache_path in http are not read. ScalWS caches per site with cache (bypass paths, cookies and query parameters, default_ttl, stale_while_revalidate) in the shared memory and optional disk store of server.cache. |
TLS and HTTP versions
| Directive | ScalWS equivalent | Converted by scalwsctl migrate | Notes |
|---|---|---|---|
ssl_certificate | tls, server.listeners[].tls.certificates | yes | Marks the host as HTTPS. With server.acme.enabled the site gets tls: auto (a certificate from Let’s Encrypt); otherwise tls: manual and a TODO to add the certificate to the HTTPS listener. The file is not copied. |
ssl_certificate_key | server.listeners[].tls.certificates[].key | ignored (not needed) | Goes with the certificate on the listener when you use tls: manual. |
ssl_protocols, ssl_ciphers, ssl_prefer_server_ciphers, ssl_session_*, ssl_dhparam, ssl_ecdh_curve, ssl_stapling*, ssl_early_data | none | ignored (not needed) | No settings: TLS 1.2 and 1.3 with forward-secret AEAD suites, stateless TLS 1.3 session tickets, no 0-RTT, no OCSP stapling. |
ssl_client_certificate, ssl_verify_client | none | reported as TODO | Client certificates are not requested on application listeners. |
http2 | none | ignored (not needed) | HTTP/2 is always on: ALPN h2 on TLS listeners, prior-knowledge h2c on plain listeners. HTTP/3 is server.listeners[].http3. |
Headers, caching and compression
| Directive | ScalWS equivalent | Converted by scalwsctl migrate | Notes |
|---|---|---|---|
add_header | .htaccess Header, routes[].response_headers | reported as TODO | No site-level key for response headers; a route can set them with response_headers. With htaccess true, Header set|append|add|unset in .htaccess (conditional env=/expr= forms are not supported). ScalWS adds no Strict-Transport-Security by itself. |
expires | .htaccess ExpiresByType / ExpiresDefault | reported as TODO | With htaccess: true. |
gzip, gzip_* | static.precompressed | ignored (not needed) | ScalWS does not compress responses on the fly. With precompressed: true the static runtime serves an existing <file>.br or <file>.gz to clients that accept it. Proxied and PHP responses pass as the backend sends them. |
Limits and timeouts
| Directive | ScalWS equivalent | Converted by scalwsctl migrate | Notes |
|---|---|---|---|
client_max_body_size | server.limits.max_request_body_bytes | reported as TODO | Server-wide (default 10 MiB); a route can override it with max_body. A larger Content-Length gets 413. |
keepalive_timeout | server.limits.idle_timeout | ignored (not needed) | Default 60 s, for HTTP/1 and HTTP/2. |
client_header_timeout | server.limits.header_read_timeout | reported as TODO | Default 15 s. |
client_body_timeout | server.limits.body_read_timeout | reported as TODO | Maximum gap between body chunks, default 30 s. |
limit_req | server.security.per_ip.requests_per_second, security.rate_limits | reported as TODO | per_ip limits every client address (IPv6 per /64) server-wide, with burst; security.rate_limits sets a rate for path patterns of one site. Over the limit: 429. limit_req_zone in http is not read. |
limit_conn | server.security.per_ip.max_connections | reported as TODO | Server-wide, per client address. |
Access control and client addresses
| Directive | ScalWS equivalent | Converted by scalwsctl migrate | Notes |
|---|---|---|---|
allow / deny | .htaccess Require ip / Require all denied, security.deny_paths | reported as TODO | Address rules: Require [not] ip … or Order/Allow/Deny in .htaccess (with htaccess: true), answered 403. To close a path for everyone: deny_paths (answered 404, * wildcards). A route can also take allow and deny lists of addresses and networks (answered 403). |
auth_basic, auth_basic_user_file | routes[].basic_auth | reported as TODO | A route’s basic_auth checks an htpasswd file (users_file, bcrypt, SHA-256/512 crypt or Apache MD5 entries) with a realm. In .htaccess, Auth* directives are not supported and close the directory with 403 rather than publish it. |
set_real_ip_from | server.trusted_proxies | reported as TODO | For requests from a trusted peer, the client is the right-most X-Forwarded-For entry that is not itself a trusted proxy. |
real_ip_header | server.trusted_proxies, server.listeners[].proxy_protocol | reported as TODO | Only X-Forwarded-For and the PROXY protocol are read. Other headers (X-Real-IP, a CDN’s own header) and Forwarded are not supported. |
Logging
| Directive | ScalWS equivalent | Converted by scalwsctl migrate | Notes |
|---|---|---|---|
access_log | server.logging.access_log, server.logging.format | ignored (not needed) | One line per request, on by default, json or text, written to the service log (journalctl -u scalwsd; under scalws-controller each instance’s own file, see logs). No per-site log files or custom formats. |
error_log | server.logging.level | ignored (not needed) | Also to the service log. |