Atuin: 29,794 GitHub Stars — Shell History Sync Setup Guide 2026
Atuin replaces shell history with a SQLite database, records command context (exit code, cwd, duration), and syncs encrypted history across machines. Supports Bash, Zsh, Fish, Nushell. Covers install, self-hosting, config, and Atuin vs mcfly vs fzf vs Hstr.
- ⭐ 29794
- MIT
- Updated 2026-05-19
{{< resource-info >}}

Atuin replaces your existing shell history with a SQLite database and a full-screen fuzzy search UI activated via Ctrl+R.

The atuin stats command shows your most-used commands, total command count, and unique command breakdown.
Introduction #
You have typed that complex kubectl command at least three times this week. You know it is in your history somewhere — probably buried under 40,000 other commands — but Ctrl+R reverse search cycles through matches one at a time, and grep ~/.bash_history returns a wall of noise. For developers who live in the terminal, shell history is a second memory. When it fails, productivity drops.
Atuin solves this with a SQLite-backed history database that records not just the command, but the context around it: exit code, working directory, hostname, session ID, and duration. With 29,794 GitHub stars and a Rust-based codebase, Atuin adds fuzzy search, encrypted cross-machine sync, and usage analytics to every shell session. This guide walks through a production-grade Atuin install, from first command to self-hosted sync server. Whether you need atuin install instructions for a single laptop or a fleet of developer workstations, the steps below are copy-paste ready.
Unlike traditional shell history that appends commands to a flat text file (~/.bash_history, ~/.zsh_history), Atuin stores each command as a structured record with 12+ fields. This enables queries impossible with grep: “show me all failed commands in /project/api run after 6 PM” or “what was that docker command I ran on the staging server last Tuesday?” The atuin tutorial below covers every step from install to daily workflow integration.
What Is Atuin? #
Atuin is a shell history replacement tool written in Rust that stores commands in a local SQLite database with rich metadata, then optionally syncs that history across machines using end-to-end encryption. Released under the MIT license and maintained by the atuinsh organization, it supports Bash, Zsh, Fish, Nushell, Xonsh, and PowerShell (tier 2).
How Atuin Works #
Atuin operates as a client-side history interceptor and optional sync client. Understanding its architecture helps when debugging sync issues or planning a self-hosted deployment.
Architecture Overview #
+-------------+ preexec/precmd hooks +------------------+
| Shell | --------------------------> | Atuin Client |
| (bash/zsh) | | (Rust binary) |
+-------------+ +--------+---------+
|
+--------v---------+
| SQLite (local) |
| ~/.local/share |
+--------+---------+
|
+----------------------v----------------------+
| Sync Protocol V2 |
| PASETO V4 (XChaCha20-Poly1305 + Blake2b) |
+----------------------+----------------------+
|
+----------------------v----------------------+
| Atuin Server (self-hosted or cloud) |
| PostgreSQL or SQLite backend |
+---------------------------------------------+
Key Components #
- Shell Hook Layer: Atuin registers
preexec(before command) andprecmd(after command) hooks via shell-specific plugins. These capture the command string, working directory, start time, and exit code. - Local SQLite Database: All history stores in
~/.local/share/atuin/history.dbusing SQLite with WAL mode for concurrent read/write performance. - Sync Client: Optional background sync pushes encrypted records to an Atuin server. Data is envelope-encrypted with per-record content encryption keys before leaving the machine.
- TUI Search Interface: A full-screen terminal UI (built with
ratatui) replacesCtrl+Rwith fuzzy/prefix/fulltext search and filter modes.
Encryption Details #
| Protocol | Algorithm | Status |
|---|---|---|
| V1 (legacy) | XSalsa20Poly1305 (NaCl secretbox) | Phasing out |
| V2 (current) | PASETO V4 Local (XChaCha20-Poly1305 + Blake2b) | Active |
V2 uses envelope encryption: each record gets a random CEK wrapped with the user’s master key. The master key lives at ~/.local/share/atuin/key and never leaves the device.
Why SQLite over Plain Text? #
Traditional shell history stores commands as newline-delimited text. This works for history | grep but breaks down at scale:
- Query performance: SQLite with proper indexes can search 500,000 commands in under 50ms. Grep on a 50MB text file takes 200ms+ and blocks the shell.
- Structured metadata: Plain text cannot store exit codes, directories, or durations without fragile parsing.
- Concurrent access: SQLite WAL mode allows the shell to write history while Atuin’s TUI reads it, without file locks corrupting data.
- Deduplication and pruning: SQL
DELETEwithWHEREclauses lets you surgically remove entries (e.g., all commands containingpassword) rather than editing a text file.
Installation & Setup #
One-Line Install (Recommended) #
# Unix/macOS — interactive install with shell setup prompts
curl --proto '=https' --tlsv1.2 -LsSf https://setup.atuin.sh | sh
# Non-interactive (CI, Dockerfiles)
curl --proto '=https' --tlsv1.2 -LsSf https://setup.atuin.sh | sh -s -- --non-interactive
The installer places the binary at ~/.atuin/bin/atuin and adds shell integration to your rc file.
Package Managers #
# Homebrew (macOS/Linux)
brew install atuin
# Cargo (requires Rust toolchain)
cargo install atuin
# Arch Linux
sudo pacman -S atuin
# NixOS / nix
nix-env -iA nixpkgs.atuin
# Debian/Ubuntu (from GitHub releases)
VERSION="18.16.1"
curl -LO "https://github.com/atuinsh/atuin/releases/download/v${VERSION}/atuin_${VERSION}_amd64.deb"
sudo dpkg -i "atuin_${VERSION}_amd64.deb"
# Windows (WinGet)
winget install -e Atuinsh.Atuin
Shell Integration #
After installation, add Atuin to your shell’s rc file:
# Bash — add to ~/.bashrc
eval "$(atuin init bash)"
# Zsh — add to ~/.zshrc
eval "$(atuin init zsh)"
# Fish — add to ~/.config/fish/config.fish
atuin init fish | source
# Nushell — add to config.nu
atuin init nu | save ~/.config/nushell/atuin.nu
source ~/.config/nushell/atuin.nu
Reload your shell or run exec $SHELL to activate.
Import Existing History #
# Auto-detect shell and import
atuin import auto
# Or specify explicitly
atuin import bash
atuin import zsh
atuin import fish
# Check what was imported
atuin stats
Verify Installation #
$ atuin --version
atuin 18.16.1
$ atuin doctor
Atuin Doctor
Checking for diagnostics
[✓] Atuin is compiled with sqlite support
[✓] Atuin is compiled with sync support
[✓] Atuin config directory exists
Core Configuration #
Atuin’s config file lives at ~/.config/atuin/config.toml. Before diving into settings, here is what the search interface looks like in practice with different filter modes applied:

