An open API service indexing awesome lists of open source software.

https://github.com/uhs-robert/sshfs.nvim

📡 sshfs.nvim integrates with Neovim, using SSH and SSHFS to manage remote systems as if they were your local files.
https://github.com/uhs-robert/sshfs.nvim

fzf-lua lua neo-tree neovim neovim-plugin netrw nnn nvim nvim- nvim-lua nvim-tree oil-nvim ranger snacks-nvim ssh sshfs telescope-nvim yazi

Last synced: 5 days ago
JSON representation

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

Awesome Lists containing this project

README

          


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.

✨ What's New / 🚨 Breaking Changes


🚨 v2.0 Breaking Changes

Config restructure with hooks




  • mounts.unmount_on_exithooks.on_exit.auto_unmount


  • mounts.auto_change_dir_on_mounthooks.on_mount.auto_change_to_dir


  • ui.file_pickerui.local_picker


  • ui.file_picker.auto_open_on_mounthooks.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:


    • editconfig


    • browsefiles


    • change_to_mount_dirchange_dir




  • Keymaps: The following have beep deprecated:


    • open_dirchange_dir


    • openfiles


    • editconfig



## ✨ 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/](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](https://github.com/macfuse/macfuse/wiki/File-Systems-%E2%80%90-SSHFS)

## 📦 Installation

### Lazy.nvim (Recommended)

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

### Packer.nvim

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

### vim-plug

```vim
Plug 'uhs-robert/sshfs.nvim'
```

Then in your `init.lua`:

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

### Manual Installation

1. Clone the repository:

```bash
git clone https://github.com/uhs-robert/sshfs.nvim ~/.local/share/nvim/site/pack/plugins/start/sshfs.nvim
```

2. Add to your `init.lua`:

```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:

```lua
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 = "m", -- change keymap prefix (default: m)
keymaps = {
mount = "mm", -- creates an ssh connection and mounts via sshfs
unmount = "mu", -- disconnects an ssh connection and unmounts via sshfs
unmount_all = "mU", -- disconnects all ssh connections and unmounts via sshfs
explore = "me", -- explore an sshfs mount using your native editor
change_dir = "md", -- change dir to mount
command = "mo", -- run command on mount
config = "mc", -- edit ssh config
reload = "mr", -- manually reload ssh config
files = "mf", -- browse files using chosen picker
grep = "mg", -- grep files using chosen picker
terminal = "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](https://man7.org/linux/man-pages/man1/sshfs.1.html).
>
> In addition, sshfs also supports a variety of options from [sftp](https://man7.org/linux/man-pages/man1/sftp.1.html) and [ssh_config](https://man7.org/linux/man-pages/man5/ssh_config.5.html).

> [!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 `m` (fully customizable):

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

If [which-key.nvim](https://github.com/folke/which-key.nvim) is installed, the `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