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 Link to this section

eval "$(atuin init zsh)"

Near the end of .zshrc, then exec zsh.

fzf Link to this section

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 Link to this section

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 Link to this section

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.

References Link to this section