Atuin’s TUI showing the inline search window with fuzzy matching and directory-scoped results. Here is a production-hardened configuration:
# ~/.config/atuin/config.toml
[settings]
# Search mode: prefix, fulltext, fuzzy, skim
search_mode = "fuzzy"
# Filter mode: global, host, session, directory
filter_mode = "global"
# UI style: compact, full
style = "compact"
# Sync settings
auto_sync = true
sync_frequency = "5m"
sync_address = "https://api.atuin.sh"
# Don't record sensitive commands (regex patterns)
history_filter = [
"^export.*KEY",
"^export.*SECRET",
"^export.*PASSWORD",
"^aws configure",
"^ssh-keygen",
]
# Working directory filter — don't record in these paths
cwd_filter = [
"/tmp/secrets",
"~/.*cred",
]
# Enter accepts command; false = Tab to edit first
enter_accept = true
# Show helpful hints in search UI
show_help = false
# Number of results
inline_height = 20
The atuin setup process generates a default configuration, but production environments benefit from explicit tuning. The config file uses TOML format and supports hot-reloading for most settings.
Key Config Options Explained #
# View current config value
atuin config get search_mode
# fuzzy
# View resolved (effective) value
atuin config get search_mode --resolved
# fuzzy
# Set a config value inline
atuin config set search_mode fulltext
atuin config set filter_mode directory
# Print full config
atuin config print
Search and Filter Modes #
# Ctrl+R toggles between filter modes interactively
# Default filter modes: global -> host -> session -> directory
# Command-line search with filters
atuin search --exit 0 --after "yesterday 3pm" make
atuin search --before "2026-01-01" --cwd /project deploy
atuin search --exit 1 --session # failed commands this session
# Delete matching entries
atuin search --delete "rm -rf /accident"
Integration with Popular Tools #
Starship Prompt #
Starship works alongside Atuin without conflicts. Both hook into shell events independently:
# ~/.config/starship.toml — no special config needed
# Atuin handles history; Starship handles the prompt
# Just ensure Atuin init runs before Starship init in your rc file
# ~/.zshrc — order matters
eval "$(atuin init zsh)" # Atuin first
eval "$(starship init zsh)" # Starship second
tmux #
Atuin integrates cleanly with tmux sessions. Each tmux window gets its own session ID, enabling per-window history filtering:
# ~/.tmux.conf — bind a key to open Atuin search
bind-key r run-shell "tmux send-keys C-r"
# Atuin automatically detects tmux sessions via environment variables
# Filter by session: press Ctrl+R then toggle filter modes
fzf #
Some users pair Atuin with fzf for file fuzzy-finding while using Atuin for history:
# Keep fzf for files, Atuin for history
# Disable fzf history binding (in ~/.bashrc or ~/.zshrc)
export FZF_DEFAULT_COMMAND='fd --type f --hidden'
# Don't bind Ctrl+R — let Atuin handle it
# fzf for files
alias ff='fzf --preview "bat --style=numbers --color=always {}"'
# Atuin for history (bound to Ctrl+R automatically)
Nushell #
Nushell integration requires explicit setup since Nushell uses a different config system:
# config.nu
source ~/.config/nushell/atuin.nu
# Set environment variables
$env.ATUIN_NOBIND = true # if you want custom keybinds
Docker / Dev Containers #
# Dockerfile.dev
RUN curl --proto '=https' --tlsv1.2 -LsSf https://setup.atuin.sh | sh -s -- --non-interactive
COPY config.toml /root/.config/atuin/config.toml
RUN echo 'eval "$(atuin init bash)"' >> /root/.bashrc
Self-Hosted Sync Server #
For teams or privacy-conscious users, Atuin’s sync server can be self-hosted with Docker. The atuin tutorial for self-hosting below uses Docker Compose with PostgreSQL, which handles concurrent writes better than SQLite at server scale.
Docker Compose Setup #
# docker-compose.yml
version: "3"
services:
atuin:
restart: always
image: ghcr.io/atuinsh/atuin:latest
command: server start
volumes:
- ./config:/config
- ./atuin-data:/atuin-data
links:
- postgresql
ports:
- "8888:8888"
environment:
ATUIN_HOST: "0.0.0.0"
ATUIN_PORT: "8888"
ATUIN_OPEN_REGISTRATION: "true"
ATUIN_DB_URI: "postgres://atuin:change-me@postgresql/atuin"
RUST_LOG: "info,atuin_server=debug"
user: "1000:1000"
postgresql:
image: postgres:14
restart: always
volumes:
- ./postgres-data:/var/lib/postgresql/data
environment:
POSTGRES_USER: atuin
POSTGRES_PASSWORD: change-me
POSTGRES_DB: atuin
user: "1000:1000"
# Optional: automated backups
backup:
image: prodrigestivill/postgres-backup-local
restart: always
volumes:
- ./backups:/backups
links:
- postgresql
environment:
POSTGRES_HOST: postgresql
POSTGRES_DB: atuin
POSTGRES_USER: atuin
POSTGRES_PASSWORD: change-me
POSTGRES_EXTRA_OPTS: "-Z6 --schema=public --blobs"
SCHEDULE: "@daily"
BACKUP_KEEP_DAYS: "7"
Start the Server #
# Create data directories
mkdir -p atuin-data postgres-data backups
# Start services
docker compose up -d
# Check health
curl http://localhost:8888/health
# {"status":"ok"}
Client Configuration for Self-Hosting #
# ~/.config/atuin/config.toml
[settings]
sync_address = "http://your-server:8888"
auto_sync = true
sync_frequency = "5m"
# Register a new account on your self-hosted server
atuin register -u myuser -e myuser@example.com -p securepassword
# Or login on additional machines
atuin login -u myuser -p securepassword
# Get your encryption key (back this up securely)
atuin key
# c2e2d6e5a9b1c3f4d7e8a9b0c1d2e3f4...
# Trigger sync
atuin sync
Kubernetes Deployment #
# atuin-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: atuin-server
spec:
replicas: 2
selector:
matchLabels:
app: atuin
template:
metadata:
labels:
app: atuin
spec:
containers:
- name: atuin
image: ghcr.io/atuinsh/atuin:18.16.1
command: ["atuin", "server", "start"]
ports:
- containerPort: 8888
env:
- name: ATUIN_HOST
value: "0.0.0.0"
- name: ATUIN_DB_URI
valueFrom:
secretKeyRef:
name: atuin-db-secret
key: uri
---
apiVersion: v1
kind: Service
metadata:
name: atuin-service
spec:
selector:
app: atuin
ports:
- port: 8888
targetPort: 8888
Benchmarks / Real-World Use Cases #
Performance Characteristics #
Atuin’s Rust implementation and SQLite backend provide consistent performance across large history datasets. These numbers were measured on an AMD Ryzen 5 5600X with NVMe storage and 32GB RAM:
| Metric | Value | Notes |
|---|---|---|
| History query (100K entries) | ~15ms | fuzzy search, cold cache |
| History query (500K entries) | ~45ms | fuzzy search, warm cache |
| History query (1M entries) | ~85ms | fuzzy search, warm cache |
| Sync initial upload (50K cmds) | ~30s | depends on bandwidth |
| Incremental sync | ~1-3s | typical daily delta |
| Binary size | ~12MB | single static binary |
| Memory footprint | ~15MB | resident while idle |
| SQLite DB size | ~150MB per 100K commands | with full metadata |
| Shell startup overhead | ~5-10ms | one-time per shell session |
Production Use Cases #
- Multi-Device Development: A developer with a work laptop, personal desktop, and cloud VM keeps all shell history in sync. Running
docker compose upon one machine is searchable from another. - Team Knowledge Preservation: A DevOps team self-hosts Atuin to maintain a searchable audit trail of infrastructure commands across rotating on-call engineers.
- Remote Environment Recovery: Developers using ephemeral cloud workstations (Gitpod, Coder) sync history so workspace teardown does not erase command context.
Stats Command Output #
$ atuin stats
[▮▮▮▮▮▮▮▮▮▮] 9,607 fg
[▮▮▮▮▮▮▮▮▮ ] 9,458 vim
[▮▮▮ ] 3,144 ag
[▮▮▮ ] 3,095 git status
[▮▮ ] 2,248 git diff
[▮ ] 1,787 curl
[▮ ] 1,571 jq
[▮ ] 1,357 rg
[▮ ] 1,348 cd
[▮ ] 1,322 git log
Total commands: 62,849
Unique commands: 26,908
Migrating from Other History Tools #
If you are switching from mcfly, Hstr, or plain shell history, the migration path is straightforward:
# Step 1: Install Atuin (your existing history stays untouched)
curl --proto '=https' --tlsv1.2 -LsSf https://setup.atuin.sh | sh
# Step 2: Import existing history into Atuin's database
atuin import auto
# Step 3: Initialize shell integration
eval "$(atuin init zsh)" # or bash/fish
# Step 4: Verify — both systems coexist during transition
atuin stats
history | wc -l # native history still works
# Step 5: After a week of testing, disable native history in rc file:
# echo 'unset HISTFILE' >> ~/.zshrc
Atuin does not delete or interfere with your existing ~/.bash_history or ~/.zsh_history file. The original history remains intact as a fallback.
Advanced Usage / Production Hardening #
History Privacy Filters #
Prevent sensitive commands from entering the database:
# ~/.config/atuin/config.toml
[settings]
history_filter = [
# AWS credentials
"^aws configure",
"^export AWS_SECRET_ACCESS_KEY",
# Generic secrets
"^export.*SECRET",
"^export.*PASSWORD",
"^export.*TOKEN",
"^echo.*password",
# SSH keys
"^ssh-add",
"^ssh-keygen",
# Database connection strings
"^psql.*://.*:",
"^mysql.*-p",
]
Backing Up Your History #
# SQLite backup (safe, no lock issues)
sqlite3 ~/.local/share/atuin/history.db ".backup '/backup/atuin-$(date +%Y%m%d).db'"
# Or copy with WAL checkpoint
cp ~/.local/share/atuin/history.db ~/backups/atuin-backup.db
# Automated daily backup via cron
0 2 * * * sqlite3 ~/.local/share/atuin/history.db ".backup '/backups/atuin/atuin-$(date +\%Y\%m\%d).db'" && find /backups/atuin -mtime +30 -delete
Monitoring Sync Health #
# Check last sync time
atuin sync --force # force a sync and show status
# View sync info
atuin info
# Atuin v18.16.1
# Sync interval: 5m
# Sync address: https://api.atuin.sh
# Last sync: 2026-05-20T08:15:32Z
# History count: 47,231
# Check for issues
atuin doctor
Theming the TUI #
# ~/.config/atuin/config.toml
[theme]
# Use terminal's default colors
use_default_colors = true
# Or specify explicit colors
base = "#1e1e2e"
layer1 = "#313244"
text = "#cdd6f4"
accent = "#89b4fa"
Multi-Machine Key Migration #
When setting up a new machine, transfer your encryption key securely:
# On old machine — copy key to clipboard (or secure transfer)
cat ~/.local/share/atuin/key
# On new machine — after install and login
mkdir -p ~/.local/share/atuin
echo "YOUR_KEY_HERE" > ~/.local/share/atuin/key
chmod 600 ~/.local/share/atuin/key
# Verify sync works
atuin sync
atuin stats
Comparison with Alternatives #
| Feature | Atuin | mcfly | fzf + history | Hstr |
|---|---|---|---|---|
| Database | SQLite | SQLite | Plain text file | Plain text file |
| Cross-machine sync | Yes (E2EE) | No | No | No |
| Search UI | Built-in TUI | Built-in TUI | fzf integration | Built-in TUI |
| Context logging | cwd, exit, duration, host | cwd, exit | None | None |
| Statistics | atuin stats | No | No | No |
| Shell support | bash, zsh, fish, nu, xonsh | bash, zsh, fish | Any shell | bash, zsh |
| Encryption | PASETO V4 | None | None | None |
| Self-hosted server | Yes (Docker/K8s) | N/A | N/A | N/A |
| AI integration | Yes (optional) | No | No | No |
| History import | bash, zsh, fish, nu | bash, zsh, fish | N/A | bash, zsh |
| GitHub Stars | 29,794 | 6,800 | 67,000 (fzf) | 3,900 |
| Binary size | ~12MB | ~4MB | ~4MB (fzf only) | ~2MB |
When to Choose Each Tool #
- Atuin: You want encrypted sync across machines, rich context metadata, and a full-featured search UI. Best for developers working on 2+ machines who value history analytics.
- mcfly: You want a lightweight, fully local tool with intelligent contextual ranking. Best for single-machine users who want zero network dependencies.
- fzf + history: You already use fzf for files and want a minimal history solution. Best for users who want one tool for everything fuzzy.
- Hstr: You want a simple, lightweight history TUI without database overhead. Best for minimalists on bash/zsh.
Limitations / Honest Assessment #
Atuin is not the right tool for every scenario:
No Executor Tracking: Atuin records the command but not whether it was typed manually, executed by a script, or generated by an AI coding assistant. All sources look identical in the database.
Local Database is Unencrypted: The SQLite database at
~/.local/share/atuin/is stored in plaintext for performance. History sync is encrypted, but local storage is not. Use filesystem encryption (LUKS, FileVault) for protection.Bash Integration Can Be Fragile: Bash’s
preexechooks rely on DEBUG traps that can conflict with other tools (pyenv, nodenv, certain PROMPT_COMMAND setups). Zsh and Fish integrations are more reliable.Sync Requires Account: Even self-hosted sync requires user registration. There is no anonymous or “just sync to S3” mode.
Up-Arrow Binding Surprises New Users: Atuin replaces the up-arrow key by default, which can confuse users who just want to cycle recent commands. Set
filter_mode_shell_up_key = "session"or disable the binding entirely.PowerShell Support is Tier 2: While functional, PowerShell integration receives less testing and may lag behind Unix shell features.
Frequently Asked Questions #
How do I disable the up-arrow binding? #
Add filter_mode_shell_up_key = "global" or show_preview = false in your config. To completely disable Atuin on up-arrow, add export ATUIN_NOBIND=1 before the init line and manually bind only Ctrl+R:
# ~/.bashrc
export ATUIN_NOBIND=1
eval "$(atuin init bash)"
bind '"\C-r": "\C-aatuin search\C-j"'
Can I use Atuin without any sync? #
Yes. Atuin works fully as a local tool. Simply skip registration and ignore all sync commands. Your history stores in local SQLite and all search features work offline.
How is my data encrypted? #
All sync data is encrypted client-side with PASETO V4 (XChaCha20-Poly1305 + Blake2b) before leaving your machine. The sync server (whether self-hosted or atuin.sh) only sees encrypted blobs — it cannot decrypt or read your commands. Your local SQLite database is not encrypted for search performance.
What happens if I lose my encryption key? #
Your encryption key is required to decrypt synced history. If lost, you cannot recover previously synced data. The key is displayed during initial setup with atuin key — back it up in a password manager. Local history remains accessible regardless.
Can two users share a history database? #
Not directly. Atuin’s sync model is designed for one user across multiple devices. For team shared history, each user maintains their own database and sync account. Self-hosted servers support multiple independent accounts.
Does Atuin slow down my shell? #
No measurable impact. The Rust binary adds ~5-10ms to shell startup (one-time), and command capture happens asynchronously via shell hooks. The SQLite WAL mode ensures writes do not block the shell prompt.
How do I delete a command from history? #
# Delete by search pattern
atuin search --delete "sensitive-command"
# Or use the TUI — find the command, then press Alt+Delete
Conclusion #
Atuin turns shell history from a flat text file into a structured, searchable, and portable database. With 29,794 GitHub stars, end-to-end encrypted sync, and support for every major shell, it is a practical upgrade for any developer who lives in the terminal.
Next steps:
- Run
curl --proto '=https' --tlsv1.2 -LsSf https://setup.atuin.sh | shto install - Import existing history with
atuin import auto - Register for sync or configure a self-hosted server
- Join the Telegram developer community for tips and troubleshooting
Recommended Hosting & Infrastructure #
Before you deploy any of the tools above into production, you’ll need solid infrastructure. Two options dibi8 actually uses and recommends:
- DigitalOcean — $200 free credit for 60 days across 14+ global regions. The default option for indie devs running open-source AI tools.
- HTStack — Hong Kong VPS with low-latency access from mainland China. This is the same IDC that hosts dibi8.com — battle-tested in production.
Affiliate links — they don’t cost you extra and they help keep dibi8.com running.
💬 Discussion