Skip to content
Go back

Atuin: Shell History That Actually Works Across Machines

By SumGuy 10 min read
Atuin: Shell History That Actually Works Across Machines

Your Shell History Is a Lie

You run Ctrl+R, type three characters, and bash cycles through the same five commands from last Tuesday. The thing you actually need — that docker run incantation with seventeen flags you pieced together at 2 AM — is gone. Overwritten. Lost to the HISTSIZE=1000 abyss.

Here’s what stock shell history gets wrong:

The bash HISTSIZE and HISTFILESIZE settings exist, and yes, you can bump them to 100,000 and set HISTTIMEFORMAT to get timestamps, and enable HISTAPPEND so sessions don’t clobber each other. People do this. It works, technically, in the same way that a really large filing cabinet technically solves the problem of organizing documents. You still can’t search it properly, you still can’t sync it, and you’re still one history -c away from nuking everything.

Atuin fixes all of this at the root. It replaces your history file with a SQLite database, gives you a proper full-text search interface, and — optionally — syncs everything E2E-encrypted across every machine you own. It’s the shell history you deserved in 2006 but didn’t get until now.


Install It

One curl, done:

Terminal window
curl --proto '=https' --tlsv1.2 -LsSf https://setup.atuin.sh | sh

That installs the atuin binary and drops the shell shim into your config. You’ll need to reload your shell after:

Terminal window
exec $SHELL

Shell integration is handled automatically by the installer for bash, zsh, and fish. If you’re doing it manually or something went sideways, add this to your rc file:

For zsh (~/.zshrc):

~/.zshrc
eval "$(atuin init zsh)"

For bash (~/.bashrc):

~/.bashrc
eval "$(atuin init bash)"

For fish (~/.config/fish/config.fish):

config.fish
atuin init fish | source

Restart your shell, run a few commands, then hit Ctrl+R. You’ll immediately see the difference — a full-screen fuzzy search overlay with timestamps, directories, and exit codes. Welcome to 2024.


Using It: More Than Just Ctrl+R

Atuin replaces Ctrl+R with its own TUI search. But it’s not just “fancier reverse search” — it’s a queryable database.

The Search Interface

Hit Ctrl+R. You get a filterable list showing:

Type to filter. Enter to execute. Tab to fill the prompt without executing (handy when you want to edit before running).

Filtering by Context

The real power is filtering. In the search box:

host:workstation docker run

Shows only docker run commands from your workstation. Combine filters:

dir:/etc/nginx exit:0

Commands run from /etc/nginx that succeeded. This is the “which nginx reload actually worked?” query you’ve been doing mentally for years.

From the CLI directly:

Terminal window
# Show last 50 commands
atuin history list --limit 50
# Search by exit code (failures only)
atuin history list --exit 1
# Commands from a specific host
atuin history list --host homelab
# Commands from a specific directory
atuin history list --cwd /var/www

The Stats Are Genuinely Fun

Terminal window
atuin stats

Sample output:

Total commands: 14,823
Unique commands: 3,241
Most used: git status (847)
ls (712)
cd .. (601)
docker ps (487)

You will immediately discover that you type git status more than you breathe. Humbling.


Config: Dotfile It

Atuin’s config lives at ~/.config/atuin/config.toml. Key knobs worth setting:

~/.config/atuin/config.toml
# Don't sync automatically — we'll use our own server
auto_sync = false
sync_frequency = "5m"
# Search UI behavior
search_mode = "fuzzy" # fuzzy | prefix | fulltext
filter_mode = "global" # global | host | session | directory
style = "compact" # auto | compact | full
# Key bindings
ctrl_r_search_mode = "workspace" # search current dir by default
# Don't record these
history_filter = [
"^secret",
"^export.*TOKEN",
"^export.*PASSWORD",
"^export.*SECRET",
]
# Don't capture commands with these exit codes in certain contexts
# (keep failures for debugging, this is personal preference)

The history_filter field is a regex list. Any command matching a pattern gets silently dropped from the database. Useful so you stop accidentally logging export AWS_SECRET_ACCESS_KEY=... into searchable history. Your future self will appreciate this.


Self-Host the Sync Server

This is where it gets good for home lab types. Atuin ships an official sync server (atuin-server) you can run yourself. Your history never touches Atuin’s cloud infrastructure — everything syncs through your own box, encrypted client-side before it leaves your machine.

The key point: the server never sees your history in plaintext. Encryption and decryption happen entirely on the client. If your server gets compromised, the attacker gets encrypted blobs. No key, no history.

Compose Setup

docker-compose.yml
services:
atuin:
image: ghcr.io/atuinsh/atuin:latest
restart: unless-stopped
command: server start
ports:
- "8888:8888"
environment:
ATUIN_HOST: "0.0.0.0"
ATUIN_PORT: "8888"
ATUIN_OPEN_REGISTRATION: "false" # set true only when adding new users
ATUIN_DB_URI: "postgresql://atuin:changeme@db/atuin"
depends_on:
db:
condition: service_healthy
volumes:
- atuin-config:/config
db:
image: postgres:16-alpine
restart: unless-stopped
environment:
POSTGRES_USER: atuin
POSTGRES_PASSWORD: changeme
POSTGRES_DB: atuin
volumes:
- atuin-db:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U atuin"]
interval: 5s
timeout: 5s
retries: 5
volumes:
atuin-config:
atuin-db:

