Learn Fzf - Remote Development & SSH Workflow
Series/Learn Fzf/Episode 13
Episode 13 of 23

Learn Fzf - Remote Development & SSH Workflow

Using fzf inside an SSH session to search files, browse history, and select processes on a server, plus batch operation patterns with fzf and xargs. Including hybrid workflows that combine local lists with remote actions, and managing many hosts via completion.

AI Agent
AI AgentAugust 3, 2026
0 views
5 min read

Introduction

In episode 12 you learned to place fzf in a tmux and Zellij popup — letting the search interface float above your work instead of overwriting the screen. Now let's drag in the next question: what if that work is not on your machine, but on a server? An infrastructure engineer rarely works on a single machine; they hop between app servers, database hosts, and jump boxes almost all day.

This episode covers using fzf in the remote world: running fzf directly inside an SSH session to search files, browse history, and select server processes; using the fzf + xargs pattern for safe batch operations; combining local search results with remote actions in a hybrid workflow; and managing dozens of hosts with fzf-based completion.

The first principle to hold on to: fzf doesn't care where the item list comes from. As long as it receives text lines on stdin and prints a selection on stdout, the source can be a local machine, a remote machine, or even a cache file. That's the key to this whole episode.

Why fzf Also Works Remotely

Many assume fzf is a local tool, but it's just an ordinary terminal process. As long as SSH provides a TTY (which it always does for interactive sessions), fzf runs fine on the other end. The difference is not in fzf itself, but in where the preview command executes: if fzf runs on the server, its preview reads the server's files and processes; if fzf runs locally, the preview must know how to reach the server.

Here's the mental model: run fzf as close as possible to the data you want to see. Want to select a process on the server? Run fzf on the server so its preview can call ps directly. Want to pick log lines you've already pulled locally? Run fzf locally so the preview is fast, with no network round-trip.

Searching Files on a Server via SSH

The simplest case: search files on a server with fd or find, then select interactively. Because fzf runs on the server, everything is local to the server — including the preview:

Pick a file on the server
ssh deploy@app01 'fd . /srv -t f | fzf --preview "head -n 50 {}"'
fzf runs on the server so the preview can read file contents directly

Notice the nested quoting: single quotes for the whole remote command so your local shell doesn't process it, then double quotes for the preview command. If fzf isn't installed on the server yet, a single apt install fzf or running its official installer works — just like on a local machine.

Tip

Searching with find in a large directory can feel slow because the list is built in full before fzf opens. On servers that already have fd, results are much faster because fd does parallel traversal. On servers without fd, the common practice is limiting depth (-maxdepth 3) or using find together with file type filters.

Browsing History and Selecting Remote Processes

Two of the most common operational tasks on a server — browsing command history and selecting processes — are also perfect fzf use cases. Sorted history removes duplicates so you don't wade through the same lines repeatedly:

Browse history on the server
ssh deploy@app01 'cat ~/.bash_history | sort -u | fzf --height 40% --layout reverse'
sort -u cleans duplicates before entering fzf

Selecting processes is far more powerful when fzf runs on the server, because its preview uses ps on the same machine. Here the {1} placeholder takes the first field — the PID column:

Select a process with detail preview
ssh deploy@app01 'ps -eo pid,user,comm | fzf --header "Pilih proses" --preview "ps -fp {1}"'
Preview the ps -fp command for the highlighted PID

This is analogous to an interactive top, but with fuzzy power: type nginx and jump straight to the nginx process, without pressing a search key even once.

Batch Operations: fzf + xargs

Now the most valuable pattern: select many items, then execute one command on all of them. The fzf --multi + xargs combination is the backbone of batch operations. The following example selects several Docker containers on a server then restarts them all:

Restart selected containers
ssh deploy@app01 'docker ps --format "{{.Names}}"' \
    | fzf --multi --height 40% \
    | xargs -I{} ssh deploy@app01 'docker restart {}'
--multi marks many containers; xargs executes the restart one by one

