Application Servers AWS

Static Web Server 2.43.0 on AWS User Guide

| Product: Static Web Server 2.43.0 on Ubuntu 24.04 on AWS

Overview

This guide covers launching and using the Static Web Server 2.43.0 on Ubuntu 24.04 AMI from cloudimg on Amazon EC2. It is one version of the cloudimg web servers listing on AWS Marketplace; you pick it as the software version when you subscribe and launch.

Static Web Server, also known as SWS, is a fast, asynchronous web server for static files written in Rust. It is a single self contained binary with no runtime dependencies, so it starts instantly and uses very little memory. You point it at a directory and it serves your website, documentation or single page application build output, with on the fly compression, cache control headers, custom error pages and a quiet health endpoint for load balancers.

How this image is put together, and what cloudimg adds:

  • HTTPS from the first boot. On your instance's first boot a certificate is generated for this instance alone, and the site is served on port 443 over HTTP/2. Port 80 redirects to HTTPS. No certificate or key is baked into the image.
  • nginx as the public edge, Static Web Server behind it. Static Web Server listens on 127.0.0.1:8080 only. nginx terminates TLS on ports 80 and 443 and passes requests to it. The reason is a published weakness in every Static Web Server 2.x release (GHSA-2fch-9x9r-mxmv): the server never closes an idle or stalled connection, so a single client holding connections open can take a directly exposed server offline. nginx closes connections that send nothing, send slowly, sit idle or stop reading, and allows at most 64 connections per client address.
  • Only your host name is served. The site answers for the name it was configured with (your instance's public DNS name by default, from the EC2 instance metadata service) and for the instance's own IP addresses. Requests for any other host name are dropped.
  • Hardened defaults. Directory listing is off, dotfiles such as .git or .env are never served, symbolic links are never followed, the Prometheus metrics endpoint is off, and security headers (Strict-Transport-Security, X-Frame-Options, X-Content-Type-Options, Content-Security-Policy, Referrer-Policy) are sent on every response. Serving pre-compressed .gz/.br/.zst files is off, because in 2.43.0 that feature can be tricked by a symbolic link into serving a file outside the web root (GHSA-cg27-w6jh-934r); responses are still compressed on the fly.
  • Locked down services. Static Web Server runs as the unprivileged sws account with no capabilities, a read only view of the system and no network access beyond the local machine. Both services are blocked from the instance metadata service, and the AMI requires IMDSv2.
  • Fail closed. nginx will not start until first boot has generated the certificate, so nothing is served before the instance is set up.
  • The official release, verified. The Static Web Server 2.43.0 release is checked against a pinned SHA-256 checksum before it is installed. Ubuntu 24.04 LTS is fully patched at build time, with unattended security upgrades switched on.

Static Web Server has no login and no administration console, so there is no password on this image.

What is on the image: Static Web Server 2.43.0 (/usr/local/bin/static-web-server, licences in /usr/share/doc/static-web-server), nginx, Ubuntu 24.04 LTS, cloud-init and the AWS Systems Manager agent.

Default user: ubuntu · Architecture: x86_64 · Recommended instance type: m5.large

Prerequisites

  • An AWS account subscribed to the listing, an EC2 key pair and a VPC subnet with a route to the internet (or the private route your users reach it by).
  • A security group that allows TCP 22 (SSH) from your own networks, and TCP 443 (the site) and TCP 80 (redirects to HTTPS) from your visitors.

Step 1: Launch the instance

In the AWS Marketplace listing choose Continue to Subscribe, then Continue to Configuration, pick Static Web Server 2.43.0 on Ubuntu 24.04 as the software version and your Region, then Launch through EC2. Choose m5.large (a small static site runs comfortably on less), your key pair and a security group as above.

With the AWS CLI (replace the values in angle brackets):

aws ec2 run-instances --region <region> --image-id <ami-id-from-the-listing> \
  --instance-type m5.large --key-name <your-key-pair> \
  --security-group-ids <your-security-group> --subnet-id <your-subnet> \
  --metadata-options HttpTokens=required,HttpEndpoint=enabled \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=static-web-server}]'

Step 2: Connect to your instance

Version SSH user
Static Web Server 2.43.0 on Ubuntu 24.04 ubuntu
ssh -i <your-key.pem> ubuntu@<public-ip>

Step 3: Check the services

First boot generates the certificate and works out the host name, then Static Web Server and nginx start:

systemctl is-active static-web-server-firstboot static-web-server nginx
static-web-server --version | sed -n 1,2p
active
active
active
Version:      2.43.0
Built:        2026-06-10 23:25:42 +02:00

Static Web Server listens on the loopback address only; nginx owns ports 80 and 443:

