Tutorial

Self-host Atuin on a VPS to Sync Your Shell History

Automation7 min read6 steps

Atuin replaces `.zsh_history` with a local SQLite database and synchronises that history across your machines via a server you run yourself. Every command is encrypted before being sent: the server never sees the content. The result is a history searchable in milliseconds, shared across laptops, servers and VMs, without handing your data to a third party.

Contents· Why sync your shell history1/9
  1. 01Why sync your shell history
  2. 02Concrete benefits of a self-hosted Atuin
  3. 03Prerequisites
  4. 04Install the Atuin server on the VPS
  5. 05Install and configure the Atuin client
  6. 06Security: firewall, registration and reverse proxy
  7. 07Troubleshooting — common errors
  8. 08Atuin vs the alternatives: which tool to choose
  9. 09Atuin, a tool that scales with you

Why sync your shell history

Working across multiple machines should be seamless. In practice, the kubectl rollout undo deployment/api command you typed on your laptop is never in the CI server terminal, and the docker compose exec from three weeks ago is gone because history rotation overwrote older entries.

Atuin solves both problems at the root: it stores every command in SQLite with its context (timestamp, duration, exit code, directory), and it synchronises that database across your machines through a server you control. Search runs locally — no network latency when recalling a command — and sync happens in the background. The Ctrl+R interface becomes an exhaustive index of everything you have ever typed, on any machine, since day one.

Concrete benefits of a self-hosted Atuin

  • Find any command typed on any machine in under a second via Ctrl+R.
  • Client-side encryption (AES-256): the server never sees the content of your history.
  • Rich context per entry: exit code, execution duration, working directory, hostname.
  • No data sent to a third party — you host, you own.
  • Rust binary with no heavy dependencies: runs on the VPS Start starting at 99 DH/month.
  • Supports zsh, bash, fish, nushell and PowerShell — one server for your entire fleet.
  • Built-in usage statistics (atuin stats): your most frequent commands, your activity peaks.

Prerequisites

VPS: the Atuin server is a compiled Rust binary with an embedded SQLite database. It uses less than 50 MB of RAM at rest, making it compatible with the VPS Start. Allow 512 MB of available RAM for the full stack (OS + Atuin process), and 5 GB of SSD storage for long-term history. A recent Linux kernel (Debian 12 or Ubuntu 22.04+) works perfectly.

Docker and Docker Compose must be installed on the VPS — this is the deployment method recommended by the project. If your VPS is fresh, install Docker via the official script or the Docker template from the ServOrbit Marketplace.

A domain name pointing to the VPS is recommended (atuin.your-domain.com, A record) to generate an automatic TLS certificate. The certificate is mandatory: the Atuin client refuses to sync in cleartext by default.

