Skip to content

πŸ“‘ sshfs.nvim integrates with Neovim, using SSH and SSHFS to manage remote systems as if they were your local files.

License

Notifications You must be signed in to change notification settings

uhs-robert/sshfs.nvim

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

SSH emoji

sshfs.nvim

.nvim

A fast SSHFS/SSH integration for NeoVim that works with your setup.

πŸ•ΆοΈ What It Is & Why

sshfs.nvim mounts hosts from your SSH config and makes them feel local.

You can browse, search, run commands, or open SSH terminals across multiple mounts without changing your workflow.

It stays lightweight and modern: no forced dependencies.

Built for Neovim 0.10+ using the best of both sshfs and ssh in tandem with your existing tools.

sshfs.nvim.mp4
✨ What's New / 🚨 Breaking Changes
🚨 v2.0 Breaking Changes

Config restructure with hooks

  • mounts.unmount_on_exit β†’ hooks.on_exit.auto_unmount
  • mounts.auto_change_dir_on_mount β†’ hooks.on_mount.auto_change_to_dir
  • ui.file_picker β†’ ui.local_picker
  • ui.file_picker.auto_open_on_mount β†’ hooks.on_mount.auto_run

SSH-first ControlMaster required

  • Mounting now tries a non-interactive socket first, then opens an auth terminal. This passes all login responsibility to ssh which enables native support for the ssh authentication flow.

sshfs_options format change

  • connections.sshfs_options must be a key/value table (e.g., { reconnect = true, ConnectTimeout = 5 }); string arrays are ignored.
    • Strings/numbers render as key=value
    • Boolean true/false enables/disables options, the value of nil also disables

Commands, API, and keymap renames (aliases will be removed after January 15, 2026)

  • Commands: The following have beep deprecated:
    • :SSHEdit β†’ :SSHConfig
    • :SSHBrowse β†’ :SSHFiles
  • API: The following have beep deprecated:
    • edit β†’ config
    • browse β†’ files
    • change_to_mount_dir β†’ change_dir
  • Keymaps: The following have beep deprecated:
    • open_dir β†’ change_dir
    • open β†’ files
    • edit β†’ config

✨ Features

  • Uses your toolkit – Auto-detects snacks, telescope, fzf-lua, mini, oil, yazi, nnn, ranger, lf, neo-tree, nvim-tree, or netrw.
  • Auth that sticks – ControlMaster sockets + floating auth handle keys/passwords/2FA once, then reuse for mounts, live search, terminals, git, or scp.
  • Real SSH config support – Honors Include/Match/ProxyJump and all ssh_config options via ssh -G; optional per-host default paths.
  • On-mount hooks – Auto-run find/grep/live find/live grep/terminal or your own function after connecting.
  • Live remote search – Stream rg/find over SSH (snacks, fzf-lua, telescope, mini) while keeping mounts quiet.
  • Multi-mount aware – Connect to several hosts, clean up on exit, and jump between mounts with keymaps or commands.
  • Command suite – :SSHFiles, :SSHGrep, :SSHLiveFind/Grep, :SSHTerminal, :SSHCommand, :SSHChangeDir, :SSHConfig, :SSHReload.
  • Host-aware defaults – Optional global and per-host default paths so you can skip path selection on common servers.
  • Modern Neovim – Built for 0.10+ with vim.uv for reliable jobs, sockets, and cleanup.

πŸ“‹ Requirements

Software Minimum Notes
Neovim >=0.10 Requires vim.uv support
sshfs any sudo dnf/apt/pacman install sshfs or brew install sshfs
SSH client any OpenSSH with ControlMaster support (default). Socket directory created automatically if missing.
SSH config working hosts Hosts come from ~/.ssh/config

Note

For Mac users, see the macOS setup steps below.


🍏 macOS Setup

To use sshfs.nvim on macOS, follow these steps:

  1. Install macFUSE Download and install macFUSE from the official site: https://macfuse.github.io/

  2. Install SSHFS for macFUSE Use the official SSHFS releases compatible with macFUSE: https://github.com/macfuse/macfuse/wiki/File-Systems-%E2%80%90-SSHFS