sudo ss -lntp | grep -E ':(80|443|8080) ' | awk '{print $4, $6}' | sed -E 's/users:\(\("([a-z-]+)".*/\1/'
127.0.0.1:8080 static-web-serv
0.0.0.0:443 nginx
0.0.0.0:80 nginx
[::]:443 nginx
[::]:80 nginx

Step 4: Find your site's address

The address the site is served at is in an information file written on first boot (it holds no secret):

sudo grep -E '^(SITE_URL|HEALTH_URL)=' /root/static-web-server-info.txt
SITE_URL=https://ec2-35-171-19-96.compute-1.amazonaws.com/
HEALTH_URL=https://ec2-35-171-19-96.compute-1.amazonaws.com/health

Open SITE_URL in your browser. The certificate is self signed, so the browser asks you to confirm the first time; Step 8 shows how to use your own name and certificate. You will see the cloudimg landing page, served from /var/www/static.

Step 5: Check HTTPS, the redirect and the host name check

These commands run on the instance and talk to nginx over the loopback address, using the configured host name:

H=$(sudo grep '^SITE_URL=' /root/static-web-server-info.txt | sed -E 's#^SITE_URL=https://([^/]+)/$#\1#')
curl -sk --resolve "$H:443:127.0.0.1" -o /dev/null -w 'https://%{url.host}/  -> %{http_code} over HTTP/%{http_version}\n' "https://$H/"
curl -s --resolve "$H:80:127.0.0.1" -o /dev/null -w 'http://%{url.host}/   -> %{http_code} %{redirect_url}\n' "http://$H/"
curl -sk --resolve "$H:443:127.0.0.1" -o /dev/null -w '/health -> %{http_code}\n' "https://$H/health"
curl -sk --resolve unknown.example:443:127.0.0.1 -o /dev/null -w 'unknown host -> %{http_code} (connection closed)\n' https://unknown.example/ || true
https://ec2-35-171-19-96.compute-1.amazonaws.com/  -> 200 over HTTP/2
http://ec2-35-171-19-96.compute-1.amazonaws.com/   -> 301 https://ec2-35-171-19-96.compute-1.amazonaws.com/
/health -> 200
unknown host -> 000 (connection closed)

The certificate was generated on this instance's first boot and names the host and the instance's addresses:

H=$(sudo grep '^SITE_URL=' /root/static-web-server-info.txt | sed -E 's#^SITE_URL=https://([^/]+)/$#\1#')
echo | openssl s_client -connect 127.0.0.1:443 -servername "$H" 2>/dev/null | openssl x509 -noout -subject -ext subjectAltName
subject=CN = ec2-35-171-19-96.compute-1.amazonaws.com, O = cloudimg
X509v3 Subject Alternative Name: 
    DNS:ec2-35-171-19-96.compute-1.amazonaws.com, IP Address:35.171.19.96, IP Address:172.31.93.2, IP Address:127.0.0.1, DNS:localhost

Step 6: Check the hardening

Security headers on every response:

H=$(sudo grep '^SITE_URL=' /root/static-web-server-info.txt | sed -E 's#^SITE_URL=https://([^/]+)/$#\1#')
curl -skI --resolve "$H:443:127.0.0.1" "https://$H/" | grep -iE '^(strict-transport|x-frame|x-content-type|content-security|referrer-policy)'
strict-transport-security: max-age=63072000; includeSubDomains; preload
x-frame-options: DENY
x-content-type-options: nosniff
content-security-policy: frame-ancestors 'self'
referrer-policy: strict-origin-when-cross-origin

A directory with no index file is not listed, a file inside it is served, and the metrics endpoint and dotfiles are not reachable:

H=$(sudo grep '^SITE_URL=' /root/static-web-server-info.txt | sed -E 's#^SITE_URL=https://([^/]+)/$#\1#')
for p in /downloads/ /downloads/README.txt /metrics /.env /no-such-page; do
  printf '%-22s %s\n' "$p" "$(curl -sk --resolve "$H:443:127.0.0.1" -o /dev/null -w '%{http_code}' "https://$H$p")"
done
/downloads/            404
/downloads/README.txt  200
/metrics               404
/.env                  404
/no-such-page          404

The connection limits nginx enforces in front of Static Web Server, and the account Static Web Server runs as:

sudo nginx -T 2>/dev/null | grep -E '^\s*(client_header_timeout|client_body_timeout|keepalive_timeout|send_timeout|limit_conn |reset_timedout)' | sort -u
ps -o user=,comm= -C static-web-server
systemctl show -p IPAddressDeny -p IPAddressAllow static-web-server.service
client_body_timeout   15s;
client_header_timeout 15s;
keepalive_timeout     30s;
limit_conn sws_perip 64;
reset_timedout_connection on;
send_timeout          30s;
sws      static-web-serv
IPAddressAllow=127.0.0.0/8 ::1/128
IPAddressDeny=::/0 0.0.0.0/0

