# Apache directives in ScalWS

URL: https://scalws.com/docs/howto/apache-directives/
Source: `docs/howto/apache-directives.md` in the ScalWS repository

This page maps the Apache httpd directives people use most to their ScalWS equivalent and says which ones `scalwsctl migrate apache` converts for you.

For the migration itself, see [Migrating from Apache](https://scalws.com/docs/howto/from-apache/).

## 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 `Include` and `IncludeOptional` (relative to `ServerRoot`, a directory
  includes every file in it) and reads every `<VirtualHost>` block. Directives outside a
  `<VirtualHost>` (`apache2.conf`, `mods-enabled/*.conf`, `conf-enabled/*.conf`) are not
  read, so a directive that is only set there gets no note.
* `<IfModule>`, `<IfDefine>` and `<IfVersion>` are transparent: their content counts as
  part of the virtual host.
* Inside other sections (`<Directory>`, `<Location>`, `<Files>`, `<FilesMatch>`, …) a
  directive that the migration does not handle is dropped **without** a note. The
  directives it handles anywhere are listed in the rows below (for example `Header`,
  `SetHandler`, `AllowOverride`, access rules (`Require`, `Order`, `Allow`, `Deny`), `RewriteRule`).

Links in the "ScalWS equivalent" column go to the [configuration reference](https://scalws.com/docs/reference/).
Site keys are shown as they appear in a site file; the same keys exist under
`tenants[].applications[]` in the full configuration form.

Many per-directory settings have a direct equivalent in ScalWS's `.htaccess` support,
enabled per site with [`htaccess: true`](https://scalws.com/docs/reference/site-file/#htaccess). It
covers mod_rewrite, `Redirect`/`RedirectMatch`, `Require`/`Order`/`Allow`/`Deny`,
`<Files>`/`<FilesMatch>`, `ErrorDocument`, `DirectoryIndex`, `Header` and `Expires*`.

## Virtual hosts, names and files

| Directive | ScalWS equivalent | Converted by `scalwsctl migrate` | Notes |
|---|---|---|---|
| `Listen` | [`server.listeners`](https://scalws.com/docs/reference/server/#server.listeners) | no | Outside `<VirtualHost>`. Listeners are server-wide, not per site. |
| `<VirtualHost>` | one site file | yes | Each block becomes a virtual host. `*:443` marks it as HTTPS. An HTTP host that only redirects to HTTPS and the HTTPS host with the same `ServerName` become one site with [`https_redirect`](https://scalws.com/docs/reference/site-file/#https_redirect). |
| `ServerName` | [`domain`](https://scalws.com/docs/reference/site-file/#domain) | yes | A `:port` suffix is removed. A block without a usable name is skipped. |
| `ServerAlias` | [`aliases`](https://scalws.com/docs/reference/site-file/#aliases), [`www`](https://scalws.com/docs/reference/site-file/#www) | yes | `www.<domain>` becomes `www: alias`; a separate host for `www.<domain>` that only redirects to the bare domain becomes `www: redirect`. Names that are not domains are dropped with a note. |
| `DocumentRoot` | [`root`](https://scalws.com/docs/reference/site-file/#root) | yes | |
| `DirectoryIndex` | [`static.index`](https://scalws.com/docs/reference/site-file/#static.index), [`php.index`](https://scalws.com/docs/reference/site-file/#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 `index.html`. Also supported in `.htaccess`. |
| `<Directory>` with `AllowOverride` | [`htaccess`](https://scalws.com/docs/reference/site-file/#htaccess) | yes | Any `AllowOverride` other than `None`, in any `<Directory>` of the host, gives `htaccess: true`. ScalWS then reads `.htaccess` files from the site's `root` down to the requested directory. Other directives inside `<Directory>` are dropped without a note (see above). |
| `Options` | none | ignored (not needed) | ScalWS never lists directories and never follows absolute symlinks. |
| `Alias` | [`routes`](https://scalws.com/docs/reference/site-file/#routes) with a `static` runtime | reported as TODO | A static route does not strip its prefix by itself (route `/assets` with [`root`](https://scalws.com/docs/reference/site-file/#routes-.runtime.static.root) `/srv/app/public` serves `/srv/app/public/assets/…`). Works when the aliased directory's name matches the URL prefix; otherwise set the route's [`rewrite.prefix`](https://scalws.com/docs/reference/site-file/#routes-.rewrite) to replace the prefix. |

## Access control

| Directive | ScalWS equivalent | Converted by `scalwsctl migrate` | Notes |
|---|---|---|---|
| `Require`, `Order`, `Allow`, `Deny` | `.htaccess` `Require`, [`security.deny_paths`](https://scalws.com/docs/reference/site-file/#security.deny_paths) | reported as TODO | Every access rule except `Require all granted` (the default) is reported as TODO with its section, in the virtual host and inside `<Directory>`/`<Location>`; none is dropped silently. Re-create address restrictions in `.htaccess` (`Require [not] ip …`, `Require local`, `Order`/`Allow`/`Deny`, answered `403`) or close paths for everyone with `deny_paths` (answered `404`). `Require valid-user`, `user`, `group`, `host`, `env` are not supported and close the directory with `403` in `.htaccess`. |
| `AuthType`, `AuthName`, `AuthUserFile` | [`routes[].basic_auth`](/docs/reference/site-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. |
| `.htaccess` (mod_rewrite, mod_alias, mod_headers, mod_expires, access) | [`htaccess`](https://scalws.com/docs/reference/site-file/#htaccess) | yes | Converted from `AllowOverride`. Supported: `RewriteEngine`, `RewriteBase`, `RewriteCond`, `RewriteRule` (flags `L`, `END`, `R=code`, `NC`, `QSA`, `QSD`, `NE`, `F`, `G`, `S=n`, `C`), `Redirect`, `RedirectPermanent`, `RedirectTemp`, `RedirectMatch`, `Require`, `Order`/`Allow`/`Deny`, `<Files>`, `<FilesMatch>`, `<IfModule>`, `ErrorDocument`, `DirectoryIndex`, `Header set\|append\|add\|unset`, `ExpiresActive`, `ExpiresByType`, `ExpiresDefault`. Refused and logged: `[P]`, `RewriteMap`, `SetHandler`, `AddHandler`, `php_value`, `php_flag`, `php_admin_*`, regular expressions with look-around or back-references inside the pattern. Files up to 64 KiB and 1000 directives; edits apply within 2 seconds. |
| mod_security (`SecRuleEngine`, `SecRule`, …) | [`security.deny_paths`](https://scalws.com/docs/reference/site-file/#security.deny_paths), [`security.rate_limits`](https://scalws.com/docs/reference/site-file/#security.rate_limits) | reported as TODO | ScalWS has no rule-based web application firewall. Closest: `deny_paths`, per-path `rate_limits`, the deny rules of an application [`profile`](https://scalws.com/docs/reference/site-file/#profile), and the built-in protections (strict request parsing, path normalisation, dotfiles never served). |

## Redirects and rewrites

| Directive | ScalWS equivalent | Converted by `scalwsctl migrate` | Notes |
|---|---|---|---|
| `Redirect`, `RedirectPermanent` | [`redirect`](https://scalws.com/docs/reference/site-file/#redirect), [`https_redirect`](https://scalws.com/docs/reference/site-file/#https_redirect) | yes | Only a redirect of `/` is converted: to `https://` on the same host it becomes `https_redirect`, to another origin a `redirect` runtime that keeps the path (status `permanent`/`301`, `temp`/`302`, `seeother`/`303`; default `302`, `RedirectPermanent` `301`). Redirects of other paths are reported; they work in `.htaccess`. |
| `RedirectMatch`, `RedirectTemp` | `.htaccess` `RedirectMatch` / `RedirectTemp` | reported as TODO | Reported when set directly in the virtual host; inside a section they are dropped without a note. |
| `RewriteEngine` | `.htaccess` `RewriteEngine` | ignored (not needed) | |
| `RewriteCond`, `RewriteRule` | [`https_redirect`](https://scalws.com/docs/reference/site-file/#https_redirect), `.htaccess` `RewriteRule` | yes | Only the HTTPS redirect is converted: a `RewriteCond` on `%{HTTPS}`, `%{SERVER_NAME}` or `%{HTTP_HOST}` followed by a `RewriteRule` to `https://` on the same host (certbot's pattern) becomes `https_redirect: true`. Every other `RewriteRule` is reported as TODO, with the hint to move it into `.htaccess`. |
| `ErrorDocument` | `.htaccess` `ErrorDocument` | reported as TODO | With `htaccess: true`: a local document is served with the original status, a URL redirects (`302`), text is sent as is. |

## Proxying and application runtimes

| Directive | ScalWS equivalent | Converted by `scalwsctl migrate` | Notes |
|---|---|---|---|
| `ProxyPass` | [`proxy.upstream`](https://scalws.com/docs/reference/site-file/#proxy.upstream), [`routes`](https://scalws.com/docs/reference/site-file/#routes) | yes | Only directly in the virtual host (inside `<Location>` it is dropped without a note). `ProxyPass / URL` sets the site's proxy; `ProxyPass /path URL` becomes a route; `ProxyPass /path !` is skipped. The target becomes its origin (`http://host:port`); a target path, or a `/` target under another prefix (`ProxyPass /api http://b/`), is reported as TODO, because Apache maps the prefix and a ScalWS route sends the full request path unless it sets [`rewrite.prefix`](https://scalws.com/docs/reference/site-file/#routes-.rewrite). `balancer://` groups are not converted: define a pool in the main configuration's [`upstreams`](https://scalws.com/docs/reference/top-level/#upstreams) and use it from [`proxy.backends`](https://scalws.com/docs/reference/site-file/#proxy.backends) (see [load balancing](https://scalws.com/docs/guide/load-balancing/)). |
| `ProxyPassReverse` | none | reported as TODO | ScalWS does not rewrite `Location` headers from the upstream. Send the right `Host` with `preserve_host` so the application builds correct URLs. |
| `ProxyPreserveHost` | [`proxy.preserve_host`](https://scalws.com/docs/reference/site-file/#proxy.preserve_host) | yes | `On` gives `preserve_host: true` for the site's proxy and every proxy route; without it the migration sets `false` (ScalWS's own default is `true`). |
| `SetHandler "proxy:unix:…\|fcgi://localhost"` / `proxy:fcgi://host:port` | [`php.fastcgi`](https://scalws.com/docs/reference/site-file/#php.fastcgi) | yes | The PHP-FPM socket becomes `unix:/run/php/…sock`, a TCP address `tcp:host:port`. Usually inside `<FilesMatch \.php$>`, which is fine. `ProxyPassMatch` to `fcgi://` with `php` in the pattern is converted too. Other `SetHandler` values are reported. ScalWS can also run PHP-FPM itself with [`php.fpm`](https://scalws.com/docs/reference/site-file/#php.fpm). |
| mod_php (`LoadModule php_module`, `SetHandler application/x-httpd-php`, `php_value`) | [`php.fpm`](https://scalws.com/docs/reference/site-file/#php.fpm), [`php.fpm.ini`](https://scalws.com/docs/reference/site-file/#php.fpm.ini) | no | mod_php is configured outside the virtual hosts, so a mod_php site migrates as a static site: add `php` to it. ScalWS runs PHP only through FastCGI, as an existing pool (`php.fastcgi`) or a managed PHP-FPM (`php.fpm`); `php_value` settings go to `php.fpm.ini`. `SetHandler application/x-httpd-php` in the virtual host is reported as TODO, and so is `php_value` set directly in it. |
| `ScriptAlias` (CGI) | none | reported as TODO | CGI is not supported. Closest: run the program as an HTTP service and use a [`proxy`](https://scalws.com/docs/reference/site-file/#proxy) route. |
| `WSGIScriptAlias`, `WSGIDaemonProcess` | [`python`](https://scalws.com/docs/reference/site-file/#python) with [`python.mode`](https://scalws.com/docs/reference/site-file/#python.mode) `wsgi` and [`python.app`](https://scalws.com/docs/reference/site-file/#python.app) | reported as TODO | ScalWS starts the application server (gunicorn, uvicorn or hypercorn) from [`python.virtualenv`](https://scalws.com/docs/reference/site-file/#python.virtualenv) and supervises it. |
| Passenger (`PassengerEnabled`, `PassengerAppRoot`, …) | [`node`](https://scalws.com/docs/reference/site-file/#node), [`python`](https://scalws.com/docs/reference/site-file/#python), [`proxy`](https://scalws.com/docs/reference/site-file/#proxy) | reported as TODO | Node.js and Python applications run as managed runtimes. There is no Ruby runtime: run the Ruby application server yourself and proxy to it. |

## TLS

| Directive | ScalWS equivalent | Converted by `scalwsctl migrate` | Notes |
|---|---|---|---|
| `SSLEngine on` | [`tls`](https://scalws.com/docs/reference/site-file/#tls) | yes | Marks the host as HTTPS. With [`server.acme.enabled`](https://scalws.com/docs/reference/server-acme/#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. |
| `SSLCertificateFile` | [`tls`](https://scalws.com/docs/reference/site-file/#tls), [`server.listeners[].tls.certificates`](/docs/reference/server-listeners/#server.listeners-.tls.certificates) | yes | Marks the host as HTTPS like `SSLEngine on`. The file is named in the TODO when ACME is off; it is not copied. |
| `SSLCertificateKeyFile`, `SSLCertificateChainFile` | [`server.listeners[].tls.certificates[].key`](/docs/reference/server-listeners/#server.listeners-.tls.certificates-.key) | ignored (not needed) | For `tls: manual`, the certificate file holds the chain (end-entity first) and `key` the private key. |
| `SSLProtocol`, `SSLCipherSuite`, `SSLHonorCipherOrder`, `SSLCompression`, `SSLSessionTickets`, `SSLUseStapling`, `SSLOpenSSLConfCmd`, `Protocols` | none | ignored (not needed) | No settings: TLS 1.2 and 1.3 with forward-secret AEAD suites, no OCSP stapling. HTTP/2 is always on (ALPN `h2`); HTTP/3 is [`server.listeners[].http3`](/docs/reference/server-listeners/#server.listeners-.http3). |

## Headers, caching and compression

| Directive | ScalWS equivalent | Converted by `scalwsctl migrate` | Notes |
|---|---|---|---|
| `Header` | `.htaccess` `Header` | reported as TODO | Reported also inside sections. No site-level key for response headers; a route can set them with [`response_headers`](https://scalws.com/docs/reference/site-file/#routes-.response_headers). With `htaccess: true`, `Header [always] set\|append\|add\|unset` in `.htaccess` (no `env=`/`expr=` conditions; hop-by-hop and framing headers cannot be changed). ScalWS adds no `Strict-Transport-Security` by itself. |
| `ExpiresActive`, `ExpiresByType` | `.htaccess` `ExpiresActive`, `ExpiresByType`, `ExpiresDefault` | reported as TODO | Reported also inside sections. |
| mod_cache (`CacheEnable`, `CacheDefaultExpire`, …) | [`cache`](https://scalws.com/docs/reference/site-file/#cache), [`server.cache`](https://scalws.com/docs/reference/server-cache/#server.cache.memory) | reported as TODO | Reported when set in the virtual host. 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`. |
| mod_deflate (`AddOutputFilterByType DEFLATE`, `SetOutputFilter`) | [`static.precompressed`](https://scalws.com/docs/reference/site-file/#static.precompressed) | no | Usually set in `mods-enabled/deflate.conf`, which is not read (in a virtual host it is reported). ScalWS does not compress responses on the fly; with `precompressed: true` the static runtime serves an existing `<file>.br` or `<file>.gz`. In `.htaccess`, `AddOutputFilterByType` is ignored. |

## Limits and timeouts

| Directive | ScalWS equivalent | Converted by `scalwsctl migrate` | Notes |
|---|---|---|---|
| `LimitRequestBody` | [`server.limits.max_request_body_bytes`](https://scalws.com/docs/reference/server-limits/#server.limits.max_request_body_bytes) | reported as TODO | Reported when set in the virtual host; inside `<Directory>` it is dropped without a note. Server-wide (default 10 MiB); a route can override it with [`max_body`](https://scalws.com/docs/reference/site-file/#routes-.max_body). A larger `Content-Length` gets `413`. |
| `Timeout` | [`server.limits.header_read_timeout`](https://scalws.com/docs/reference/server-limits/#server.limits.header_read_timeout), [`server.limits.body_read_timeout`](https://scalws.com/docs/reference/server-limits/#server.limits.body_read_timeout), [`server.limits.write_timeout`](https://scalws.com/docs/reference/server-limits/#server.limits.write_timeout), [`proxy.response_timeout`](https://scalws.com/docs/reference/site-file/#proxy.response_timeout) | no | Usually global. ScalWS splits it: request head (15 s), gap between body chunks (30 s), stalled writes (60 s), and per runtime the time to the upstream's response headers. In a virtual host it is reported. |
| `KeepAlive`, `KeepAliveTimeout` | [`server.limits.idle_timeout`](https://scalws.com/docs/reference/server-limits/#server.limits.idle_timeout) | no | Usually global. There is no setting to turn keep-alive off; idle connections are closed after `idle_timeout` (default 60 s). In a virtual host they are reported. |

## Logging and client addresses

| Directive | ScalWS equivalent | Converted by `scalwsctl migrate` | Notes |
|---|---|---|---|
| `CustomLog` | [`server.logging.access_log`](https://scalws.com/docs/reference/server-logging/#server.logging.access_log), [`server.logging.format`](https://scalws.com/docs/reference/server-logging/#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](https://scalws.com/docs/guide/files-and-services/#logs)). No per-site log files or custom formats. |
| `ErrorLog`, `LogLevel` | [`server.logging.level`](https://scalws.com/docs/reference/server-logging/#server.logging.level) | ignored (not needed) | Also to the service log. |
| `RemoteIPHeader`, `RemoteIPTrustedProxy` | [`server.trusted_proxies`](https://scalws.com/docs/reference/server/#server.trusted_proxies), [`server.listeners[].proxy_protocol`](/docs/reference/server-listeners/#server.listeners-.proxy_protocol) | reported as TODO | Reported when set in the virtual host (mod_remoteip is usually configured globally). For requests from a trusted peer, the client is the right-most `X-Forwarded-For` entry that is not itself a trusted proxy. Only `X-Forwarded-For` and the PROXY protocol are read: other headers (`X-Real-IP`, a CDN's own header) are not supported. |
