Static Web Server 2.43.0 on AWS User Guide
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:8080only. 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
.gitor.envare 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/.zstfiles 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
swsaccount 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/staticowned by root, and never put secrets,.envfiles 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 upgradeapplies 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