Static Web Server
Overview
Static Web Server (SWS) is a cross-platform, high-performance, asynchronous web server written in Rust for serving static files. It compiles to a single self-contained binary with zero runtime dependencies, so it starts instantly, uses very little memory and is simple to operate. This guide covers deploying and configuring it on Ubuntu 24.04 from the SecureAnyCloud images on the Azure Marketplace.
What you get:
- Static Web Server 2.43.0 from the pinned upstream release, verified against a published SHA256 checksum. It is a single static Rust binary at
/usr/local/bin/static-web-server, run under systemd as the dedicated unprivilegedswsaccount. - Two listeners, one site.
static-web-server.serviceserves the document root over HTTPS on port 443, terminating TLS itself.static-web-server-http.serviceserves the same document root over HTTP on port 80. No host name is written into the image, so both listeners answer for whatever address or domain you point at the VM, from the moment it boots. Once your domain is in place, you can turn port 80 into a permanent HTTPS redirect (Step 11). - A unique self-signed certificate per instance. On the first boot of every VM, a one-shot service generates the certificate into
/etc/static-web-server/certs. No certificate or private key is baked into the image, so no key material is shared across deployments. - A hardened default posture. Directory listing is disabled, hidden dotfiles are never served, and symbolic links are not followed out of the document root. Strict Transport Security, X-Frame-Options, X-Content-Type-Options and a Content Security Policy are sent on every response. The Prometheus metrics endpoint is switched off, because it is an unauthenticated information disclosure surface on a public web server.
- No credentials. Static Web Server has no login, so no shared or default credential of any kind ships in the image. There is no web interface, no database and no reverse proxy: readable TOML configuration is the whole configuration.
- Content delivery tuned out of the box. On-demand Gzip, Deflate, Brotli and Zstandard compression, precompressed file support, cache control headers, custom 404 and error pages, and a quiet
/healthendpoint. - A document root at /var/www/static with a SecureAnyCloud landing page that you replace with your own site.
- A tightly sandboxed systemd unit that runs as the unprivileged
swsaccount. Its only remaining privilege is the ability to bind the standard web ports.
Prerequisites
You need:
- An active Azure subscription, an SSH public key, and a VNet and subnet in the target region.
- A subscription to the Static Web Server listing on Azure Marketplace.
- Network Security Group rules that allow TCP 22 for administration, and TCP 80 and TCP 443 from the clients that will reach the web server.
- Recommended virtual machine size:
Standard_B2s(2 vCPU, 4 GB RAM), which is ample for serving static sites or single-page applications. Static Web Server is extremely light, so larger sizes are only needed for very high request volumes.
Deploy from the Azure Portal
In the Azure Portal, search the Marketplace for Static Web Server, select the SecureAnyCloud publisher and click Create. Configure the Network Security Group to allow TCP 443 (HTTPS) and TCP 80 (HTTP) from the clients that will reach the server, and TCP 22 for administration.
Deploy from the Azure CLI
RG="web-prod"; LOCATION="eastus"; VM_NAME="sws1" IMAGE_URN="sac1647283583153:static-web-server:default:latest" SSH_KEY="$(cat ~/.ssh/id_rsa.pub)" az group create --name "$RG" --location "$LOCATION"
# Accept the Marketplace terms once per subscription (required before the first CLI deploy)
az vm image terms accept --urn sac1647283583153:static-web-server:default:latest
az vm create \
--resource-group "$RG" --name "$VM_NAME" \
--image "$IMAGE_URN" \
--size Standard_B2s \
--admin-username azureuser \
--ssh-key-values "$SSH_KEY" \
--public-ip-sku Standard
az vm open-port --resource-group "$RG" --name "$VM_NAME" --port 443 --priority 1001
az vm open-port --resource-group "$RG" --name "$VM_NAME" --port 80 --priority 1002
az vm open-port --resource-group "$RG" --name "$VM_NAME" --port 22 --priority 1003First boot
On first boot, the image generates a unique self-signed TLS certificate for this VM into /etc/static-web-server/certs/sws.crt and sws.key. It also writes the informational note /root/static-web-server-info.txt and starts both listeners. This completes within seconds.
SSH in as azureuser and read the note:
sudo cat /root/static-web-server-info.txtThe note records the served HTTPS and HTTP URLs, the health endpoint, the document root, both configuration paths and the certificate path:
static_web_server.version=Version: 2.43.0 static_web_server.document_root=/var/www/static static_web_server.config_https=/etc/static-web-server/config.toml static_web_server.config_http=/etc/static-web-server/config-http.toml static_web_server.tls_cert=/etc/static-web-server/certs/sws.crt HTTPS_URL=https://10.0.0.17/ HTTP_URL=http://10.0.0.17/ HEALTH_URL=https://10.0.0.17/healthStatic Web Server has no password, so the note holds no secret.
Confirm the services are running
Confirm that both listener units are active, that static-web-server --version reports the installed build, and that ss shows the server listening on :443 for HTTPS and :80 for HTTP.
systemctl is-active static-web-server.service static-web-server-http.service static-web-server --version sudo ss -tlnp | grep -E ':80 |:443 'Expected output:
active active Version: 2.43.0 Built: 2026-06-10 23:25:42 +02:00 Git commit: 74b0f3cd26beba31f2ac6d128b47c48439adfc8b Build target: x86_64-unknown-linux-gnu Rust version: rustc 1.96.0 (ac68faa20 2026-05-25) License: MIT OR Apache-2.0 Homepage: https://static-web-server.net LISTEN 0 128 *:443 *:* users:(("static-web-serv",pid=2793,fd=9)) LISTEN 0 128 *:80 *:* users:(("static-web-serv",pid=2794,fd=9))Confirm HTTPS and the per-instance certificate
Static Web Server answers HTTPS on port 443 directly. Request the site over HTTPS on the loopback address, then inspect the certificate. The -k flag accepts the self-signed certificate.
curl -ksI https://127.0.0.1/
sudo openssl x509 -in /etc/static-web-server/certs/sws.crt -noout -subject -issuer -datesThe response is served over HTTP/2 with the security headers applied, and the certificate subject is sac-static-web-server, unique to this VM:
HTTP/2 200 content-type: text/html; charset=utf-8 cache-control: max-age=86400 strict-transport-security: max-age=63072000; includeSubDomains; preload x-frame-options: DENY x-content-type-options: nosniff content-security-policy: frame-ancestors 'self' subject=C = GB, O = sac, CN = sac-static-web-server issuer=C = GB, O = sac, CN = sac-static-web-server notBefore=Jul 25 18:24:14 2026 GMT notAfter=Jul 22 18:24:14 2036 GMTThe certificate was generated on this instance's first boot, so no private key is shared with any other deployment.
Confirm HTTP, any domain serving, and the health endpoint
The same site is served on port 80. No host name is baked into the image, so both listeners answer for any Host header. The site works immediately on the VM's public IP and on any domain you later point at it.
curl -s -o /dev/null -w 'HTTP GET / -> %{http_code}\n' http://127.0.0.1/
curl -ks -o /dev/null -w 'HTTPS GET / -> %{http_code}\n' https://127.0.0.1/
curl -ks -o /dev/null -w 'HTTPS Host www.example.com -> %{http_code}\n' -H 'Host: www.example.com' https://127.0.0.1/
curl -s -o /dev/null -w 'HTTP Host www.example.com -> %{http_code}\n' -H 'Host: www.example.com' http://127.0.0.1/
curl -ks -o /dev/null -w 'HTTPS GET /health -> %{http_code}\n' https://127.0.0.1/healthEvery request returns 200, including requests that carry an arbitrary domain name in the Host header:
HTTP GET / -> 200 HTTPS GET / -> 200 HTTPS Host www.example.com -> 200 HTTP Host www.example.com -> 200 HTTPS GET /health -> 200The /health endpoint returns 200 and writes no log entry, which makes it ideal for Azure Load Balancer health probes and availability monitoring.
Confirm the hardening defaults
Directory listing is disabled, so a directory with no index file returns the custom 404 page rather than exposing its contents. Individual files inside it are still served by their path. The Prometheus metrics endpoint is off, and the security headers are present on every response.
curl -ks -o /dev/null -w 'GET /downloads/ -> %{http_code} (index-less directory)\n' https://127.0.0.1/downloads/
curl -ks -o /dev/null -w 'GET /downloads/README.txt -> %{http_code} (file inside it)\n' https://127.0.0.1/downloads/README.txt
curl -ks -o /dev/null -w 'GET /metrics -> %{http_code} (Prometheus endpoint off)\n' https://127.0.0.1/metrics
curl -ksI https://127.0.0.1/ | grep -iE 'strict-transport|x-frame|x-content-type|content-security'Expected output:
GET /downloads/ -> 404 (index-less directory) GET /downloads/README.txt -> 200 (file inside it) GET /metrics -> 404 (Prometheus endpoint off) strict-transport-security: max-age=63072000; includeSubDomains; preload x-frame-options: DENY x-content-type-options: nosniff content-security-policy: frame-ancestors 'self'Hidden dotfiles such as .git, .env and .htpasswd are never served either, and a path component that is a symbolic link is never followed out of the document root.
Review the configuration
The HTTPS listener is configured entirely by /etc/static-web-server/config.toml. The HTTP listener uses /etc/static-web-server/config-http.toml with the same hardening.
sudo grep -vE '^\s*#|^\s*$' /etc/static-web-server/config.tomlThe effective settings are:
[general] host = "::" port = 443 root = "/var/www/static" log-level = "info" http2 = true http2-tls-cert = "/etc/static-web-server/certs/sws.crt" http2-tls-key = "/etc/static-web-server/certs/sws.key" security-headers = true directory-listing = false ignore-hidden-files = true disable-symlinks = true index-files = "index.html, index.htm" cache-control-headers = true compression = true compression-level = "default" compression-static = true redirect-trailing-slash = true text-charset = true page404 = "/var/www/static/404.html" page50x = "/var/www/static/50x.html" health = true metrics = false threads-multiplier = 1 grace-period = 5Serve your own site
Replace the SecureAnyCloud landing page with your own site. Write your files into the document root /var/www/static, or point root at a different directory in both configuration files. Keep the content readable by the sws account. The commands below are illustrative, so substitute your own content.
# copy your static site into the document root, then fix ownership:
#
sudo rsync -a ./my-site/ /var/www/static/
#
sudo chown -R root:sws /var/www/static
#
sudo find /var/www/static -type d -exec chmod 0755 {} +
#
sudo find /var/www/static -type f -exec chmod 0644 {} +
#
# or change `root` in BOTH config files to another path, then apply:
#
sudo systemctl restart static-web-server.service static-web-server-http.serviceFor a single-page application, set page-fallback to your index.html. Client-side routes that do not exist on disk are then served the application shell with a 200 status:
# add to the [general] section of BOTH config files:
# page-fallback = "/var/www/static/index.html"
# then apply:
#
sudo systemctl restart static-web-server.service static-web-server-http.serviceInstall a browser trusted certificate for your domain
The image ships with a self-signed certificate so HTTPS works immediately. To get a browser-trusted certificate, point your domain's DNS A record at this VM's public IP and obtain a certificate for <your-domain> from a certificate authority. Then point Static Web Server at the new certificate and key.
# 1) obtain a certificate + key for <your-domain> (for example with acme.sh or certbot)
# 2) install them and set in /etc/static-web-server/config.toml:
# http2-tls-cert = "/etc/static-web-server/certs/<your-domain>.crt"
# http2-tls-key = "/etc/static-web-server/certs/<your-domain>.key"
# 3) keep the key readable by the sws account only:
#
sudo chown root:sws /etc/static-web-server/certs/<your-domain>.key
#
sudo chmod 0640 /etc/static-web-server/certs/<your-domain>.key
# 4) apply the change:
#
sudo systemctl restart static-web-server.serviceStatic Web Server loads the certificate at start-up, so restart the service to serve a replacement certificate.
Redirect HTTP to HTTPS (optional)
Once your domain resolves to this VM, you can turn port 80 into a permanent redirect instead of a second copy of the site. The redirect sends every request to one fixed canonical host and rejects any Host header that is not on its allow list with 400 Bad Request. Enable it only once you know your domain, and list every host name you serve.
# 1) stop the plain HTTP listener so port 80 is free for the redirect server:
#
sudo systemctl disable --now static-web-server-http.service
# 2) uncomment and edit these lines in /etc/static-web-server/config.toml,
# replacing the placeholder host names with your own:
# https-redirect = true
# https-redirect-host = "www.example.com"
# https-redirect-from-port = 80
# https-redirect-from-hosts = "www.example.com,example.com"
# 3) apply the change:
#
sudo systemctl restart static-web-server.serviceTo go back to serving the site on both ports, comment the four lines out again and run sudo systemctl enable --now static-web-server-http.service.
Optional features
Static Web Server supports several features that are available but not enabled by default. Add them to the [general] or [advanced] sections of the configuration files and restart the services.
# HTTP basic authentication (supply your own bcrypt hash; nothing is seeded in the image):
# basic-auth = "myuser:$2y$10$<your-own-bcrypt-hash>"
#
# Prometheus metrics — an unauthenticated endpoint, so restrict it at the network layer first:
# metrics = true
#
# Directory listing for a downloads site:
# directory-listing = true
# directory-listing-order = 1
#
# CORS for an assets or API origin:
# cors-allow-origins = "https://app.internal.test"
#
# Custom headers, redirects, rewrites and virtual hosts live in [advanced];
# see https://static-web-server.net/ for the full reference.Security recommendations
Restrict the NSG to your use case. Allow TCP 443 and TCP 80 only from the networks that need to reach the site, and keep TCP 22 restricted to your administration network.
Replace the self-signed certificate for public sites. The per-VM self-signed certificate secures the transport immediately, but browsers warn on it. Install a CA-issued certificate as in Step 10 for public-facing sites.
Keep directory listing disabled. The SecureAnyCloud default returns the 404 page for a directory with no index file, so directory contents are never exposed. Enable listing only for a path where you deliberately want it.
Leave the metrics endpoint off unless you need it. /metrics is unauthenticated. If you enable it, restrict access at the network layer.
Never place secrets in the document root. Dotfiles are ignored and symbolic links are not followed, but anything else under /var/www/static is public.
Keep the OS patched. Unattended security upgrades remain enabled on the running VM.
Support and Licensing
Static Web Server is an open source project dual-licensed under the Apache License 2.0 or the MIT License, at your option. This SecureAnyCloud image installs the unmodified upstream release binary, verified against its published SHA256 checksum, and ships both licence texts at /usr/share/doc/static-web-server/.
SecureAnyCloud provides the packaging, the systemd hardening, the secure-by-default TLS configuration, the per-instance certificate automation, the paired deploy guide, and 24/7 support with a guaranteed 24 hour response SLA.
SecureAnyCloud is not affiliated with or endorsed by the Static Web Server project. Static Web Server is a mark of its respective owner and is used here only to identify the software.
Deploy on Azure
Find Static Web Server on Ubuntu 24.04 LTS on the Azure Marketplace, published by SecureAnyCloud. Deploy from the Portal or the Azure CLI as shown above.
Need Help?
Email sales@secureanycloud.com for deployment help, configuration questions, or licensing enquiries.
Deploy on Azure
Launch Static Web Server on Ubuntu 24.04 LTS on Azure with 24/7 support from SecureAnyCloud.
View this image
Need Help?
Our support team is available 24/7.
sales@secureanycloud.com