Spin it up:

Terminal window
docker compose up -d

Register and Connect

On your first machine, register an account (this is local to your server — not Atuin’s cloud):

Terminal window
atuin register -u yourname -e [email protected] -p 'strongpassword' \
--server http://your-server:8888

On every other machine, log in:

Terminal window
atuin login -u yourname -p 'strongpassword' \
--server http://your-server:8888

Import your existing history so you don’t lose it:

Terminal window
atuin import auto

Then do an initial sync:

Terminal window
atuin sync

After that, syncing happens automatically based on sync_frequency in your config. Or trigger it manually any time:

Terminal window
atuin sync -f # force full sync

How It Compares: mcfly and hstr

You’ve probably heard of the other two players in this space.

hstr is the OG. It gives you a better Ctrl+R with color-coded frequency indicators. No sync, no database — just reads your existing history file with a nicer UI. If you want zero-setup improvement to history search and nothing else, hstr is fine. It’s the ls --color of shell history tools.

mcfly replaces history search with a neural network that learns your patterns and ranks suggestions by context (current directory, recent commands). Genuinely clever. The suggestions get better over time. But: no sync across machines, no stats, no filtering by exit code. And honestly, “neural network for shell history” is a bit of a forklift for what you’re actually doing.

Atuin is the most complete solution if you use more than one machine. The sync story is first-class, the filtering is powerful, and the self-hosting option is actually good — not an afterthought. The tradeoff is slightly more setup upfront.

FeaturehstrmcflyAtuin
Better Ctrl+R UIYesYesYes
Timestamps + metadataNoPartialYes
Exit code filteringNoNoYes
Multi-machine syncNoNoYes
Self-hosted serverN/AN/AYes
E2E encryptionN/AN/AYes
StatsNoNoYes

A Note on Privacy

Atuin’s public sync service (sync.atuin.sh) uses the same E2E encryption as self-hosting. Your key never leaves your machine — the server stores ciphertext. This is documented and the crypto is open source. If you trust the implementation, the public service is fine.

If you don’t trust it — or you just prefer keeping your data on hardware you control — self-hosting takes about ten minutes with the Compose stack above. That’s the home lab way.

Either way, set history_filter for anything sensitive. Belt and suspenders.


Keybindings Worth Knowing

By default Atuin binds Ctrl+R for history search and Up Arrow for inline history navigation. You can remap both:

~/.config/atuin/config.toml
[keys]
# Vim-style: Ctrl+H opens history search
# ctrl_r = "ctrl-h"
# Disable the up-arrow binding if it conflicts with your existing setup
up_arrow_select = false

The up_arrow_select toggle is worth knowing. Some people love it — Up scrolls through history in Atuin’s TUI directly from the prompt. Others find it breaks their mental model of the up arrow. Personal preference. Turn it off if it annoys you.

There’s also Ctrl+O by default to switch between filter modes without opening config. Hit it while in the search TUI to cycle between global (all machines), host (current machine only), session (current terminal session), and directory (current working directory). Muscle memory for that one pays off fast.


Migrating Without Losing Anything

Before Atuin rewrites your shell init, it doesn’t delete your existing history file. The import command reads it and loads everything into the SQLite database:

Terminal window
# Detects your shell and imports automatically
atuin import auto
# Or be explicit
atuin import bash # reads ~/.bash_history
atuin import zsh # reads ~/.zsh_history

Old history comes in without timestamps — Atuin assigns the import time as a placeholder. That’s fine. You’re not losing the commands, you’re losing the original timestamps, which you probably didn’t have anyway because HISTTIMEFORMAT is off by default.

After import, verify the count:

Terminal window
atuin stats

If the total looks roughly right compared to your old history file, you’re good. Now you can set HISTFILE=/dev/null in your rc if you want to fully cut over, or just leave the old file in place — Atuin ignores it from here on.


The Bottom Line

If you use one machine and never lose history, Ctrl+R is probably fine. But the moment you’re bouncing between a laptop, a workstation, and a server — or you’ve lost that one perfect command for the third time — Atuin is the right call.

Install takes two minutes. Self-hosted sync with Postgres takes ten. You get encrypted, searchable, context-aware history across every machine you own, backed by a database instead of a text file that one errant > could obliterate.

Stop being a human cron job for your own command history. Let the database do its job.

Your 2 AM self will appreciate it.


Share this post on:

Send a Webmention

Written about this post on your own site? Send a webmention and it'll show up above once verified.


Next Post
Boundary vs Teleport

Discussion

Powered by Garrul . Sign in with GitHub or Google, or post anonymously.

Related Posts