Server Tools: Virtual Hosts
Virtual hosts with Server Tools: create and delete PHP-FPM and reverse proxy VHosts for Apache and Nginx, switch PHP version, redirects, logrotate and VHost audit, with all checks and special cases.
Virtual Hosts
The VHost module creates Apache or Nginx configurations, enables them and reloads the webserver. The backend is determined globally by ST_WEBSERVER, not per VHost. The generated configurations are shown under Technical details.
| Apache | Nginx | |
|---|---|---|
| Configuration file | <ST_APACHE_SITES_AVAILABLE>/<domain>.conf |
<ST_NGINX_SITES_AVAILABLE>/<domain> (no extension) |
| Enable | a2ensite |
Symlink to <ST_NGINX_SITES_ENABLED>/<domain> |
| Modules | a2enmod headers proxy_fcgi (PHP) or headers proxy proxy_http (proxy) |
not needed |
| Reload | apache2ctl configtest, then systemctl reload apache2 |
nginx -t, then systemctl reload nginx |
SSL: ssl create, ssl delete, ssl renew |
supported | not supported |
SSL: ssl wildcard |
runs | runs |
vhost audit (template check) |
supported | not implemented |
Regardless of the backend, the logs of each domain are located at /var/www/<domain>/logs/ (access.log, error.log).
Create a PHP VHost
server-tools vhost create --domain example.com --php 8.3
server-tools vhost create --domain example.com --aliases "www.example.com cdn.example.com" --docroot /var/www/example.com/public
| Option | Meaning |
|---|---|
--domain |
Required, valid domain name |
--php |
PHP version, default ST_DEFAULT_PHP_VERSION. Allowed: 7.4, 8.0, 8.1, 8.2, 8.3, 8.4 |
--aliases |
Additional names, separated by spaces in one argument. Each name is checked as a domain |
--docroot |
Custom DocumentRoot, must be located under ST_ALLOWED_DOCROOT_PATHS. Default /var/www/<domain>/html |
--type |
php (default) or proxy |
Flow:
- Domain, aliases and PHP version are checked. The FPM socket
/run/php/php<version>-fpm.sockmust exist, otherwise the command aborts and lists the versions it found. - If the VHost file already exists, the tool warns and asks
Overwrite existing configuration?. - DocumentRoot and
/var/www/<domain>/logsare created and given towww-data, the DocumentRoot gets mode 755. - The logrotate file
/etc/logrotate.d/vhost-<domain>is written. - A welcome page
index.phpis written into the DocumentRoot. - The configuration is written with mode 640, the site is enabled and the webserver is reloaded.
- If the reload fails, the site is disabled again. The written files and directories remain.
If you create a VHost again for an existing domain, the tool overwrites the VHost file and the
index.phpin the DocumentRoot. With--yesthis happens without a prompt. An existingindex.phpof your site is lost.
Create a reverse proxy VHost
server-tools vhost create --domain app.example.com --type proxy --backend http://localhost:3000
server-tools vhost create --domain ws.example.com --type proxy --backend http://localhost:3000 --websocket
| Option | Meaning |
|---|---|
--backend |
Required. URL with http:// or https://, host, optionally port and path |
--websocket |
Enables WebSocket forwarding |
--preserve-host |
The value false turns ProxyPreserveHost off, any other value leaves it on. Default from ST_PROXY_PRESERVE_HOST |
For proxy VHosts, --php and --docroot are not evaluated, there is no DocumentRoot, only the log directory. The effects in detail:
- Apache:
ProxyPass / <backend>/andProxyPassReverse. Specify the backend without a trailing slash, the tool appends one itself. With--websocket,mod_proxy_wstunnel,mod_rewriteand a rewrite rule are added that forwards upgrade requests asws://(aswss://for anhttps://backend). - Nginx:
proxy_pass <backend>;unchanged, plus the headersX-Real-IP,X-Forwarded-For,X-Forwarded-Proto,X-Forwarded-Port. With--websocket,UpgradeandConnection "upgrade"are added. With--preserve-host false,proxy_set_header Host $hostis omitted.
--preserve-host off, as the help text puts it, is not recognized and leaves ProxyPreserveHost on. Usefalse.
Delete
server-tools vhost delete --domain example.com
Flow:
- The VHost must exist.
- If the domain has an assigned SSH user (home
/var/www/<domain>, UID 1000 or higher), the command aborts. Delete the user first, see SSH users. - With
ST_AUTO_BACKUP=truethe VHost file is saved asvhost_<domain>_<timestamp>.tar.gzinST_BACKUP_DIR, before the prompt. - Prompt
Delete virtual host '<domain>'? (y/N):. - Disable the site, on Apache also
<domain>-le-ssl, remove configuration files and the logrotate file. - If the tool finds a DocumentRoot in the configuration (PHP VHosts), the prompt
Also delete DocumentRoot (/var/www/<domain>)?follows. On "yes" the entire directory/var/www/<domain>is deleted recursively, that is DocumentRoot, logs and everything else in it, without a backup. Proxy VHosts have no DocumentRoot, so this prompt does not appear for them and nothing is deleted. - Reload the webserver.
For VHosts with a DocumentRoot,
--yesalso answers the second prompt with "yes":vhost delete --yesthen deletes the directory/var/www/<domain>including its content. For proxy VHosts nothing is deleted. Only the VHost file is backed up. A certificate under/etc/letsencryptremains, remove it withssl deleteif needed.
List and info
server-tools vhost list
server-tools vhost info --domain example.com
list shows "Active sites" (files in sites-enabled) and "Available sites" (files in sites-available). On Apache only *.conf files count.
info shows domain, type (php or proxy, detected from the file content), for PHP the DocumentRoot and PHP version, for proxy the backend and WebSocket, SSL and status. On Apache, SSL counts as active if <domain>-le-ssl.conf exists. On Nginx it shows not applicable (nginx).
Switch the PHP version
server-tools vhost php --domain example.com --version 8.4
The tool checks domain and version, requires the FPM socket of the target version, saves the file to /tmp, replaces the socket path and reloads the webserver. If the reload fails, it restores the old file and reloads again.
The switch only changes the file
<domain>.conf. If certbot created its own<domain>-le-ssl.conffor the domain, that file keeps the old PHP version until you adjust it there yourself.
Redirect VHost
server-tools vhost redirect --from old.example.com --to https://new.example.com/ --code 301
Creates a separate VHost for the source domain that redirects to the target URL. --code is 301 (default) or 302. If a configuration already exists for the domain, the command aborts. Apache uses Redirect <code> / <url>, Nginx return <code> <url>;. Neither a DocumentRoot nor logrotate is created, and there is no rollback if the reload fails.
www and HTTPS redirect (menu only)
In the menu "Virtual Host Management", "Add www redirect" and "Force HTTPS redirect" add a block to an existing VHost. The tool saves the file to /tmp, inserts the block before the first </VirtualHost> (Apache) or before the last line } (Nginx), reloads and restores the file on an error.
- www redirect: from
<domain>tohttp://www.<domain>or the other way round, with status code 301. - HTTPS redirect: redirects requests without HTTPS to
https://<host><path>.
Both actions only change <domain>.conf and do not check whether the block is already present. A second call inserts it a second time.
VHost audit
server-tools vhost audit
server-tools vhost audit --domain example.com
Checks Apache VHosts against the template the tool generates. There are four checks per VHost:
| Check | Meaning |
|---|---|
X-Forwarded-Proto |
Header is present in the file |
X-Forwarded-Port |
Header is present in the file |
ErrorLog |
Points to /var/www/<domain>/logs/error.log |
CustomLog |
Points to /var/www/<domain>/logs/access.log |
The example shows the format:
Vhost: example.com
[OK] X-Forwarded-Proto header present
[OK] X-Forwarded-Port header present
[OK] ErrorLog matches canonical path (rotated via /etc/logrotate.d/vhost-example.com)
[DRIFT] CustomLog does not match canonical path /var/www/example.com/logs/access.log (found: CustomLog /var/log/apache2/access.log combined)
Pure redirect VHosts are skipped with [SKIP]. Without --domain the command checks all *.conf in sites-available except *-le-ssl.conf, 000-default, default-ssl and default. The exit code is 1 as soon as one VHost deviates, otherwise 0. This makes the audit usable in a cron job. The audit changes nothing.
With ST_WEBSERVER=nginx, vhost audit --domain prints the line [SKIP] template audit not yet implemented for nginx and vhost audit without --domain prints the line (template audit not yet implemented for nginx, see #13). Both end with exit code 0.