Reverse proxy and HTTPS
Lantern serves plain HTTP on port 3000. Something in front of it has to give it HTTPS: a reverse proxy with a certificate, such as Traefik or Caddy, or Tailscale for a household that keeps Lantern private. This guide sets up each of them. Installing Lantern Home is the rest of an install.
Why HTTPS is required
Section titled “Why HTTPS is required”Over plain HTTP, browsers turn off what a family hub depends on:
- the service worker behind offline mode;
- Web Push notifications;
- the wake lock that keeps a display’s screen on;
- the
Securecookies that Lantern’s sign-in and displays use.
Lantern still opens over HTTP from another machine, with a notice on every page saying so (SET-5). It’s for trying it out, not for living with.
Never publish Lantern’s port to the internet. Only the proxy, or Tailscale, should reach it.
What every setup needs
Section titled “What every setup needs”Setting in .env | Why |
|---|---|
APP_BASE_URL | The HTTPS address people use, such as https://lantern.example.com. Links, cookies, and sign-in are built from it, never from what a request claims |
APP_PUBLISH | 127.0.0.1:3000 when the proxy runs on the same machine, so port 3000 isn’t open to the network. A proxy in a container on the same Docker network reaches web:3000 directly, so the port isn’t needed at all |
APP_TRUST_PROXY | Where the proxy’s requests come from, as Lantern sees them. Lantern then reads each person’s own address from X-Forwarded-For, which sign-in’s rate limits are counted by. Leave it empty and every sign-in is counted together: safe, but one person’s typing slows everyone else’s |
To find the address, look at the Docker network Lantern’s containers are on:
docker network inspect lantern_default -f '{{range .IPAM.Config}}{{.Subnet}} {{end}}'lantern_default is the default network of a project in a folder named lantern. A proxy on the host reaches Lantern through that network’s gateway, and a proxy container through its own address on the network. Its subnet, such as 172.18.0.0/16, covers both. Never trust every address (0.0.0.0/0): anyone could then claim any address and dodge the limits.
The stream. Every screen keeps one request open at /api/v1/stream, through which changes arrive (SPEC Section 4.4). The proxy must pass it through as it comes, without buffering. Lantern sends a heartbeat every 25 seconds, so an idle timeout of a minute or more is enough. Traefik and Caddy do both by default.
Headers. Lantern sends HSTS, nosniff, Referrer-Policy, and X-Frame-Options itself (docs/security-checklist.md), so the proxy needn’t add them. It must accept TLS 1.2 or later only, which Traefik and Caddy do by default.
Traefik
Section titled “Traefik”For a Traefik that already runs in Docker with a certificate resolver (here letsencrypt, on an entry point websecure) and a network of its own (here traefik). Add a docker-compose.override.yml beside Lantern’s docker-compose.yml, which Compose merges in, so the release’s file stays as it came:
# docker-compose.override.yml: Lantern behind Traefik.services: web: networks: [default, traefik] labels: - traefik.enable=true - traefik.docker.network=traefik - traefik.http.routers.lantern.rule=Host(`lantern.example.com`) - traefik.http.routers.lantern.entrypoints=websecure - traefik.http.routers.lantern.tls.certresolver=letsencrypt - traefik.http.services.lantern.loadbalancer.server.port=3000
networks: traefik: external: trueIn .env:
-
APP_BASE_URL=https://lantern.example.com; -
APP_PUBLISH=127.0.0.1:3000, since Traefik reacheswebon its network; -
APP_TRUST_PROXYset to thetraefiknetwork’s subnet:Terminal window docker network inspect traefik -f '{{range .IPAM.Config}}{{.Subnet}} {{end}}'
Then docker compose up -d.
Caddy gets a certificate from Let’s Encrypt by itself, so the name must point at the machine, with ports 80 and 443 open to it. With Caddy on the same machine, the whole Caddyfile is:
lantern.example.com { reverse_proxy 127.0.0.1:3000}In .env:
APP_BASE_URL=https://lantern.example.com;APP_PUBLISH=127.0.0.1:3000;APP_TRUST_PROXYset to the subnet oflantern_default, as above.
Reload Caddy (systemctl reload caddy) and start Lantern.
Caddy streams /api/v1/stream as it comes, since it flushes text/event-stream responses at once, and it sets X-Forwarded-For itself, ignoring one a client sent.
Tailscale
Section titled “Tailscale”For a household that keeps Lantern on its tailnet, with nothing open to the internet. Every phone, computer, and display then needs Tailscale too.
-
Turn on HTTPS certificates for the tailnet.
tailscale serveasks for it the first time if it isn’t on. -
On the machine that runs Lantern:
Terminal window tailscale serve --bg localhost:3000It serves
https://<machine>.<tailnet>.ts.netwith a certificate Tailscale manages.tailscale serve --https=443 offstops it. -
In
.env:APP_BASE_URL=https://<machine>.<tailnet>.ts.net;APP_PUBLISH=127.0.0.1:3000;APP_TRUST_PROXYleft empty: Tailscale doesn’t document sendingX-Forwarded-For, and everyone who can reach Lantern is on the tailnet anyway.
Checking it
Section titled “Checking it”https://lantern.example.com/healthzanswers with Lantern’s version.- A browser shows no HTTPS notice on the sign-in page.
- After signing in, More → Notifications offers notifications. They’re unavailable over plain HTTP.
- A change made on one device shows on another within a couple of seconds. If it doesn’t, the proxy is buffering the stream.