Reverse Proxy
Behind a proxy, the server sees the proxy's connection, not the browser's. Tell it where the real URL and client address come from.
#Request URL
SvelteKit needs the public URL for url.origin, redirects and its CSRF check on
form actions. Pick one:
-
ORIGIN— simplest when the app lives at a single public URL:ORIGIN=https://my.siteRead at runtime; on SvelteKit 3 it overrides
paths.origin. -
Forwarded headers — when the app answers on several hosts:
PROTOCOL_HEADER=x-forwarded-proto HOST_HEADER=x-forwarded-host
With neither set, the protocol defaults to https and the host comes from the
Host header.
Remote function calls (/_app/remote/…) are the exception: when the browser's
Origin matches the request's host, that origin is used as-is, so they pass
SvelteKit's CSRF check even when ORIGIN points elsewhere.
#Client address
event.getClientAddress() returns the socket address by default. Behind a
proxy, read it from a header instead:
ADDRESS_HEADER=x-forwarded-for
XFF_DEPTH=1XFF_DEPTH is the number of trusted proxies in front of the app: the address is
taken that many entries from the right of X-Forwarded-For, so a client
can't spoof it by sending its own header. With ADDRESS_HEADER set, a request
missing that header throws — only set it when every request goes through the
proxy.
#Unix socket
Proxies on the same host can skip TCP:
SOCKET_PATH=/run/app.socklocation / {
proxy_pass http://unix:/run/app.sock;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}#WebSockets and SSE
Both work through the proxy as long as it forwards Upgrade / keeps long-lived
responses open. The server itself disables its idle timeout for
text/event-stream responses.
This guide lives in the project repo: edit it there, and this page follows within a day.