Speicher serves plain HTTP on port 3000. This page sets up a proxy, to serve HTTPS, for the Docker Compose file from the quick start guide. There are two options to set up the proxy:
- Caddy in the Compose file gets and renews certificates by itself. Use it if you do not plan to run any other services on the same server.
- nginx on the host, if you already run nginx there or want to use it for other services as well.
Before you start
- Choose the domain for Speicher, for example
docs.example.com, and point its DNS record at the server. In every step below, replacedocs.example.comwith your domain. - Open ports 80 and 443 on the server.
1. Set the public URL and the trusted proxy
In docker-compose.yaml, under environment: in the x-speicher block, set your domain as the public URL and add the trusted proxy:
SPEICHER_PUBLIC_URL: https://docs.example.com
SPEICHER_TRUST_PROXY: uniquelocal
Write the URL without a path or a trailing slash.
Speicher takes the client IP from X-Forwarded-For and the protocol from X-Forwarded-Proto only when the request comes from a trusted address. The proxy connects from a private IPv4 address: Caddy from its address on the Docker network, nginx on the host from the gateway of the Docker bridge. By setting SPEICHER_TRUST_PROXY to uniquelocal we trust all private IP addresses. For other values of SPEICHER_TRUST_PROXY, see Trusted proxies.
2. Set up the proxy
Caddy in the Compose file
-
In the
webservice, removeports, so only Caddy can reach Speicher. -
Add the Caddy service and its volumes to
docker-compose.yaml, next to the existing ones:services: caddy: image: caddy:2 ports: - '80:80' - '443:443' - '443:443/udp' volumes: - ./Caddyfile:/etc/caddy/Caddyfile:ro - caddy-data:/data - caddy-config:/config depends_on: - web restart: unless-stopped volumes: caddy-data: caddy-config: -
Create a
Caddyfilenext todocker-compose.yaml, with your domain in the first line:docs.example.com { reverse_proxy web:3000 } -
Start the containers:
docker compose up -d
Info
Caddy will request a certificate from Let's Encrypt on the first start. This only succeeds once the DNS record of your domain points at the server and ports 80 and 443 are open.
nginx on the host
-
In the
webservice, publish the port on127.0.0.1only, so nothing but nginx can reach Speicher:ports: - '127.0.0.1:3000:3000' -
Get a certificate for your domain, for example with Certbot.
-
Add a site to nginx. Replace
docs.example.comwith your domain, and the certificate paths with the ones your certificate tool created:server { listen 80; server_name docs.example.com; return 301 https://$host$request_uri; } server { listen 443 ssl; http2 on; server_name docs.example.com; ssl_certificate /etc/letsencrypt/live/docs.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/docs.example.com/privkey.pem; location / { proxy_pass http://127.0.0.1:3000; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; } } -
Reload nginx and start Speicher:
sudo nginx -t && sudo systemctl reload nginx docker compose up -d
Other proxies
Any reverse proxy works if it:
- serves HTTPS and forwards to port 3000 over HTTP
- sends
X-Forwarded-ForandX-Forwarded-Proto, and passes on the originalHostheader - is the only thing that can reach port 3000
Speicher uses no WebSockets or long-lived connections, so the proxy needs no special timeouts.
3. Check that it works
-
To check that Speicher answers over HTTPS, run:
curl https://docs.example.com/-/readyzIt answers with
{"status":"ready",…}once Speicher can serve requests. -
To check that Speicher uses your public URL, run:
curl https://docs.example.com/.well-known/oauth-authorization-serverThe
issuerin the answer must be your public URL.
If you use the GitHub App content source, GitHub must also reach the instance from the internet to deliver webhooks.
Continue with setting up the instance →
Trusted proxies
SPEICHER_TRUST_PROXY lists the addresses from which Speicher accepts the X-Forwarded-For and X-Forwarded-Proto headers. Give one or more of these values, separated by commas:
| Value | Trusts |
|---|---|
uniquelocal |
private network addresses: 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 and fc00::/7. Use it with Docker. |
loopback (default) |
127.0.0.1 and ::1, for a proxy on the same host when Speicher does not run in Docker |
linklocal |
169.254.0.0/16 and fe80::/10 |
| an IP address or subnet | that address only, for example the proxy's Docker network 172.18.0.0/16 |
In Docker, requests reach Speicher from a private Docker network address, even when the proxy runs on the same host, so the default loopback does not work there. With uniquelocal, make sure only the proxy can reach port 3000, as both setups above do. Otherwise anyone on your network could send forged headers.
Settings
| Variable | Default | Read by | Description |
|---|---|---|---|
SPEICHER_PUBLIC_URL |
— | web + worker | The public URL of this instance, used in webhook deliveries, magic links, and the sitemap. When unset, the request host is used instead. |
SPEICHER_TRUST_PROXY |
loopback |
web | Which proxies to believe about the client address and protocol: loopback, linklocal, uniquelocal, IP addresses or subnets, comma-separated. In Docker, set uniquelocal; true and hop counts are not accepted. |
SPEICHER_INSECURE_COOKIES |
false |
web | Omit the Secure flag on cookies, allowing them over HTTP. Never enable in production; without this flag, modern browsers refuse cookies over plain HTTP. |
PORT |
3000 |
web | TCP port the HTTP server listens on. |