Step 7: Publish your own site

Static Web Server serves whatever is in /var/www/static. Files there are owned by root and readable by everyone, and the server can read but never change them. To try it, add a page, fetch it over HTTPS and remove it again:

H=$(sudo grep '^SITE_URL=' /root/static-web-server-info.txt | sed -E 's#^SITE_URL=https://([^/]+)/$#\1#')
echo '<!DOCTYPE html><title>Hello</title><h1>Hello from my site</h1>' | sudo tee /var/www/static/hello.html >/dev/null
curl -sk --resolve "$H:443:127.0.0.1" "https://$H/hello.html"; echo
sudo rm -f /var/www/static/hello.html
curl -sk --resolve "$H:443:127.0.0.1" -o /dev/null -w 'after removal: %{http_code}\n' "https://$H/hello.html"
<!DOCTYPE html><title>Hello</title><h1>Hello from my site</h1>
after removal: 404

To publish a whole site, copy your build output from your own machine and fix the permissions on the instance:

# from your machine:
#   rsync -a --delete -e "ssh -i <your-key.pem>" ./public/ ubuntu@<public-ip>:/tmp/site/
# then on the instance:
#   sudo rsync -a --delete --exclude 404.html --exclude 50x.html /tmp/site/ /var/www/static/
#   sudo chown -R root:root /var/www/static
#   sudo find /var/www/static -type d -exec chmod 0755 {} +
#   sudo find /var/www/static -type f -exec chmod 0644 {} +

New files are served at once; no restart is needed. For a single page application whose router handles every path, add page-fallback = "/var/www/static/index.html" to the [general] section of /etc/static-web-server/config.toml and run sudo systemctl restart static-web-server.

Step 8: Your own DNS name and certificate

Point your DNS name at the instance (an Elastic IP keeps the address stable across stops and starts), then tell the instance its name. This records the name, generates a new self signed certificate for it and reloads nginx; port 80 then redirects to the new name:

sudo sws-set-hostname <your-domain>

To use a certificate your visitors' browsers trust, obtain one for your name (for example with certbot or acme.sh) and point nginx at it in /etc/nginx/sites-available/cloudimg-static-web-server (the two ssl_certificate lines of the second port 443 server block), then check and reload:

sudo nginx -t && sudo systemctl reload nginx

Step 9: Configuration reference

What Where
Your files /var/www/static
Static Web Server settings /etc/static-web-server/config.toml (restart static-web-server after a change)
nginx site (ports 80/443, host names, certificate) /etc/nginx/sites-available/cloudimg-static-web-server
nginx connection limits /etc/nginx/static-web-server-limits.conf
Served host names /etc/nginx/static-web-server-host.conf (written by sws-set-hostname)
Certificate and key /etc/ssl/static-web-server/ (key readable by root only)
First boot script /usr/local/sbin/static-web-server-firstboot.sh
Site address note /root/static-web-server-info.txt
Licences and release checksum /usr/share/doc/static-web-server/

The settings that matter most:

grep -E '^(host|port|root|security-headers|directory-listing|ignore-hidden-files|disable-symlinks|compression|compression-static|accept-markdown|health|metrics) ' /etc/static-web-server/config.toml
host = "127.0.0.1"
port = 8080
root = "/var/www/static"
security-headers = true
directory-listing = false
ignore-hidden-files = true
disable-symlinks = true
compression-static = false
accept-markdown = false
compression = true
health = true
metrics = false

Static Web Server also offers custom headers, redirects, rewrites, CORS and virtual hosts in the [advanced] section; see the Static Web Server documentation. Keep host = "127.0.0.1": the server must stay behind nginx.

Leave these features off on 2.43.0: compression-static, accept-markdown, metrics and basic-auth. Each has a published security advisory that is fixed only in Static Web Server 2.44.0 or later. If you need password protection, add it in nginx (auth_basic in the port 443 server block) rather than in Static Web Server.

Step 10: Security recommendations

  • Allow port 22 in the security group from your own networks only.
  • Use your own DNS name and a certificate your visitors trust (Step 8).
  • Keep everything under /var/www/static owned by root, and never put secrets, .env files or private keys in the web root.
  • Keep the operating system patched; unattended security upgrades are on, and sudo apt-get update && sudo apt-get upgrade applies everything else.

Support and licensing

Static Web Server is open source, dual licensed under the Apache License 2.0 and the MIT License; there is no per user or per CPU licence fee. The licences are in /usr/share/doc/static-web-server/. cloudimg is not affiliated with or endorsed by the Static Web Server project. The AMI price covers cloudimg's hardening, first boot setup and support.

  • Email: support@cloudimg.co.uk
  • Website: https://www.cloudimg.co.uk
  • Support: 24/7, with a 24 hour response SLA