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.
https://github.com/user-attachments/assets/d6453878-f93b-429a-9f36-8580cd9ed889
ssh_config options via ssh -G; optional per-host default paths.rg/find over SSH (snacks, fzf-lua, telescope, mini) while keeping mounts quiet.:SSHFiles, :SSHGrep, :SSHLiveFind/Grep, :SSHTerminal, :SSHCommand, :SSHChangeDir, :SSHConfig, :SSHReload.vim.uv for reliable jobs, sockets, and cleanup.| 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.
To use sshfs.nvim on macOS, follow these steps:
Install macFUSE Download and install macFUSE from the official site: https://macfuse.github.io/
Install SSHFS for macFUSE Use the official SSHFS releases compatible with macFUSE: https://github.com/macfuse/macfuse/wiki/File-Systems-%E2%80%90-SSHFS
{
"uhs-robert/sshfs.nvim",
opts = {
-- Refer to the configuration section below
-- or leave empty for defaults
},
}
use {
"uhs-robert/sshfs.nvim",
config = function()
require("sshfs").setup({
-- Your configuration here
})
end
}
Plug 'uhs-robert/sshfs.nvim'
Then in your init.lua:
require("sshfs").setup({
-- Your configuration here
})
git clone https://github.com/uhs-robert/sshfs.nvim ~/.local/share/nvim/site/pack/plugins/start/sshfs.nvim
init.lua:require("sshfs").setup({
-- Your configuration here
})
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_argstable can accept any configuration option that applies to thesshfscommand. 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 viaconnections.socket_dir). The directory is created automatically with proper permissions (0700) during the first connection.
: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)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 ().
:SSHConnect — pick a host and mount path (home/root/custom/global_paths/host_paths).:SSHFiles, :SSHGrep, or :SSHChangeDir:SSHLiveFind / :SSHLiveGrep (streams over SSH, still mounted):SSHTerminal, :SSHCommand: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.
global_paths with common directories (/var/www, /var/log, ~/.config) to have them available across all hostshost_paths for frequently-used hosts to skip path selectionpreferred_picker for local/remote pickers to force specific file picker(s) instead of auto-detection