Why sync your shell history
Working across multiple machines should be seamless. In practice, the kubectl rollout undo deployment/api command you typed on your laptop is never in the CI server terminal, and the docker compose exec from three weeks ago is gone because history rotation overwrote older entries.
Atuin solves both problems at the root: it stores every command in SQLite with its context (timestamp, duration, exit code, directory), and it synchronises that database across your machines through a server you control. Search runs locally — no network latency when recalling a command — and sync happens in the background. The Ctrl+R interface becomes an exhaustive index of everything you have ever typed, on any machine, since day one.
Concrete benefits of a self-hosted Atuin
- Find any command typed on any machine in under a second via
Ctrl+R. - Client-side encryption (AES-256): the server never sees the content of your history.
- Rich context per entry: exit code, execution duration, working directory, hostname.
- No data sent to a third party — you host, you own.
- Rust binary with no heavy dependencies: runs on the VPS Start starting at 99 DH/month.
- Supports zsh, bash, fish, nushell and PowerShell — one server for your entire fleet.
- Built-in usage statistics (
atuin stats): your most frequent commands, your activity peaks.
Prerequisites
VPS: the Atuin server is a compiled Rust binary with an embedded SQLite database. It uses less than 50 MB of RAM at rest, making it compatible with the VPS Start. Allow 512 MB of available RAM for the full stack (OS + Atuin process), and 5 GB of SSD storage for long-term history. A recent Linux kernel (Debian 12 or Ubuntu 22.04+) works perfectly.
Docker and Docker Compose must be installed on the VPS — this is the deployment method recommended by the project. If your VPS is fresh, install Docker via the official script or the Docker template from the ServOrbit Marketplace.
A domain name pointing to the VPS is recommended (atuin.your-domain.com, A record) to generate an automatic TLS certificate. The certificate is mandatory: the Atuin client refuses to sync in cleartext by default.
Install the Atuin server on the VPS
Connect to the VPS and create a working directory
Connect via SSH to your VPS:
ssh root@<vps-ip>Create a dedicated directory:
mkdir -p /opt/atuin && cd /opt/atuinWrite the docker-compose.yml file
Create the composition file:
cat > docker-compose.yml << 'EOF' services: atuin: image: ghcr.io/atuinsh/atuin:v18.23.0 command: server start volumes: - ./config:/config ports: - "8888:8888" environment: ATUIN_HOST: "0.0.0.0" ATUIN_PORT: "8888" ATUIN_OPEN_REGISTRATION: "true" ATUIN_DB_URI: "sqlite:///config/history.db" restart: unless-stopped EOFThis configuration uses SQLite (embedded, no external dependency) and enables registration so you can create your account on first connection.
Create the config directory and start the container
Atuin needs a writable config directory:
mkdir -p config && chown 1000:1000 configStart the server:
docker compose up -dVerify the container is running:
docker compose ps # NAME STATUS PORTS # atuin Up 2 minutes 0.0.0.0:8888->8888/tcpOpen port 8888 in the UFW firewall
Port 8888 must be reachable from your client machines. On Ubuntu/Debian with UFW:
sudo ufw allow 8888/tcp sudo ufw status⚠️ If you skip this step, the atuin client will display
Connection refusedorFailed to sync historywhen syncing — the most common error in self-hosted deployments.Put an HTTPS reverse proxy in front of the server
The Atuin client requires HTTPS by default. Place Caddy in front:
apt-get install -y caddyCreate
/etc/caddy/Caddyfile:atuin.your-domain.com { reverse_proxy localhost:8888 }Restart Caddy (it obtains the Let's Encrypt certificate automatically):
systemctl restart caddyVerify the server responds:
curl https://atuin.your-domain.com # {"msg":"Atuin server v18.23.0"}Close registration once your account is created
Once your accounts are created (see the next section), disable public registration to prevent unwanted accounts. Edit
docker-compose.yml:ATUIN_OPEN_REGISTRATION: "false"Restart the container:
docker compose up -dAlternatively, create
/opt/atuin/config/server.tomlfor more granular control:[server] host = "0.0.0.0" port = 8888 open_registration = false [server.db] db_uri = "sqlite:///config/history.db"
Install and configure the Atuin client
On each of your machines (laptop, dev server, etc.), install Atuin using the official script:
curl --proto '=https' --tlsv1.2 -LsSf https://setup.atuin.sh | shThe script installs the binary to ~/.atuin/bin and detects your shell.
Shell integration — add the corresponding line to your shell configuration file:
# For zsh (~/.zshrc)
eval "$(atuin init zsh)"
# For bash (~/.bashrc)
eval "$(atuin init bash)"
# For fish (~/.config/fish/config.fish)
atuin init fish | sourceReload your shell: source ~/.zshrc (or equivalent).
Register an account on your server:
atuin register \
--server https://atuin.your-domain.com \
-u your-username \
-e [email protected] \
-p your-passwordAtuin generates a local encryption key — it is displayed on screen. Note it immediately: you will need it to connect other machines, and it cannot be regenerated.
First sync:
atuin sync
# Syncing history...
# History synced!Logging in from a second machine:
atuin login \
--server https://atuin.your-domain.com \
-u your-username \
-p your-password \
-k "<your-encryption-key>"
atuin syncSecurity: firewall, registration and reverse proxy
Three settings to check after your initial deployment:
1. open_registration: false as soon as your accounts are created. Left as true, anyone who knows the URL can create an account and use your storage.
2. Never expose port 8888 directly — traffic must always go through the HTTPS reverse proxy. Restrict UFW:
# Block direct external access to port 8888
sudo ufw delete allow 8888/tcp
# The local reverse proxy (Caddy) connects to it internally3. Back up the SQLite database regularly:
docker compose exec atuin sh -c "sqlite3 /config/history.db \".backup /config/history.backup.db\""The database grows slowly — a few dozen MB even after years of heavy use.
Troubleshooting — common errors
A few errors encountered during a self-hosted deployment and how to resolve them.
Connection refused on first atuin sync
Port 8888 is not open in UFW, or the container is not running. Check with:
sudo ufw status | grep 8888
docker compose psSSL certificate problem: unable to get local issuer certificate
The TLS certificate has not yet been issued by Caddy. Wait 30–60 seconds after restarting Caddy and retry atuin sync. Make sure the DNS for atuin.your-domain.com points to the VPS IP.
Invalid credentials at atuin login
The encryption key is incorrect or truncated. Retrieve it from the original machine:
atuin keyPaste the full value (long base64 string) after -k.
open_registration is disabled
You closed registration before creating all your accounts. Temporarily set ATUIN_OPEN_REGISTRATION: "true" in docker-compose.yml, restart, create the account, then switch back to false.
History does not sync after atuin sync
Check that the server is set as the target in the client config:
cat ~/.config/atuin/config.toml | grep sync_address
# sync_address = "https://atuin.your-domain.com"If the key is missing, add it manually to ~/.config/atuin/config.toml.
Atuin vs the alternatives: which tool to choose
Scroll the table
| Tool | E2E encryption | Multi-machine sync | Self-hosted | Advanced search |
|---|---|---|---|---|
| **Atuin** | Yes (AES-256, local key) | Yes (optional server) | Yes (Docker, SQLite) | Yes (context, exit code, duration) |
| McFly | No | No | N/A (local only) | Yes (neural network, working directory) |
| Histdb | No | No | N/A (local only) | Partial (local SQLite, zsh plugin) |
| Native zsh_history | No | No (manual rsync possible) | N/A | Minimal (grep, fzf optional) |
Atuin, a tool that scales with you
Atuin v18.23.0 is the most complete solution for a developer working across multiple machines who cares about data privacy. Initial setup takes under 30 minutes on a VPS Start, and the result is an exhaustive, encrypted history searchable instantly from any terminal.
Once the server is running, you can go further: configure automatic sync (atuin daemon), use weekly statistics (atuin stats), or explore the advanced search interface with filtering by exit code or hostname. The command you are looking for is always there — no matter which machine you typed it on.