Install the Atuin server on the VPS

  1. Connect to the VPS and create a working directory

    Connect via SSH to your VPS:

    ssh root@<vps-ip>

    Create a dedicated directory:

    mkdir -p /opt/atuin && cd /opt/atuin
  2. Write the docker-compose.yml file

    Create the composition file:

    cat > docker-compose.yml << 'EOF'
    services:
      atuin:
        image: ghcr.io/atuinsh/atuin:v18.23.0
        command: server start
        volumes:
          - ./config:/config
        ports:
          - "8888:8888"
        environment:
          ATUIN_HOST: "0.0.0.0"
          ATUIN_PORT: "8888"
          ATUIN_OPEN_REGISTRATION: "true"
          ATUIN_DB_URI: "sqlite:///config/history.db"
        restart: unless-stopped
    EOF

    This configuration uses SQLite (embedded, no external dependency) and enables registration so you can create your account on first connection.

  3. Create the config directory and start the container

    Atuin needs a writable config directory:

    mkdir -p config && chown 1000:1000 config

    Start the server:

    docker compose up -d

    Verify the container is running:

    docker compose ps
    # NAME      STATUS          PORTS
    # atuin     Up 2 minutes    0.0.0.0:8888->8888/tcp
  4. Open port 8888 in the UFW firewall

    Port 8888 must be reachable from your client machines. On Ubuntu/Debian with UFW:

    sudo ufw allow 8888/tcp
    sudo ufw status

    ⚠️ If you skip this step, the atuin client will display Connection refused or Failed to sync history when syncing — the most common error in self-hosted deployments.

  5. Put an HTTPS reverse proxy in front of the server

    The Atuin client requires HTTPS by default. Place Caddy in front:

    apt-get install -y caddy

    Create /etc/caddy/Caddyfile:

    atuin.your-domain.com {
        reverse_proxy localhost:8888
    }

    Restart Caddy (it obtains the Let's Encrypt certificate automatically):

    systemctl restart caddy

    Verify the server responds:

    curl https://atuin.your-domain.com
    # {"msg":"Atuin server v18.23.0"}
  6. Close registration once your account is created

    Once your accounts are created (see the next section), disable public registration to prevent unwanted accounts. Edit docker-compose.yml:

    ATUIN_OPEN_REGISTRATION: "false"

    Restart the container:

    docker compose up -d

    Alternatively, create /opt/atuin/config/server.toml for more granular control:

    [server]
    host = "0.0.0.0"
    port = 8888
    open_registration = false
    
    [server.db]
    db_uri = "sqlite:///config/history.db"

Install and configure the Atuin client

On each of your machines (laptop, dev server, etc.), install Atuin using the official script:

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

The script installs the binary to ~/.atuin/bin and detects your shell.

Shell integration — add the corresponding line to your shell configuration file:

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

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

# For fish (~/.config/fish/config.fish)
atuin init fish | source

Reload your shell: source ~/.zshrc (or equivalent).

Register an account on your server:

atuin register \
  --server https://atuin.your-domain.com \
  -u your-username \
  -e [email protected] \
  -p your-password

Atuin generates a local encryption key — it is displayed on screen. Note it immediately: you will need it to connect other machines, and it cannot be regenerated.

First sync:

atuin sync
# Syncing history...
# History synced!

Logging in from a second machine:

atuin login \
  --server https://atuin.your-domain.com \
  -u your-username \
  -p your-password \
  -k "<your-encryption-key>"

atuin sync

Security: firewall, registration and reverse proxy

Three settings to check after your initial deployment:

1. open_registration: false as soon as your accounts are created. Left as true, anyone who knows the URL can create an account and use your storage.

2. Never expose port 8888 directly — traffic must always go through the HTTPS reverse proxy. Restrict UFW:

# Block direct external access to port 8888
sudo ufw delete allow 8888/tcp
# The local reverse proxy (Caddy) connects to it internally

3. Back up the SQLite database regularly:

docker compose exec atuin sh -c "sqlite3 /config/history.db \".backup /config/history.backup.db\""

The database grows slowly — a few dozen MB even after years of heavy use.

Troubleshooting — common errors

A few errors encountered during a self-hosted deployment and how to resolve them.

Connection refused on first atuin sync
Port 8888 is not open in UFW, or the container is not running. Check with:

sudo ufw status | grep 8888
docker compose ps

SSL certificate problem: unable to get local issuer certificate
The TLS certificate has not yet been issued by Caddy. Wait 30–60 seconds after restarting Caddy and retry atuin sync. Make sure the DNS for atuin.your-domain.com points to the VPS IP.

Invalid credentials at atuin login
The encryption key is incorrect or truncated. Retrieve it from the original machine:

atuin key

Paste the full value (long base64 string) after -k.

open_registration is disabled
You closed registration before creating all your accounts. Temporarily set ATUIN_OPEN_REGISTRATION: "true" in docker-compose.yml, restart, create the account, then switch back to false.

History does not sync after atuin sync
Check that the server is set as the target in the client config:

cat ~/.config/atuin/config.toml | grep sync_address
# sync_address = "https://atuin.your-domain.com"

If the key is missing, add it manually to ~/.config/atuin/config.toml.

Atuin vs the alternatives: which tool to choose

Scroll the table

ToolE2E encryptionMulti-machine syncSelf-hostedAdvanced search
**Atuin**Yes (AES-256, local key)Yes (optional server)Yes (Docker, SQLite)Yes (context, exit code, duration)
McFlyNoNoN/A (local only)Yes (neural network, working directory)
HistdbNoNoN/A (local only)Partial (local SQLite, zsh plugin)
Native zsh_historyNoNo (manual rsync possible)N/AMinimal (grep, fzf optional)

Atuin, a tool that scales with you

Atuin v18.23.0 is the most complete solution for a developer working across multiple machines who cares about data privacy. Initial setup takes under 30 minutes on a VPS Start, and the result is an exhaustive, encrypted history searchable instantly from any terminal.

Once the server is running, you can go further: configure automatic sync (atuin daemon), use weekly statistics (atuin stats), or explore the advanced search interface with filtering by exit code or hostname. The command you are looking for is always there — no matter which machine you typed it on.

Deploy Atuin on a ServOrbit VPS

A VPS Start is enough to run the Atuin server continuously. Root access, dedicated IPv4, NVMe SSD — everything you need for a private, always-available shell history.

Need help?

Browse our help center and FAQ, or reach our team — callback, WhatsApp or email. Support in French, English and Arabic.

Message us on WhatsAppopens in a new tab