How-to

HTTPS with Let's Encrypt

Get and renew certificates from Let’s Encrypt automatically, test against the staging CA first, then redirect HTTP to HTTPS and www. to the bare domain.

ScalWS has its own ACME client (ADR-0018); no certbot is needed. It uses the HTTP-01 challenge only.

Prerequisites

  • DNS A/AAAA records of every name pointing at the server, including www.<domain> when the site serves or redirects it.
  • Ports 80 and 443 reachable from the internet and ending at ScalWS (not at another web server or a load balancer that redirects). The package’s first install opens them in an active ufw or a running firewalld; otherwise open them yourself (firewall).
  • No wildcard names, IP addresses or localhost: they cannot get HTTP-01 certificates. Use a certificate file for them (see TLS).

1. Listeners and server.acme, against staging first

Edit /etc/scalws/scalws.yaml. Three things are required: a plain-HTTP listener for the challenges, a TLS listener with acme: true, and server.acme:

include: sites.d/*.yaml
server:
  listeners:
    - {name: http, address: "0.0.0.0:80"}
    - {name: https, address: "0.0.0.0:443", tls: {acme: true}}
  acme:
    enabled: true
    directory: https://acme-staging-v02.api.letsencrypt.org/directory
    state_dir: /var/lib/scalws/acme-staging
    contact: ["mailto:[email protected]"]
    terms_of_service_agreed: true

Apply. A new listener needs a restart under scalwsd.service; the controller binds it on reload:

sudo scalwsctl check
sudo scalws-controller reload        # under scalws-controller
sudo systemctl restart scalwsd       # under scalwsd.service

2. The site

With server.acme enabled, every site file without a tls key gets tls: auto: a certificate for all its names. scalwsctl site add needs no extra option:

sudo scalwsctl site add example.com --root /srv/www/example.com
# /etc/scalws/sites.d/example.com.yaml
domain: example.com
root: /srv/www/example.com
www: redirect

With www: redirect (the default of site add for a name with one dot), www.example.com answers with a 301 to https://example.com, path and query kept. That redirect is an application of its own with its own certificate, so www.example.com needs DNS too. www: alias serves www.example.com as part of the site (one certificate for both names); www: none does not serve it.

In scalws.yaml itself the equivalent is acme: true on the application.

3. Watch the order

scalwsctl certs                                           # under scalwsd.service
scalwsctl --socket /run/scalws/web-0/admin.sock certs     # under scalws-controller

certs lists each ACME application with its state (ok, pending, error, missing), days left, names, and the last error with the time of the next retry. Log lines use the target scalws::acme:

journalctl -u scalwsd | grep scalws::acme
sudo grep '"scalws::acme"' /var/lib/scalws-controller/logs/web-0.log

A new certificate is activated without a restart: new handshakes get it, existing connections keep theirs. Staging certificates are not trusted by browsers; curl -k https://example.com/ shows that the chain is in place.

4. Switch to production

Remove the directory and state_dir lines (the defaults are the production CA and /var/lib/scalws/acme), check and reload:

include: sites.d/*.yaml
server:
  listeners:
    - {name: http, address: "0.0.0.0:80"}
    - {name: https, address: "0.0.0.0:443", tls: {acme: true}}
  acme:
    enabled: true
    contact: ["mailto:[email protected]"]
    terms_of_service_agreed: true
sudo scalwsctl check
sudo scalws-controller reload        # or: sudo scalwsctl reload
curl -sI https://example.com/

5. Redirect HTTP to HTTPS

Once certs shows ok, add https_redirect to the site:

domain: example.com
root: /srv/www/example.com
www: redirect
https_redirect: true

(site add ... --https-redirect sets it for new sites.) Plain-HTTP requests then get a 301 to the same host, path and query over https:// (always port 443). ACME challenges are still answered over HTTP, so renewals keep working. ScalWS does not add Strict-Transport-Security; send it from the application or with Header set Strict-Transport-Security "max-age=31536000" in an .htaccess file.

Renewals

Nothing to schedule. The certificate manager checks every ACME application at start, every check_interval (default 12h) and after every configuration change, and orders a new certificate when:

  • the application has none yet,
  • its names changed (an added alias, www changed), or
  • it expires within renew_before (default 30d).

Under scalws-controller the instances share the state directory: one places the order, all of them answer the challenge and load the certificate. The metric scalws_acme_certificate_expiry_seconds{tenant,app} holds each expiry time, for alerts.

Back up the state directory yourself: scalwsctl backup does not include it.

Common problems

  • state stays error: DNS does not point here, port 80 does not reach ScalWS, or a rate limit was hit. A failed order is retried after 5 minutes, then with doubling delays up to 24 hours. The delay is kept in the server’s memory; after fixing DNS or the firewall, sudo systemctl restart scalwsd (sudo systemctl restart scalws-controller under the controller) retries at once.
  • ACME HTTP-01 needs a plain HTTP listener: add the port-80 listener.
  • ACME is enabled but no TLS listener has acme: true: add tls: {acme: true} to the HTTPS listener.
  • must be true (the CA's terms of service are accepted on account creation): set terms_of_service_agreed: true.
  • wildcard domains need DNS-01, which is not supported: use a certificate file and tls: manual for that site.
  • Browsers warn after switching to production: the staging certificate is still in use because state_dir was not changed back; remove the state_dir line.
  • Handshake fails before the first certificate: until an application has its first certificate, its names get the listener’s default certificate if there is one, and a failed handshake otherwise.

See also: TLS, quick start, troubleshooting.