适用人群:需要日常 SSH 到 Linux 服务器的 Mac 用户,希望用最小成本获得类似 Xshell 的体验
核心工具 :iTerm2 +
~/.ssh/config+ Shell 函数(scp/rsync)不需要安装:任何第三方传输工具、sshfs、Electron App
目录
- [背景:为什么不用 Xshell / SecureCRT?](#背景:为什么不用 Xshell / SecureCRT?)
- 方案全景
- 环境准备
- [第一步:生成 SSH 密钥对](#第一步:生成 SSH 密钥对)
- [第二步:配置
~/.ssh/config](#第二步:配置 ~/.ssh/config) - 第三步:把公钥上传到服务器
- [第四步:配置 iTerm2 Profile](#第四步:配置 iTerm2 Profile)
- 第五步:配置文件传输函数
- 日常使用示例
- 多台服务器的管理技巧
- 常见问题排查
- 完整配置汇总
1. 背景
Xshell 是 Windows 上老牌的 SSH 客户端,功能全面,但它没有原生的 macOS 版本。Mac 用户通常用 SecureCRT、Termius、Royal TSX 等工具替代,这些工具都要付费或有功能限制。
其实 macOS 自带的工具链已经相当完整:
- OpenSSH 客户端:系统自带,支持密钥认证、连接复用、Keep-alive 等
- scp / rsync:文件传输,同样系统自带
~/.ssh/config:SSH 的"通讯录",把服务器信息存进去,以后不用记 IP
配合 iTerm2 的 Profiles 功能,整套体验并不比 GUI 工具差,而且:
- 零额外软件成本(iTerm2 本身免费)
- 无需维护依赖,macOS 系统升级不影响
- 纯文本配置,备份只需要复制几个文件
本文把这套工作流从头到尾完整走一遍。
2. 方案全景
完成配置后,整个工作流是这样的:
shell
连接服务器:
Cmd+O → 输入服务器名 → 回车 → 直接进入 Shell(无需输入密码)
上传文件:
put ~/Downloads/config.json /etc/myapp/
下载文件:
get /var/log/app.log ~/Desktop/
同步整个目录:
sync-up ./dist /var/www/myapp
涉及的文件清单:
| 文件 | 作用 |
|---|---|
~/.ssh/config |
保存服务器地址、用户名、密钥路径等,SSH/scp/rsync 共用 |
~/.ssh/id_ed25519 |
私钥(只存本机,不要泄露) |
~/.ssh/id_ed25519.pub |
公钥(上传到服务器的 ~/.ssh/authorized_keys) |
~/.zshrc |
Shell 配置,存放文件传输快捷函数 |
3. 环境准备
3.1 确认 OpenSSH 已安装
macOS 自带 OpenSSH,正常不需要额外安装,确认一下:
bash
ssh -V
输出类似 OpenSSH_9.x, LibreSSL x.x.x 即为正常。
3.2 安装 Homebrew(如果还没装)
后续不需要用 Homebrew 安装任何东西,但如果你的 Mac 上还没有,建议顺手装上,以后会经常用到:
bash
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
3.3 安装 iTerm2
前往 https://iterm2.com 下载,或用 Homebrew:
bash
brew install --cask iterm2
安装完毕后打开,后续操作都在 iTerm2 里完成。
4. 第一步:生成 SSH 密钥对
SSH 密钥认证是比密码登录更安全、更方便的方式。生成一次,以后连接服务器无需每次输密码。
4.1 检查是否已有密钥
bash
ls ~/.ssh/
如果看到 id_ed25519 和 id_ed25519.pub(或 id_rsa 和 id_rsa.pub),说明已经有了,可以跳过生成步骤,直接用现有密钥。
4.2 生成新密钥
推荐用更现代、更安全的 Ed25519 算法(比老式的 RSA 4096 更短、更快、同样安全):
bash
ssh-keygen -t ed25519
执行后会有几个交互提示:
bash
Generating public/private ed25519 key pair.
Enter file in which to save the key (/Users/yourname/.ssh/id_ed25519):
直接回车,使用默认路径即可。
bash
Enter passphrase (empty for no passphrase):
这里是给私钥设置一个"加密密码"。有两种选择:
- 留空(直接回车):私钥不加密,连接服务器完全不用输任何密码,最方便,但私钥文件如果泄露有风险
- 设置 passphrase:每次使用私钥时需要输入这个密码(可以配合 macOS Keychain 缓存,只需第一次输入)
个人服务器日常使用,留空即可。如果是工作环境或对安全要求高,建议设置 passphrase 并结合 ssh-agent 使用。
生成完毕后:
bash
ls ~/.ssh/
# 应该看到:
# id_ed25519 ← 私钥,绝对不要传给任何人
# id_ed25519.pub ← 公钥,这个需要上传到服务器
4.3 设置密钥文件权限
SSH 对私钥文件权限有严格要求,权限过宽会拒绝使用:
bash
chmod 700 ~/.ssh
chmod 600 ~/.ssh/id_ed25519
chmod 644 ~/.ssh/id_ed25519.pub
5. 第二步:配置 ~/.ssh/config
这是整个方案的核心文件,相当于 SSH 的"通讯录"。配置好后,ssh、scp、rsync 都会自动读取这里的信息,不用每次手动输入 IP、用户名、端口。
5.1 创建或编辑配置文件
bash
touch ~/.ssh/config
chmod 600 ~/.ssh/config # 权限必须是 600,否则 SSH 会忽略此文件
open -e ~/.ssh/config # 用文本编辑器打开(或用 vim/nano 也行)
5.2 配置文件语法说明
一个基本的 Host 块如下:
bash
Host <别名>
HostName <IP 或域名>
User <登录用户名>
Port <SSH 端口,默认 22>
IdentityFile <私钥路径>
Host后面的是别名 ,自己起,用于连接时的简称(ssh 别名)- 每一行的配置项前面有 4 个空格缩进(或一个 Tab),这是规范写法
#开头的行是注释
5.3 完整配置示例
bash
# ========== 服务器 1:个人云服务器 ==========
Host myserver
HostName 1.2.3.4
User root
Port 22
IdentityFile ~/.ssh/id_ed25519
# ========== 服务器 2:公司开发机 ==========
Host dev
HostName dev.mycompany.com
User ubuntu
Port 2222
IdentityFile ~/.ssh/id_ed25519
# ========== 全局设置(对所有连接生效)==========
Host *
# 每 60 秒发一次心跳包,防止长时间空闲被服务器踢掉
ServerAliveInterval 60
# 心跳失败 3 次才真正断开
ServerAliveCountMax 3
# 开启连接复用:同一台服务器的多个 SSH 连接共用一条 TCP 连接
# 好处:再次连接同一台服务器时几乎瞬间建立(跳过握手阶段)
ControlMaster auto
ControlPath ~/.ssh/control-%r@%h:%p
ControlPersist 10m
配置好后验证一下:
bash
ssh -T myserver
# 如果能连上并出现 Shell,说明配置正确
6. 第三步:把公钥上传到服务器
只需做一次。把本机的公钥添加到服务器的 authorized_keys 文件里,以后就能免密码登录。
6.1 方法一:使用 ssh-copy-id(推荐,最简单)
bash
ssh-copy-id -i ~/.ssh/id_ed25519.pub myserver
这条命令会:
- 用密码登录服务器(最后一次需要输密码)
- 自动把公钥追加到服务器的
~/.ssh/authorized_keys - 自动设置好相关权限
完成后立刻测试:
bash
ssh myserver
# 应该不用输密码就能进入 Shell
6.2 方法二:手动上传(如果 ssh-copy-id 不可用)
先把公钥内容复制到剪贴板:
bash
cat ~/.ssh/id_ed25519.pub | pbcopy
然后用密码 SSH 登录到服务器,手动添加:
bash
ssh root@1.2.3.4 # 用 IP 直接登录(此时还没免密)
# 登录后在服务器上执行:
mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "粘贴你的公钥内容" >> ~/.ssh/authorized_keys
chmod 600 ~/.ssh/authorized_keys
6.3 排查:为什么还在要密码?
如果上传公钥后仍然要求输密码,通常是以下原因之一:
bash
# 在服务器上检查权限
ls -la ~/.ssh/
# .ssh 目录权限必须是 700(drwx------)
# authorized_keys 文件权限必须是 600(-rw-------)
# 检查 SSH 服务端配置(需要 root 权限)
grep -E "PubkeyAuthentication|AuthorizedKeysFile" /etc/ssh/sshd_config
# 确认 PubkeyAuthentication yes 没有被注释掉
7. 第四步:配置 iTerm2 Profile
配置好 Profile 后,打开 iTerm2 → Cmd+O 搜索服务器名 → 直接连接,和 Xshell 双击服务器的体验一样。
7.1 新建 Profile
- 打开 iTerm2,按
Cmd ,(逗号)打开偏好设置 - 点击左侧 Profiles 标签
- 点击左下角 + 新建一个 Profile
7.2 配置 General 标签
- Name :起一个好认的名字,比如
myserver - 生产环境 - Command :选择 Command,输入框里填:
bash
ssh myserver
如果你想连接后自动进入某个目录,可以:
bash
ssh myserver -t 'cd /var/www && exec $SHELL'

7.3 配置 Colors 标签(可选,区分不同服务器)
给不同的服务器设置不同的背景颜色,是个很实用的习惯------一眼就能看出自己在哪台机器上,避免误操作:
- 生产环境:深红色背景 (
#1a0000),时刻提醒自己"谨慎操作" - 测试环境:深蓝色背景 (
#00001a) - 开发环境:默认黑色
设置方法:Colors → Color Presets → 下拉选择或自定义背景色(Background)
7.4 使用 Profile 连接
配置完成后,两种方式打开:
Cmd+O:打开 Profile 搜索框,模糊搜索服务器名,回车连接Cmd+N:在弹出的窗口选择 Profile
8. 第五步:配置文件传输函数
把以下内容追加到 ~/.zshrc 文件末尾,然后重载配置。
8.1 打开 zshrc
bash
open -e ~/.zshrc
在文件最末尾加入以下内容:
8.2 文件传输函数完整代码
bash
# ============================================================
# SSH 文件传输快捷函数
# 前提:~/.ssh/config 中已配置好 Host 别名(如 myserver)
# ============================================================
# ------ scp:适合单个文件,简单直接 ------
# 上传文件
# 用法: put 本地文件路径 [远程目录,默认家目录]
put() {
if [ -z "$1" ]; then
echo "用法: put <本地文件路径> [远程目录]"
echo "示例: put ~/Downloads/app.jar /opt/myapp"
return 1
fi
local file="$1"
local remote_dir="${2:-~}"
echo "正在上传 $file → myserver:$remote_dir"
scp "$file" myserver:"$remote_dir" && echo "✓ 上传完成"
}
# 下载文件
# 用法: get 远程文件路径 [本地存放目录,默认当前目录]
get() {
if [ -z "$1" ]; then
echo "用法: get <远程文件路径> [本地目录]"
echo "示例: get /var/log/app.log ~/Desktop"
return 1
fi
local remote_file="$1"
local local_dir="${2:-.}"
echo "正在下载 myserver:$remote_file → $local_dir"
scp myserver:"$remote_file" "$local_dir" && echo "✓ 下载完成"
}
# 上传整个文件夹(递归)
# 用法: put-dir 本地文件夹路径 [远程目录,默认家目录]
put-dir() {
if [ -z "$1" ]; then
echo "用法: put-dir <本地文件夹路径> [远程目录]"
return 1
fi
echo "正在上传目录 $1 → myserver:${2:-~}"
scp -r "$1" myserver:"${2:-~}" && echo "✓ 目录上传完成"
}
# 下载整个文件夹(递归)
# 用法: get-dir 远程文件夹路径 [本地目录,默认当前目录]
get-dir() {
if [ -z "$1" ]; then
echo "用法: get-dir <远程文件夹路径> [本地目录]"
return 1
fi
echo "正在下载目录 myserver:$1 → ${2:-.}"
scp -r myserver:"$1" "${2:-.}" && echo "✓ 目录下载完成"
}
# ------ rsync:适合反复同步,只传变化部分,速度更快 ------
# 参数说明:
# -a 归档模式(保留权限、时间戳、符号链接等)
# -v 显示传输的文件列表
# -z 传输时压缩(对文本文件效果好)
# --progress 显示每个文件的进度条
# 上传并同步(本地 → 远程)
# 用法: sync-up 本地路径 [远程目录,默认家目录]
sync-up() {
if [ -z "$1" ]; then
echo "用法: sync-up <本地路径> [远程目录]"
echo "示例: sync-up ./dist /var/www/myapp"
return 1
fi
rsync -avz --progress "$1" myserver:"${2:-~}"
}
# 下载并同步(远程 → 本地)
# 用法: sync-down 远程路径 [本地目录,默认当前目录]
sync-down() {
if [ -z "$1" ]; then
echo "用法: sync-down <远程路径> [本地目录]"
echo "示例: sync-down /var/log/myapp ~/local-logs"
return 1
fi
rsync -avz --progress myserver:"$1" "${2:-.}"
}
8.3 使配置生效
bash
source ~/.zshrc
验证函数已加载:
bash
type put
# 应该输出函数定义内容,而不是 "not found"
9. 日常使用示例
9.1 连接服务器
bash
# 方式一:直接在终端里连
ssh myserver
# 方式二:iTerm2 里用 Cmd+O 搜索 Profile 名一键连接
9.2 上传文件
bash
# 上传单个文件到服务器家目录
put ~/Downloads/config.json
# 上传单个文件到指定目录
put ~/Downloads/app-v2.jar /opt/myapp/
# 上传整个文件夹
put-dir ~/projects/frontend /var/www/
# 用 rsync 同步目录(只传变化的部分,适合反复部署)
sync-up ./build /var/www/myapp
9.3 下载文件
bash
# 下载日志文件到当前目录
get /var/log/nginx/error.log
# 下载配置文件到桌面
get /etc/nginx/nginx.conf ~/Desktop/
# 下载整个日志目录
get-dir /var/log/myapp ~/local-logs/
# 用 rsync 同步(适合增量备份)
sync-down /var/www/myapp/uploads ~/backups/uploads
9.4 在 Shell 里直接用 Tab 补全远程路径
配合 ~/.ssh/config,scp 命令本身支持 Tab 补全远程路径(需要先连接过一次,或使用 zsh 的 ssh 补全插件),比如:
bash
scp myserver:/var/log/<Tab> # 可以补全远程目录
10. 多台服务器的管理技巧
10.1 在 ~/.ssh/config 里加更多服务器
格式完全相同,一台服务器一个 Host 块,别名自己起:
bash
Host prod
HostName 111.222.333.444
User deploy
Port 22
IdentityFile ~/.ssh/id_ed25519
Host staging
HostName 192.168.1.100
User ubuntu
Port 22
IdentityFile ~/.ssh/id_ed25519
Host db-server
HostName 10.0.0.5
User root
Port 22
IdentityFile ~/.ssh/id_ed25519
10.2 给每台服务器加文件传输函数
在 ~/.zshrc 里,对每台服务器复制一套函数,修改函数名和 Host 别名即可:
bash
# 针对 prod 服务器
put-prod() { scp "$1" prod:"${2:-~}"; }
get-prod() { scp prod:"$1" "${2:-.}"; }
sync-prod() { rsync -avz --progress "$1" prod:"${2:-~}"; }
# 针对 staging 服务器
put-staging() { scp "$1" staging:"${2:-~}"; }
get-staging() { scp staging:"$1" "${2:-.}"; }
sync-staging() { rsync -avz --progress "$1" staging:"${2:-~}"; }
10.3 通用版本:支持传入服务器名
如果服务器多了,也可以写一个通用函数,第一个参数传服务器名:
bash
# 通用上传:transfer-put 服务器别名 本地文件 [远程目录]
transfer-put() {
local server="$1"
local file="$2"
local remote_dir="${3:-~}"
if [ -z "$server" ] || [ -z "$file" ]; then
echo "用法: transfer-put <服务器别名> <本地文件> [远程目录]"
return 1
fi
scp "$file" "$server":"$remote_dir"
}
# 用法示例:
# transfer-put prod ~/app.jar /opt/
# transfer-put staging ~/config.json /etc/myapp/
10.4 用 iTerm2 的 Profiles 分组管理
在 iTerm2 Profiles 里,每台服务器对应一个 Profile,Tags 字段可以用来分组(比如 production、staging、personal),Cmd+O 搜索框支持按 Tag 过滤。
11. 常见问题排查
问题 1:Permission denied (publickey) --- 公钥认证失败
排查步骤:
bash
# 加 -v 参数查看详细调试信息
ssh -v myserver
# 重点看这几行:
# "Offering public key: ~/.ssh/id_ed25519" ← 有这行说明本地在尝试用密钥
# "Authentications that can continue: publickey" ← 服务器支持密钥认证
# "Permission denied" ← 服务器拒绝了这个密钥
常见原因和解决方法:
bash
# 原因 1:服务器上 .ssh 目录或 authorized_keys 权限不对
# 在服务器上执行:
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
# 原因 2:公钥没有正确添加到 authorized_keys
# 在服务器上检查:
cat ~/.ssh/authorized_keys
# 确认里面有你的公钥内容(以 "ssh-ed25519" 开头的那一行)
# 原因 3:本地私钥路径写错了
# 检查 ~/.ssh/config 里的 IdentityFile 路径是否存在
ls -la ~/.ssh/
问题 2:Connection refused --- 连不上服务器
bash
# 先确认网络是否通
ping 1.2.3.4
# 确认 SSH 端口是否开放(默认 22)
nc -zv 1.2.3.4 22
# 出现 "succeeded" 表示端口可达
# 如果端口不是 22,确认 ~/.ssh/config 里 Port 填的是否正确
问题 3:WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!
这个警告说明服务器的 SSH 指纹变了(常见于服务器重装系统后)。如果你确认是自己的服务器且确实重装过,可以删掉旧记录:
bash
ssh-keygen -R myserver
# 或者指定 IP:
ssh-keygen -R 1.2.3.4
然后重新连接,确认新指纹后会自动记录。
⚠️ 如果你没有重装服务器,这个警告可能意味着中间人攻击,不要轻易忽略,先联系服务器管理员确认。
问题 4:scp 或 rsync 命令提示找不到
scp 是系统自带的,通常不会缺失。rsync 在新版 macOS 上可能需要单独安装:
bash
brew install rsync
问题 5:长时间不操作后连接断了
在 ~/.ssh/config 的 Host * 块里加入 Keep-alive 设置(本文第 5 步已包含):
Host *
ServerAliveInterval 60
ServerAliveCountMax 3
这会让 SSH 客户端每 60 秒发一次心跳包,防止服务器因为空闲超时把你踢掉。
问题 6:~/.ssh/config 不生效
最常见的原因是文件权限不对:
bash
# 检查权限
ls -la ~/.ssh/config
# 必须是 -rw------- (即 600)
# 修复权限
chmod 600 ~/.ssh/config
12. 完整配置汇总
把这个章节当做"完整模板",按需复制修改。
~/.ssh/config
bash
# ============ 服务器 1 ============
Host myserver
HostName 1.2.3.4 # ← 改成你的服务器 IP 或域名
User root # ← 改成你的用户名
Port 22
IdentityFile ~/.ssh/id_ed25519
# ============ 全局设置 ============
Host *
ServerAliveInterval 60
ServerAliveCountMax 3
ControlMaster auto
ControlPath ~/.ssh/control-%r@%h:%p
ControlPersist 10m
~/.zshrc 末尾追加部分
bash
# ============ SSH 文件传输函数 ============
put() {
[ -z "$1" ] && echo "用法: put <本地文件> [远程目录]" && return 1
scp "$1" myserver:"${2:-~}" && echo "✓ 上传完成"
}
get() {
[ -z "$1" ] && echo "用法: get <远程文件> [本地目录]" && return 1
scp myserver:"$1" "${2:-.}" && echo "✓ 下载完成"
}
put-dir() {
[ -z "$1" ] && echo "用法: put-dir <本地目录> [远程目录]" && return 1
scp -r "$1" myserver:"${2:-~}" && echo "✓ 目录上传完成"
}
get-dir() {
[ -z "$1" ] && echo "用法: get-dir <远程目录> [本地目录]" && return 1
scp -r myserver:"$1" "${2:-.}" && echo "✓ 目录下载完成"
}
sync-up() {
[ -z "$1" ] && echo "用法: sync-up <本地路径> [远程目录]" && return 1
rsync -avz --progress "$1" myserver:"${2:-~}"
}
sync-down() {
[ -z "$1" ] && echo "用法: sync-down <远程路径> [本地目录]" && return 1
rsync -avz --progress myserver:"$1" "${2:-.}"
}
快速安装脚本
如果你想一次性把以上配置都跑完(请先阅读内容,确认符合你的情况再执行):
bash
# 1. 生成密钥(如果还没有)
[ ! -f ~/.ssh/id_ed25519 ] && ssh-keygen -t ed25519 -C "$(whoami)@mac"
# 2. 设置权限
chmod 700 ~/.ssh
chmod 600 ~/.ssh/id_ed25519
# 3. 创建 config 文件(如果还没有)
touch ~/.ssh/config && chmod 600 ~/.ssh/config
# 4. 把公钥复制到剪贴板(手动添加到服务器)
cat ~/.ssh/id_ed25519.pub | pbcopy
echo "✓ 公钥已复制到剪贴板,请手动添加到服务器的 ~/.ssh/authorized_keys"
总结
这套方案的核心理念是用 macOS 已有的工具做好一件事,不引入不必要的依赖:
~/.ssh/config是连接配置的唯一来源,ssh/scp/rsync共用,不用到处填 IP- iTerm2 Profile 把"搜索 + 一键连接"的体验做到和 Xshell 一样顺手
- Shell 函数把上传/下载变成几个字母,比打开 GUI 文件管理器还快
- Keep-alive + 连接复用确保体验稳定,不会频繁断线
配置完成后,日常最高频的两个操作------连接服务器 和传文件------都是一条命令或一次按键的事。