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 Shell History

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

Atuin Stats

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 #

  1. Shell Hook Layer: Atuin registers preexec (before command) and precmd (after command) hooks via shell-specific plugins. These capture the command string, working directory, start time, and exit code.
  2. Local SQLite Database: All history stores in ~/.local/share/atuin/history.db using SQLite with WAL mode for concurrent read/write performance.
  3. 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.
  4. TUI Search Interface: A full-screen terminal UI (built with ratatui) replaces Ctrl+R with fuzzy/prefix/fulltext search and filter modes.

Encryption Details #

ProtocolAlgorithmStatus
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 DELETE with WHERE clauses lets you surgically remove entries (e.g., all commands containing password) rather than editing a text file.

Installation & Setup #

# 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 Search UI

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"

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:

MetricValueNotes
History query (100K entries)~15msfuzzy search, cold cache
History query (500K entries)~45msfuzzy search, warm cache
History query (1M entries)~85msfuzzy search, warm cache
Sync initial upload (50K cmds)~30sdepends on bandwidth
Incremental sync~1-3stypical daily delta
Binary size~12MBsingle static binary
Memory footprint~15MBresident while idle
SQLite DB size~150MB per 100K commandswith full metadata
Shell startup overhead~5-10msone-time per shell session

Production Use Cases #

  1. Multi-Device Development: A developer with a work laptop, personal desktop, and cloud VM keeps all shell history in sync. Running docker compose up on one machine is searchable from another.
  2. Team Knowledge Preservation: A DevOps team self-hosts Atuin to maintain a searchable audit trail of infrastructure commands across rotating on-call engineers.
  3. 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 #

FeatureAtuinmcflyfzf + historyHstr
DatabaseSQLiteSQLitePlain text filePlain text file
Cross-machine syncYes (E2EE)NoNoNo
Search UIBuilt-in TUIBuilt-in TUIfzf integrationBuilt-in TUI
Context loggingcwd, exit, duration, hostcwd, exitNoneNone
Statisticsatuin statsNoNoNo
Shell supportbash, zsh, fish, nu, xonshbash, zsh, fishAny shellbash, zsh
EncryptionPASETO V4NoneNoneNone
Self-hosted serverYes (Docker/K8s)N/AN/AN/A
AI integrationYes (optional)NoNoNo
History importbash, zsh, fish, nubash, zsh, fishN/Abash, zsh
GitHub Stars29,7946,80067,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:

  1. 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.

  2. 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.

  3. Bash Integration Can Be Fragile: Bash’s preexec hooks rely on DEBUG traps that can conflict with other tools (pyenv, nodenv, certain PROMPT_COMMAND setups). Zsh and Fish integrations are more reliable.

  4. Sync Requires Account: Even self-hosted sync requires user registration. There is no anonymous or “just sync to S3” mode.

  5. 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.

  6. 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:

  1. Run curl --proto '=https' --tlsv1.2 -LsSf https://setup.atuin.sh | sh to install
  2. Import existing history with atuin import auto
  3. Register for sync or configure a self-hosted server
  4. Join the Telegram developer community for tips and troubleshooting

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.

Sources & Further Reading #

💬 Discussion