Self-host FUTO Notes sync
Run the FUTO Notes sync server on your own box and your devices keep each other up to date without anyone else in the middle. Sync is end-to-end encrypted: every note is encrypted on your device before upload, and the server stores sealed blobs it cannot open. See how the encryption works below.
There is no hosted service today, so self-hosting is how you get sync. It is a single-user server with one sync password.
Install
You need a Linux box with Docker and Docker Compose v2, on amd64 or arm64.
curl -fsSL https://notes.futo.tech/install-server.sh | sh
The installer asks for an install directory, a port (3005 by default), and a sync password, then starts one container and checks /health before it finishes. SQLite runs inside the container; the database and your encrypted blobs both live in a data directory under the install directory.
Connect the app
In the app, open Settings → Self-hosted sync → Server URL. On your own network, enter:
http://<your-server-ip>:3005
Use the same sync password on every device. That password does two jobs: it signs the app in to your server, and it unlocks the encryption key for your notes. If you lose it, nothing on the server can be decrypted, and there is no reset.
Reach it from outside your network
The server speaks plain HTTP, which is fine on a LAN. To reach it from anywhere, put a TLS reverse proxy in front of it.
Tailscale Funnel is the least work: it gives the machine an HTTPS name without opening a port on your router. If you already run a public server, Caddy handles the certificate for you:
notes.example.com {
reverse_proxy localhost:3005
}
Then point the app at the HTTPS URL instead.
Upgrade and back up
From the install directory:
docker compose pull && docker compose up -d
To back up, stop the container, copy the whole data directory somewhere safe, and start it again. That directory is everything the server has.
How the encryption works
The app does all of the cryptography. The server is a dumb store for bytes it cannot read, and it does the same job whether or not it is trusted.
- When you first connect, the app generates a random 256-bit vault key on your device.
- Every note is packed together with its filename and folder path, then encrypted with AES-256-GCM under the vault key. Each blob gets a fresh random nonce, and the authentication tag means a tampered or truncated blob is rejected rather than decrypted wrong.
- The vault key is wrapped with a key derived from your sync password using PBKDF2-SHA-256 with 100,000 iterations and a random salt. Only this wrapped copy, the salt, and the KDF name are stored on the server. A new device downloads the wrapped key and unwraps it locally with your password.
- The server never receives the vault key or a decryption key, and it never decrypts anything.
What the server can see is the sync coordination data: how many objects there are, the size of each encrypted blob, when each one changed, and opaque IDs. It cannot see note text, titles, filenames, folder names, tags, or links. That means a copy of the data directory or a backup of it is worthless without your password.
Three honest caveats. The sync password is also the login password, so whoever runs the server is handed it when a device signs in; in a self-hosted setup that person is you. The installer writes that password into the server’s .env file, so protect that file: it is created mode 600, it should not sit in the same backup as the data directory, and you can store a scrypt hash in FUTO_NOTES_PASSWORD_HASH instead of the password itself (the server README covers it). And the app speaks plain HTTP unless you put TLS in front of it; TLS protects the password and session token in transit, while the notes are already encrypted either way.
Without Docker
Release binaries are published for Linux, macOS, and Windows if you would rather run the server directly, with a systemd unit example. The server README covers that path.
The full README and the source are at gitlab.futo.org/futo-notes/futo-notes-server.