πŸ“¦ Installation

Lazy.nvim (Recommended)

{
  "uhs-robert/sshfs.nvim",
  opts = {
    -- Refer to the configuration section below
    -- or leave empty for defaults
  },
}

Packer.nvim

use {
  "uhs-robert/sshfs.nvim",
  config = function()
    require("sshfs").setup({
      -- Your configuration here
    })
  end
}

vim-plug

Plug 'uhs-robert/sshfs.nvim'

Then in your init.lua:

require("sshfs").setup({
  -- Your configuration here
})

Manual Installation

  1. Clone the repository:
git clone https://github.com/uhs-robert/sshfs.nvim ~/.local/share/nvim/site/pack/plugins/start/sshfs.nvim
  1. Add to your init.lua:
require("sshfs").setup({
  -- Your configuration here
})

βš™οΈ Configuration

You can optionally customize behavior by passing a config table to setup().

Note

Only include what you want to edit.

Here's the full set of defaults for you to configure:

require("sshfs").setup({
  connections = {
    ssh_configs = {                 -- Table of ssh config file locations to use
      "~/.ssh/config",
      "/etc/ssh/ssh_config",
    },
    -- SSHFS mount options (table of key-value pairs converted to sshfs -o arguments)
    -- Boolean flags: set to true to include, false/nil to omit
    -- String/number values: converted to key=value format
    sshfs_options = {
      reconnect = true,             -- Auto-reconnect on connection loss
      ConnectTimeout = 5,           -- Connection timeout in seconds
      compression = "yes",          -- Enable compression
      ServerAliveInterval = 15,     -- Keep-alive interval (15s Γ— 3 = 45s timeout)
      ServerAliveCountMax = 3,      -- Keep-alive message count
      dir_cache = "yes",            -- Enable directory caching
      dcache_timeout = 300,         -- Cache timeout in seconds
      dcache_max_size = 10000,      -- Max cache size
      -- allow_other = true,        -- Allow other users to access mount
      -- uid = "1000,gid=1000",     -- Set file ownership (use string for complex values)
      -- follow_symlinks = true,    -- Follow symbolic links
    },
    control_persist = "10m",        -- How long to keep ControlMaster connection alive after last use
    socket_dir = vim.fn.expand("$HOME/.ssh/sockets"), -- Directory for ControlMaster sockets
  },
  mounts = {
    base_dir = vim.fn.expand("$HOME") .. "/mnt", -- where remote mounts are created
  },
  global_paths = {
    -- Optionally define default mount paths for ALL hosts
    -- These appear as options when connecting to any host
    -- Examples:
    -- "~/.config",
    -- "/var/www",
    -- "/srv",
    -- "/opt",
    -- "/var/log",
    -- "/etc",
    -- "/tmp",
    -- "/usr/local",
    -- "/data",
    -- "/var/lib",
  },
  host_paths = {
    -- Optionally define default mount paths for specific hosts
    -- These are shown in addition to global_paths
    -- Single path (string):
    -- ["my-server"] = "/var/www/html"
    --
    -- Multiple paths (array):
    -- ["dev-server"] = { "/var/www", "~/projects", "/opt/app" }
  },
  hooks = {
    on_exit = {
      auto_unmount = true,        -- auto-disconnect all mounts on :q or exit
      clean_mount_folders = true, -- optionally clean up mount folders after disconnect
    },
    on_mount = {
      auto_change_to_dir = false, -- auto-change current directory to mount point
      auto_run = "find",          -- "find" (default), "grep", "live_find", "live_grep", "terminal", "none", or a custom function(ctx)
    },
  },
  ui = {
    file_picker = {
      preferred_picker = "auto",  -- one of: "auto", "snacks", "fzf-lua", "mini", "telescope", "oil", "neo-tree", "nvim-tree", "yazi", "lf", "nnn", "ranger", "netrw"
      fallback_to_netrw = true,   -- fallback to netrw if no picker is available
      netrw_command = "Explore",  -- netrw command: "Explore", "Lexplore", "Sexplore", "Vexplore", "Texplore"
    },
    remote_picker = {
      preferred_picker = "auto",  -- one of: "auto", "snacks", "fzf-lua", "telescope", "mini"
    },
  },
  lead_prefix = "<leader>m",      -- change keymap prefix (default: <leader>m)
  keymaps = {
    mount = "<leader>mm",         -- creates an ssh connection and mounts via sshfs
    unmount = "<leader>mu",       -- disconnects an ssh connection and unmounts via sshfs
    unmount_all = "<leader>mU",   -- disconnects all ssh connections and unmounts via sshfs
    explore = "<leader>me",       -- explore an sshfs mount using your native editor
    change_dir = "<leader>md",    -- change dir to mount
    command = "<leader>mo",       -- run command on mount
    config = "<leader>mc",        -- edit ssh config
    reload = "<leader>mr",        -- manually reload ssh config
    files = "<leader>mf",         -- browse files using chosen picker
    grep = "<leader>mg",          -- grep files using chosen picker
    terminal = "<leader>mt",      -- open ssh terminal session
  },
})

