Deployment & TLS
- Option A — behind a reverse proxy (default model)
- Option B — terminate TLS in the server (ACME / Let’s Encrypt)
DPoP and WebID/OIDC assume the storage is reached over TLS in production. There are two ways to get there — use one, not both.
Option A — behind a reverse proxy (default model)
The server terminates plain HTTP and sits behind a reverse proxy (nginx, Caddy, Traefik, …) that terminates HTTPS.
- Set
lws.base-urito the externalhttps://URL. DPoPhtu, WebIDs, ACLs and every minted IRI come from this value (not the internal request scheme/host), so everything is correct even though the app speaks HTTP behind the proxy. - Set
lws.behind-proxy=trueso the server trustsX-Forwarded-Proto/X-Forwarded-Host/Forwarded(RFC 7239) from the proxy — makingrequest.isSecure(), generated redirects and secure-cookie flags reflect the external HTTPS URL. Enable it only when a trusted proxy is in front and strips client-supplied forwarding headers (otherwise a client could spoof the scheme). - Set
lws.require-https=trueto fail fast ifbase-uriis nothttps://(loopback hosts stay exempt for local development).
Example nginx front end for a server on 127.0.0.1:8080:
server {
listen 443 ssl;
server_name storage.example;
# ssl_certificate / ssl_certificate_key ...
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme; # https
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
Option B — terminate TLS in the server (ACME / Let’s Encrypt)
The bare-Jetty launcher (com.ebremer.lws.server.JettyLauncher) can terminate TLS itself,
obtaining and renewing a certificate from an ACME CA (Let’s Encrypt by default) via
acme4j and the HTTP-01 challenge. (The Spring Boot entry point is
intended for the reverse-proxy model above; TLS is wired into the bare launcher.)
How it works. The launcher starts the HTTP connector (lws.tls.http-port, default 80),
provisions the certificate (serving the challenge at /.well-known/acme-challenge/*), then starts the
HTTPS connector (lws.tls.port, default 443). Plain-HTTP requests other than the challenge are
redirected to HTTPS. The account key, domain key and certificate are cached under lws.tls.dir, so
restarts reuse a still-valid certificate; a daemon checks twice daily and renews within
lws.tls.acme.renew-before-days of expiry, hot-reloading the TLS context with no restart.
Setup.
- Point DNS for your domain at the host, and make ports 80 and 443 publicly reachable (the CA
connects to port 80 to validate the HTTP-01 challenge). Run the launcher as a user permitted to
bind those ports (e.g.
setcap/authbind, a systemd socket, or root). -
Configure (in
lws.propertiesor via-Dlws.*):lws.base-uri=https://storage.example lws.tls.enabled=true lws.tls.acme.email=admin@storage.example lws.tls.acme.accept-terms-of-service=true # required; agrees to the CA's Terms of Service # lws.tls.acme.domains=storage.example # defaults to the lws.base-uri host # while testing, use the staging CA to avoid rate limits (its certs are NOT browser-trusted): # lws.tls.acme.directory-url=https://acme-staging-v02.api.letsencrypt.org/directory -
Run the bare-Jetty launcher (it lives in the same jar):
java -cp target/lws-server.jar -Dloader.main=com.ebremer.lws.server.JettyLauncher \ org.springframework.boot.loader.launch.PropertiesLauncher # or, from a source checkout: mvn -q exec:java -Dexec.mainClass=com.ebremer.lws.server.JettyLauncher
Notes. Start with the staging directory URL to validate the setup (Let’s Encrypt production
has strict rate limits); switch to production once it works, deleting lws.tls.dir so a fresh,
trusted certificate is ordered. Other challenge types (TLS-ALPN-01, DNS-01) and non-Let’s-Encrypt CAs
are not wired up — but you can point lws.tls.acme.directory-url at any ACME CA that supports
HTTP-01.