Who Driftless is built for
The current release targets trusted deployments — your own family or personal devices on a server you control. Several design choices follow from that assumption, so it is worth understanding them before you expose the server more widely.
How clients authenticate
A device logs in once with a username and password and receives a bearer token, which it sends on every API call. The server stores only a hash of each token, never the token itself. Passwords are stored as bcrypt hashes. A device's stored token is as good as its password, so treat the client config file as a secret.
Tokens expire on idleness: after 180 days without syncing, a token stops working and the device has to sign in again. A device that keeps syncing keeps its token, however long it has held it — the clock measures silence, not age. Operators can shorten, lengthen, or disable the limit with -token-ttl, and because it is evaluated when the token is used, a change applies at once to tokens that already exist. Expired tokens are deleted by the server's hourly housekeeping pass.
Signing a device out
Waiting out an idle limit is not the only lever — you can revoke access immediately, at three levels:
- From the device:
driftless logoutrevokes that one token on the server and forgets it locally. Other devices on the same account keep syncing. If the server is unreachable, the local token is dropped anyway, so the command is never a no-op. - From the admin: sign out on the Users page revokes every token and admin session belonging to that user at once — the move for a lost or stolen device. Their files are untouched; each device signs in again with the existing password.
- By changing the password: resetting a user's password also revokes all of their tokens and sessions, so a compromised account is genuinely closed rather than merely given a new password. (An administrator resetting their own password stays signed in to the admin UI.)
Sign-in throttling
Password guessing is throttled per client address on both sign-in endpoints, POST /v1/login and the admin login. Each address gets a burst of 10 attempts and then one attempt back every 10 seconds; over that, the server answers 429 with a Retry-After hint. Adjust the burst with -login-burst, or set it to 0 to switch the throttle off.
Only sign-in is throttled. The sync endpoints are already token-authenticated and legitimately arrive in bursts — a first upload is thousands of requests — so rate-limiting them would break normal use.
One caveat matters if you run behind a reverse proxy: without -trust-proxy, every request appears to come from the proxy, so all your users share one bucket and a single attacker can lock everyone out. With it, the address is taken from X-Forwarded-For. Turn it on only when a proxy you control is the sole way in — a client connecting directly sets that header itself, and would get a fresh allowance for every guess.
How the admin is protected
The admin UI uses a separate server-side session cookie with a same-origin guard, independent of client tokens. Admin access and sync access cannot be exchanged for one another. Admin sessions expire on idleness too, after a week by default (-session-ttl); active use keeps a session alive.
Storage and de-duplication
Each user's chunks live in their own storage folder on the server, and every question the sync API answers about chunks — does it exist, may it be downloaded — is scoped to the account asking. Identical content is stored once per user: de-duplication works fully within an account (versions, copies, small edits) but deliberately does not span accounts. The upside is clean separation — one user can never detect, read, or affect another user's chunks, and deleting a user frees their space immediately. The cost is that the same file uploaded by two users is stored (and uploaded) twice, which is a fair trade for family-scale deployments. For content that should stay unreadable even to the server operator, optional per-folder end-to-end encryption remains on the roadmap.
Putting the server online safely
Driftless does not terminate TLS itself, so do not expose plain HTTP to the internet: a bearer token travels on every request, and on an unencrypted connection anyone on the path can read it. Two good options, in the order we recommend them.
A private network with Tailscale (recommended)
The simplest safe answer is not to be on the internet at all. Tailscale puts your server and your devices on one private WireGuard network: traffic between them is encrypted end to end, and nothing is published to the world — no ports forwarded, no certificate to renew, no login page for the internet to hammer.
- Install Tailscale on the machine running Driftless and sign it in (
tailscale up). Under Docker, the easiest arrangement is Tailscale on the host with the container's port published only to localhost or to the Tailscale interface. - Install Tailscale on each device that should sync and sign it in to the same account.
- Point clients at the server's Tailscale name, for example
driftless login -server http://server.tailnet-name.ts.net:8080 -user alice.
For HTTPS inside the tailnet — worth it, because browsers treat it as a secure origin and the admin's session cookie is then marked Secure — enable HTTPS certificates in your tailnet and run tailscale serve --bg 8080 on the server. Tailscale gets and renews the certificate and proxies to Driftless; clients then use https://server.tailnet-name.ts.net. Because that proxy is local and the only way in, start the server with -trust-proxy so sign-in throttling sees real client addresses.
A TLS-terminating reverse proxy
If the server must be reachable from anywhere — a phone on cellular, someone without Tailscale — put a proxy in front that owns the certificate. With Caddy, a whole configuration is two lines:
driftless.example.com {
reverse_proxy localhost:8080
}
Caddy obtains and renews the certificate automatically and sets X-Forwarded-For and X-Forwarded-Proto. Traefik, nginx, or any other proxy works the same way as long as it forwards both headers. Then:
- Start the server with
-trust-proxy(orDRIFTLESS_TRUST_PROXY=1) so sign-in throttling keys on the real client rather than the proxy. - Bind Driftless to localhost, or keep its port off the public interface, so nobody can reach it around the proxy — bypassing the proxy would mean plain HTTP and a forgeable client address.
- Point clients at the
https://address.X-Forwarded-Proto: httpsis what tells the server to mark the admin session cookieSecure. - Do not let the proxy buffer responses: the change-feed stream at
/v1/eventsis a long-lived Server-Sent Events connection. Caddy and Traefik stream by default; nginx needsproxy_buffering off;.
Either way, the server should sit behind something that provides HTTPS before it is reachable from outside your home or office.