Stuck? Start here

Follow the previous articles and it works first time eight or nine times out of ten. When it doesn't, it's almost always one of these eight. Find the error message you're seeing and jump straight to it — there's no need to read this page from the top.

Connection timed out (Operation timed out)

Symptom

You run ssh user@host and the screen just sits there. After a long wait you get Operation timed out or Connection timed out, never having been offered a password prompt.

Causes

  • A typo in the hostname (say my-serverr). SSH normally reports Could not resolve hostname immediately for that rather than hanging until it times out — if you see that message, go check the spelling; it isn't what this section covers. What genuinely hangs is a wrong IP: a missing octet, one wrong digit, an address where nothing is listening.
  • SSH isn't on the standard port 22 on the other end and you left out -p.
  • The target machine is asleep, or simply switched off.
  • A firewall in between (home router, corporate network, hotel Wi-Fi) is dropping the connection, so the machine never even receives your request.

Fix

Don't guess — confirm one hop at a time. First check whether the machine responds at all:

ping host-address

If ping gets nothing back, check the IP for typos and whether the machine is asleep or powered off. (Some machines block ping deliberately, in which case a failed ping doesn't mean the machine is unreachable — go straight to the next step.)

Next, check whether anything is actually listening on the SSH port:

nc -vzw 5 host-address 22

Replace 22 with the port the other end really uses. The -w 5 caps the wait at five seconds so a blocked port doesn't leave the command hanging with nothing to show you. succeeded or open means the port is reachable and the problem lies elsewhere. Connection refused means the machine received you but nothing is listening on that port. Another timeout means something in between — usually a firewall — is dropping the packets entirely, and what you need to look at is the router configuration or the restrictions on the network you're on, not the server.

Permission denied (publickey)

Symptom

The connection is refused the instant you make it, printing Permission denied (publickey). without ever offering you a password prompt.

Cause

The server won't accept the key you're presenting. Most often the public key was never placed on the server at all. The other cause, easy to overlook, is that the permissions on ~/.ssh or on authorized_keys are too loose — SSH is extremely strict about this, and if the permissions are wrong it ignores the file completely, regardless of whether the key inside is correct.

Fix

If you've never copied the public key across, the quickest route is ssh-copy-id: it logs in once with a password and appends your local public key to the server's authorized_keys.

ssh-copy-id user@host

That assumes password login is still enabled on the server. If it has already been turned off (see Password is correct but rejected below), you'll need someone with console access to that machine to paste the public key into ~/.ssh/authorized_keys directly.

If the key really is in there — one per line, complete, not truncated — and you're still refused, the permissions are probably too loose and SSH is therefore ignoring the whole file. Check them against the table below. The last row is the one people miss: even with ~/.ssh and authorized_keys both correct, if your home directory itself is group- or world-writable, StrictModes will still refuse.

PathRequired permissionsCommand
~/.ssh700chmod 700 ~/.ssh
~/.ssh/authorized_keys600chmod 600 ~/.ssh/authorized_keys
~ (the home directory itself)Not writable by group or otherschmod go-w ~

Try connecting again once the permissions are fixed. If it still fails, add -v locally to watch the exchange and confirm SSH is really offering the key you think it is:

ssh -v user@host

The output runs to fifty or sixty lines; you don't need to read it closely, just find two. Offering public key: ... means your key really was sent, so the problem isn't on your end. If that's immediately followed by Authentications that can continue: publickey, the server received the key and declined it — the problem is on the server (key in the wrong place, or wrong permissions), so carry on with the steps above.

Password is correct but rejected

Symptom

You're certain the password is right — you may even have pasted it — but SSH keeps returning Permission denied, please try again., or ends without ever prompting you for one.

Cause

This usually has nothing to do with whether the password is correct. The server's SSH configuration has password authentication switched off entirely (PasswordAuthentication no), a very common hardening choice meant to force everyone onto keys and take brute-force guessing off the table. With that set, a correct password will be refused a hundred times out of a hundred.

There's a trap here that costs more time than anything else in this section. Newer systems (Ubuntu after 22.10, Debian 12, and nearly every cloud provider's default image) put a line near the top of sshd_config reading Include /etc/ssh/sshd_config.d/*.conf, pulling in every config file in that directory — and sshd takes the first value it reads for a setting, rather than letting later ones override earlier ones. If /etc/ssh/sshd_config.d/ holds a file like 50-cloud-init.conf that also says PasswordAuthentication no, that takes effect before the line you just edited in the main file. You save, you restart, and you're still refused.

Fix

The preferred fix is to switch to key authentication — see Permission denied (publickey) above for getting your public key into the server's authorized_keys.

Only change the configuration if you administer this server and have decided you do want password login. First find out whether another file will override the value you're about to edit:

sudo grep -r PasswordAuthentication /etc/ssh/sshd_config /etc/ssh/sshd_config.d/

If a file under /etc/ssh/sshd_config.d/ also sets PasswordAuthentication, change that value too (or comment the line out). Once that's clean, edit /etc/ssh/sshd_config and set PasswordAuthentication to yes.

Don't close your current connection yet. A broken sshd configuration can lock you out of the machine completely, so keep this window as your way back in until the new configuration is confirmed working. After saving, check the syntax first:

sudo sshd -t

Only once that prints no errors should you restart the service to apply it:

sudo systemctl restart ssh

On some distributions the service is called sshd rather than ssh; if the line above can't find it, try sudo systemctl restart sshd. Then open a brand-new connection — not the one you kept as a fallback — and confirm password login really works. Only then is it safe to close the old window.

Since this whole setup is built on Tailscale, a safer option than opening password login to the entire internet is to allow passwords only from the Tailscale range and require keys everywhere else. Add this at the end of sshd_config:

Match Address 100.64.0.0/10
    PasswordAuthentication yes

Match All
    PasswordAuthentication no

tmux not found

Symptom

Agentmux reports that it can't find tmux, or you connect yourself, type tmux, and get command not found: tmux.

Cause

tmux isn't installed on the target machine yet. This isn't something you have to handle — Agentmux installs it for you: it detects whether the machine uses apt, yum, dnf, pacman, apk (Alpine), or brew, then runs the matching install command over SSH. In most cases you just wait for it to finish.

When the automatic install fails, the most common reason is that the install command includes sudo but the channel is non-interactive, so sudo has no terminal on which to ask for a password (see sudo: a terminal is required to read the password below). The second most common is installing via Homebrew on a Mac while connected as root — Homebrew refuses to run as root by design.

There's also a rarer case that's easy to misread: tmux is installed, just somewhere Agentmux didn't see when it probed the PATH (Linuxbrew under ~/.linuxbrew, say, or MacPorts under /opt/local/bin). The message looks identical, but the real problem is the PATH rather than a missing install, so handle it the way described in claude: command not found, but it runs fine in the desktop terminal below.

Fix

Start with the error Agentmux showed you. If it asks for a sudo password, or mentions enabling passwordless sudo, entering the password in Agentmux and retrying is usually the fastest path — you don't necessarily need to open a separate window. If that channel also fails, or the message mentions a terminal or TTY, work through sudo: a terminal is required to read the password yourself; once that's sorted the automatic install normally succeeds and you can stop there.

Install by hand only if it still won't install after the sudo problem is ruled out:

# Debian / Ubuntu
sudo apt install -y tmux

# RHEL / CentOS
sudo yum install -y tmux

# Fedora
sudo dnf install -y tmux

# Arch
sudo pacman -S tmux

# Alpine
sudo apk add tmux

# Mac (do not run as root)
brew install tmux

claude: command not found, but it runs fine in the desktop terminal

Symptom

You type claude in Terminal on the Mac and it runs. Connect through Agentmux, or run ssh host claude directly, and you get claude: command not found (or bash: claude: command not found). Same machine, same account, different result — which makes it very easy to assume Agentmux is broken.

Cause

It isn't Agentmux; it's how shells behave. This is the hardest one to reason out on your own, so it's worth the space to explain the mechanism — after which you'll be able to diagnose anything similar yourself.

The Terminal you open on the Mac is an interactive login shell. The reason claude can be found there is usually that nvm, npm, or an installer added a line to some startup file putting the directory containing claude on the PATH. But which startup file differs between zsh and bash, and mixing them up is misleading, so here they are separately:

zsh (the default on macOS): an interactive login shell reads .zshenv.zprofile.zshrc.zlogin, in that order. A connection that runs a single command, like ssh host claude, starts a non-interactive, non-login zsh that reads only .zshenv and skips everything else — including .zshrc, which is where most people's PATH setup lives.

bash: this case is stranger, and the behaviour varies by distribution rather than being uniform bash behaviour. An interactive login shell reads .bash_profile (or .profile), not .bashrc; the reason you think bash reads .bashrc too is that most distributions' default .bash_profile contains a snippet that sources .bashrc if it exists. As for whether bash reads ~/.bashrc when sshd invokes it to run a single command — the ssh host claude case — that depends on the distribution. It's a compile-time special case called SSH_SOURCE_BASHRC: Debian, Ubuntu, and their derivatives carry that patch, so even a non-interactive single-command shell reads ~/.bashrc. Distributions like RHEL, Fedora, and Arch ship upstream bash without it, and in the same situation ~/.bashrc is never read at all. Even on the ones that do read it, almost every default .bashrc opens with:

case $- in
    *i*) ;;
      *) return;;
esac

which means "if this isn't interactive, stop here and read no further." That check is usually right at the top of the file, so even when bash does open .bashrc, it bails out before reaching the PATH line you added — the same outcome as never reading it.

zsh or bash, the result is identical: the line that sets the PATH only ever ran when you opened Terminal yourself. It wasn't read when SSH ran a single command, so the PATH has no entry for claude, and the shell quite correctly says it can't find it. This has nothing to do with whether claude is installed — it was there the whole time; nobody told this particular shell where to look. Nor is it there to make life difficult: skipping those startup files for non-interactive runs is deliberate, so that every remote command doesn't first have to load a pile of interactive aliases and prompt configuration and gain more ways to go wrong.

One more detail: when Agentmux probes the PATH on a Mac it uses zsh -lc (login but not interactive), which likewise skips .zshrc and reads only .zshenv and .zprofile. So for zsh users on a Mac, moving the PATH setup into those two files doesn't just fix your own manual SSH — it's exactly what determines whether Agentmux can locate the agent command in the first place.

Fix

The quickest option: find the absolute path to claude and call it by that path instead of relying on the PATH. In your own Terminal — the window where claude already works — run:

which claude

That prints something like /Users/yourname/.nvm/versions/node/v20.11.0/bin/claude. Put that full path into the field where Agentmux asks for the agent command, in place of plain claude. If you installed node with nvm, that path contains the current version number, so it will break the next time you upgrade node and you'll need to run which claude again. If you'd rather not redo it on every upgrade, use the durable fix below.

The durable fix depends on your shell — don't mix the two:

On zsh (the macOS default): move the lines that set the PATH — typically from nvm, pyenv, or an export PATH=... you added yourself — out of .zshrc and into ~/.zshenv. ~/.zprofile works too; both are read by non-interactive SSH runs and by Agentmux when it probes the PATH.

On bash: don't expect moving them to ~/.profile or ~/.bash_profile to help — ssh host claude starts a non-login shell and neither file is read. On Debian, Ubuntu, or a derivative, moving the PATH lines into ~/.bashrc above the case $- in ... esac block at the top of the file lets them take effect before the interactive check bails out. On RHEL, Fedora, Arch, and similar, ~/.bashrc isn't read in this situation at all, so editing it changes nothing. If you're not sure which camp your distribution is in, or you hit exactly this, fall back to the absolute-path approach above — it works everywhere, and it's the only fix guaranteed to hold across distributions.

Once you've moved them, open a fresh SSH connection and confirm it took effect:

ssh host 'claude --version'

A version number means the PATH really is being read in a non-interactive shell this time.

Tailscale says connected, SSH still can't reach it

Symptom

The Tailscale app on your phone shows both devices green and online, but connecting with Agentmux or plain ssh still fails or times out entirely.

Causes

  • You're connecting to a local network IP (something like 192.168.x.x) rather than the MagicDNS name or the 100.x Tailscale IP. A local IP only works on that same Wi-Fi; the moment the phone switches to mobile data or leaves that network, the address is meaningless.
  • The other device has just dropped off or gone to sleep, and the "online" state in the app hasn't caught up.
  • Tailscale is working fine, but the SSH service on the target machine was never enabled or started. Tailscale only connects the two machines' networks; it doesn't turn SSH on for you.
  • The most common real cause is an expired node key: the target machine needs to reauthenticate, and while the app still shows a green "connected", the machine is effectively no longer on the network. Check the Tailscale admin console to see whether that machine is flagged as needing to sign in again or similarly expired — reauthenticating usually resolves it.

Fix

Tailscale on iOS has no command line, so tailscale status can only be run on the target machine — the Mac or Linux box you're connecting to:

tailscale status

On the target machine, confirm the phone's node also shows as online. On the phone, open the Tailscale app and confirm the target machine looks healthy too — not greyed out, and with no "needs to sign in again" style warning. If both look right, always connect using the MagicDNS name (for example mac-mini.tail1234.ts.net) or the 100.x IP from tailscale status, never the local network IP — that way the address stays valid whether you're on home Wi-Fi or mobile data. Once all of that checks out, go to the target machine and check the SSH service itself is running (on Linux, sudo systemctl status ssh; on a Mac, see Remote Login is on but the Mac still won't accept connections below).

Remote Login is on but the Mac still won't accept connections

Symptom

Remote Login is enabled in System Settings and the Mac is definitely on the network, but SSH still can't connect — or it connects and is refused, and you're certain the password or key is correct.

Causes

  • Remote Login defaults to allowing only the accounts in its "Only these users" list, and the account you're connecting as may not be on it.
  • Once the Mac goes to sleep the network interface sleeps with it, so SSH has nothing to reach. This has nothing to do with whether Remote Login is on; the machine simply isn't answering network requests.

Fix

Open System Settings → General → Sharing (the path on macOS Ventura and later; before Ventura it's System Preferences → Sharing, with no "General" level), find Remote Login, confirm the switch is on, then look inside at the list of users allowed access and check that the account you're connecting as is there — or select "All users".

Next open System Settings → Battery on a laptop, or System Settings → Energy Saver on a desktop that stays on as a server — a Mac mini, Mac Studio, or iMac, which is exactly what mac-mini.tail1234.ts.net in the example above would be. Increase or disable the idle sleep timer, and if there's a wake-for-network option (usually labelled "Wake for network access"), turn that on as well so the Mac can wake in response to an incoming connection and answer SSH even from sleep.

sudo: a terminal is required to read the password

Symptom

Running a command containing sudo through Agentmux or SSH fails immediately with sudo: a terminal is required to read the password, showing no sign of having run at all.

Cause

Normally sudo prints a password prompt to the terminal and waits for you to type before continuing. When the command arrives over a non-interactive channel — SSH running a single remote command, or an install that Agentmux runs for you in the background — there's no real terminal (TTY) to show a prompt on and nowhere for you to type. Finding no TTY available, sudo refuses outright rather than waiting forever for input that will never come.

There's a common misconception here: that having just entered a sudo password manually means sudo won't ask again for a while. Modern sudo does record that you've authenticated, but that record is tied to the TTY you were on (tty_tickets), not to your account or to the machine as a whole. So even if you typed your password in an interactive SSH window moments ago, when Agentmux later runs a command it opens a fresh SSH connection with no TTY, and as far as sudo is concerned that's an entirely different record — you'll be asked again. The cache didn't expire; it was never the same record being consulted.

Fix

Start with what Agentmux is telling you: when an automatic install fails it usually offers to take a sudo password, or suggests enabling passwordless sudo, and then retries. If you see that field, entering the password there is often faster than handling it yourself.

If that channel isn't available, the most direct route is to SSH into the machine yourself, get a normal interactive session, and run the same command there by hand (sudo apt install -y tmux, for instance), entering the password and finishing the job. That works — but not because it unlocked anything for Agentmux's later automated runs. As explained above, the two consult different TTY records. It works simply because you installed the thing interactively yourself. When Agentmux or the next non-interactive command checks again, it finds the software already present, nothing needs installing, and the obstacle is gone.

If you need one specific command to run passwordless over non-interactive channels long-term, sudo visudo can add a NOPASSWD rule scoped to just that command:

youraccount ALL=(ALL) NOPASSWD: /usr/bin/apt

That security trade-off is worse than it looks. Granting NOPASSWD to a package manager like apt is very nearly equivalent to granting full root: apt can be asked to install arbitrary packages, and hooks such as APT::Update::Pre-Invoke can run arbitrary commands as root during the install. This is not a narrow, low-risk exception — it's passwordless root written a different way, and the scope is far wider than it appears. Only configure it if you understand that cost and are deliberately accepting it. In most cases, prefer going back to installing once by hand as described above, and never grant a blanket NOPASSWD: ALL, which removes the password requirement from sudo across the entire machine.