Atuin replaces shell history search: full-text and fuzzy search over a SQLite database rather than a flat history file, per-directory and per-session context, statistics, optional end-to-end encryption, and sync between machines. In zsh it replaces the default Ctrl+R reverse search.
Setup
eval "$(atuin init zsh)"
Near the end of .zshrc, then exec zsh.
fzf
fzf is optional (Atuin has its own fuzzy search) but still covers file finding, git branch switching, process selection, piped input. Atuin for history, fzf for everything else.
Both want Ctrl+R; last initialised wins. For Atuin:
source <(fzf --zsh)
eval "$(atuin init zsh)"
For fzf: eval "$(atuin init zsh --disable-up-arrow)", remap by hand.
Other commands:
atuin import auto # pull in existing zsh history
atuin stats
atuin search docker
atuin history list
atuin status # sync enabled? logged in? which address?
Sync
Sync needs an account even self-hosted:
atuin register -u USER -e [email protected] --sync-address https://atuin.example.com
atuin login -u USER -p PASSWORD --sync-address https://atuin.example.com
atuin sync
sync_address in ~/.config/atuin/config.toml must be the bare origin — no /api, no /api/v0, no trailing slash. Without a login, history stays local, the server database empty.
Checking that sync works
The server logs almost nothing when healthy, nothing at INFO; verify from the client and database:
atuin sync -f # prints uploaded / downloaded record counts
ATUIN_LOG=debug atuin sync # requests to /api/v0/record and /sync
atuin status
Probe the API directly; inside the container it listens on 8888:
curl -i https://atuin.example.com/api/v0/ping
curl -i https://atuin.example.com/api/v0/store/history
docker exec -it atuin sh
wget -S -O - http://localhost:8888/api/v0/ping
401 is good — responded, wants auth. 404 means the route does not exist, usually a reverse-proxy problem. A root-level 404 on / proves nothing; that path is not served.
Proxy mistake that strips the path:
location /atuin/ {
proxy_pass http://atuin:8888/; # breaks the API routes
}
Serve at the root or a dedicated subdomain; answers inside the container but not outside mean the proxy, not Atuin.
Confirm data lands (Postgres):
SELECT count(*) FROM history;
SELECT count(*) FROM records;
SELECT username FROM users;
End to end: echo "test-atuin-sync" on one machine, atuin sync on both, then atuin search test-atuin-sync.
If the client logs nothing, raise server verbosity:
environment:
RUST_LOG: debug
then docker compose up -d --force-recreate. Usual causes of silence: the client never attempts a sync, sync_address is wrong, HTTPS or the reverse proxy blocks it, or the container runs without debug logging.