Tip

The sshfs_args table can accept any configuration option that applies to the sshfs command. You can learn more about sshfs mount options here.

In addition, sshfs also supports a variety of options from sftp and ssh_config.

Note

ControlMaster sockets are stored at ~/.ssh/sockets/%C (configurable via connections.socket_dir). The directory is created automatically with proper permissions (0700) during the first connection.

πŸ”§ Commands

  • :checkhealth sshfs - Verify dependencies and configuration
  • :SSHConnect [host] - Mount a remote host
  • :SSHDisconnect - Unmount current host
  • :SSHDisconnectAll - Unmount all hosts
  • :SSHConfig - Edit SSH config files
  • :SSHReload - Reload SSH configuration
  • :SSHFiles - Find files with auto-detected picker
  • :SSHGrep [pattern] - Search files with auto-detected tool
  • :SSHLiveFind [pattern] - Stream remote find/fd results over SSH (snacks/fzf-lua/telescope/mini)
  • :SSHLiveGrep [pattern] - Stream remote rg/grep results over SSH (snacks/fzf-lua/telescope/mini)
  • :SSHExplore - Open file browser on mount
  • :SSHChangeDir - Change directory to mount (tcd)
  • :SSHCommand [cmd] - Run custom command (e.g. Oil, Telescope)
  • :SSHTerminal - Open terminal session (reuses auth)

🎹 Key Mapping

Default keybindings under <leader>m (fully customizable):

Mapping Description
<leader>mm Mount an SSH host
<leader>mu Unmount an active session
<leader>mU Unmount all active sessions
<leader>me Explore SSH mount via native edit
<leader>md Change dir to mount
<leader>mo Run command on mount
<leader>mc Edit SSH config
<leader>mr Reload SSH configuration
<leader>mf Find files
<leader>mg Grep files
<leader>mF Live find (remote)
<leader>mG Live grep (remote)
<leader>mt Open SSH terminal session

If which-key.nvim is installed, the <leader>m group will be labeled with a custom icon (󰌘).

πŸš€ Usage

  1. :SSHConnect β€” pick a host and mount path (home/root/custom/global_paths/host_paths).
  2. Work from the mount:
    • :SSHFiles, :SSHGrep, or :SSHChangeDir
    • Live remote search: :SSHLiveFind / :SSHLiveGrep (streams over SSH, still mounted)
    • Terminals/commands: :SSHTerminal, :SSHCommand
  3. Disconnect with :SSHDisconnect (or let hooks.on_exit.auto_unmount handle it).

Auth flow: keys first, then floating terminal for passphrases/passwords/2FA; ControlMaster keeps the session alive across operations.

πŸ’‘ Tips

  • Use SSH keys for faster connections (no password prompts)
  • Configure global_paths with common directories (/var/www, /var/log, ~/.config) to have them available across all hosts
  • Configure host_paths for frequently-used hosts to skip path selection
  • Set preferred_picker for local/remote pickers to force specific file picker(s) instead of auto-detection