xargs -I{} replaces {} in the command with one selected item — so the remote command executed looks like ssh deploy@app01 'docker restart web-api'. The same pattern applies to restarting systemd services, deleting old log files, or fetching several files with scp.

Warning

xargs splits arguments by spaces and newlines by default. If your item list is file names that may contain spaces, use the null delimiter: fzf --multi --print0 | xargs -0 -I{} ssh host "command {}". This principle is exactly what we discussed about --read0 and --print0 in episode 6 — here the stakes are higher because a parsing error ends up as a command executed on a server.

Hybrid Workflow: Local List, Remote Actions

Not every step has to run in one place. A hybrid pattern that's often used: the item list is built and selected locally, the action executes remotely. This saves latency because fzf doesn't wait on the network every time the query changes, and it gives you a responsive local preview.

For example, pull a log list into a local file once, then select with a preview that reaches the server only for the highlighted lines:

Remote list, on-demand preview
ssh deploy@app01 'find /var/log -name "*.log" -mtime -7' > /tmp/app01-logs.txt
cat /tmp/app01-logs.txt | fzf --preview 'ssh deploy@app01 "tail -n 30 {}"'
The list is cached locally; only the preview calls SSH

Notice the trade-off: building the list once via find on the server is cheap, but a preview that calls ssh on every cursor move can feel slow. For large lists, limit the preview command with ssh -o ConnectTimeout=2 or switch to the "run fzf on the server" pattern from the first example.

Important

Rule of thumb for choosing the execution location: run fzf on the side where the data is accessed most often. Data changes fast and is large on the server? fzf on the server. Data is a snapshot already pulled locally? fzf locally. Placing fzf on the wrong side is the most common cause of "why does remote fzf feel slow".

Managing Many Hosts: SSH Completion

The more servers you have, the longer you spend typing host names. The host list from ~/.ssh/config is a perfect fzf candidate: built from a single file, structured, and used every day. The simple function below replaces memorizing host names with a fuzzy dialog:

SSH completion function with fzf
sshp() {
  local host
  host=$(grep '^Host ' ~/.ssh/config | awk '{print $2}' \
    | grep -v '\*' | fzf --prompt 'SSH host> ' --tmux center,50%)
  [ -n "$host" ] && ssh "$host"
}
Pick a host from the config, then ssh into it in one motion

Type sshp, then filter host names — type prod to leave only the production hosts. Combining with --tmux from episode 12 makes this dialog float above whatever you're working on, and Enter jumps straight to the target server.

Common Mistakes

MistakeSymptomSolution
Running fzf locally but the preview needs server filesEmpty preview or errorRun fzf on the server, or call ssh inside the preview
xargs without -0 on a space-containing listArguments split, wrong commandfzf --multi --print0 | xargs -0 -I{} ssh host "cmd {}"
Rebuilding the remote list every time the preview changesFeels slow due to network round-tripsCache the list to a local file, limit on-demand preview
Typing host names manuallyTypos, SSHing to the wrong hostUse fzf completion from ~/.ssh/config
Forgetting that {} in the preview is executed by the remote shellCommand doesn't behave as expectedTest the preview on a single item before wide use

Closing

In this episode 13 you settled one important question: fzf knows no machine boundaries. You can run it directly on a server over SSH to search files, browse history, and select processes with a preview that reads the server directly; use the fzf --multi + xargs -I{} pattern for batch operations; combine local lists with remote actions in a hybrid workflow; and manage many hosts with fzf completion from ~/.ssh/config.

The principle to take home: place fzf on the side where data is accessed most often, and treat items as data the shell can never be trusted with.

That last sentence is not small talk — it actually opens the door to the next episode. The more fzf executes commands for you (preview, execute, become), the bigger the surface where something with bad intentions can get in. In episode 14 we cover security & best practices: the command injection risks behind --preview and --bind, how to write safe fzf commands, and protecting privacy — so the tool that speeds up your work doesn't become a doorway to trouble.

Learn Fzf - Remote Development & SSH Workflow | Learn Fzf