ssh -i ~/.ssh/id_ed25519 -p 2222 deploy@203.0.113.10
이 한 줄을 하루에 열 번씩 치고 있다면, 그리고 서버가 세 대를 넘어갔다면 이제 외우는 걸 포기할 때가 됐습니다. 키 파일 이름은 서버마다 다르고, 포트도 22였다가 2222였다가 제각각입니다. 히스토리에서 Ctrl+R로 뒤지는 것도 서버가 늘어나면 한계가 옵니다.
~/.ssh/config에 블록 하나만 써두면 그다음부터는 ssh web1로 끝납니다.
매번 치던 긴 명령을 config 한 블록으로 옮기기
SSH 클라이언트 설정 파일은 두 군데 있습니다. 사용자용은 ~/.ssh/config, 시스템 전체용은 /etc/ssh/ssh_config입니다. 내 개인 서버 목록은 사용자용에 쓰는 게 맞습니다. 시스템 파일은 sudo가 필요하고, 그 머신을 쓰는 모든 계정에 영향을 줍니다.
파일이 아직 없을 겁니다. 만들고 권한부터 잡습니다.
touch ~/.ssh/config
chmod 600 ~/.ssh/config
chmod 600 ~/.ssh/config는 선택이 아닙니다. 권한이 느슨하면 ssh가 아예 파일을 무시하거나 에러를 뱉고 멈춥니다. 이 부분은 아래 트러블슈팅에서 다시 다룹니다.
최소 블록 5줄
에디터로 열어서 이렇게 씁니다.
Host web1
HostName 203.0.113.10
User deploy
Port 2222
IdentityFile ~/.ssh/id_ed25519
Host— 내가 부를 별칭입니다. 아무 이름이나 됩니다.HostName— 실제 IP나 도메인.User— 접속 계정.Port— 기본 22면 생략해도 됩니다.IdentityFile— 쓸 개인키 경로.
저장하고 바로 ssh web1을 치면 접속됩니다. sshd 재시작 같은 건 필요 없습니다. 이건 서버 데몬 설정이 아니라 내 컴퓨터의 ssh 클라이언트 설정이라, 저장하는 순간 다음 명령부터 반영됩니다.
서버가 여러 대라면 블록을 이어서 씁니다.
Host web1
HostName 203.0.113.10
User deploy
Port 2222
IdentityFile ~/.ssh/id_ed25519
Host db1
HostName 10.0.5.21
User ubuntu
IdentityFile ~/.ssh/aws-db.pem
IdentitiesOnly yes
Host wp-old
HostName old.example.com
User root
Port 22022
IdentityFile ~/.ssh/id_rsa_legacy
Host *
ServerAliveInterval 60
ServerAliveCountMax 3

키 자체가 아직 없거나 서버에 공개키를 안 올렸다면 config를 아무리 잘 써도 비밀번호를 물어봅니다. 그 단계는 SSH 키 인증 설정하는 법, 접속 거부까지 잡는 실전 순서에서 먼저 정리하고 오시면 됩니다.
설정이 실제로 어떻게 읽히는지 눈으로 확인하기
“분명 썼는데 적용이 안 되는 것 같다” 싶을 때 추측하지 말고 확인하면 됩니다.
ssh -G web1
-G는 접속을 시도하지 않고 최종적으로 적용된 옵션 전부를 출력합니다. 출력이 수십 줄 나오는데 다 볼 필요는 없습니다.
ssh -G web1 | grep -E '^(hostname|port|user|identityfile)'
hostname 203.0.113.10
port 2222
user deploy
identityfile ~/.ssh/id_ed25519
여기 값이 내가 쓴 대로 나오면 config는 정상입니다. 그런데도 접속이 안 되면 config 문제가 아니라 서버 쪽 문제입니다.
어느 파일을 읽었는지 보기
ssh -v web1
첫 몇 줄에 이런 게 나옵니다.
OpenSSH_9.6p1, OpenSSL 3.0.13
debug1: Reading configuration data /home/user/.ssh/config
debug1: /home/user/.ssh/config line 1: Applying options for web1
debug1: Reading configuration data /etc/ssh/ssh_config
Reading configuration data /home/user/.ssh/config 줄이 안 보이면 파일 경로나 권한이 잘못된 겁니다. Applying options for web1 줄이 없으면 Host 이름이 매칭되지 않은 겁니다. 오타를 의심하세요.
먼저 나온 값이 이깁니다
SSH config는 위에서 아래로 읽으면서 처음 만난 값을 확정합니다. 나중에 같은 옵션이 또 나와도 덮어쓰지 않습니다. 대부분의 설정 파일과 반대라 여기서 많이들 헷갈립니다.
그래서 Host 같은 와일드카드 블록은 반드시 파일 맨 아래에 둬야 합니다. 맨 위에 Host 를 두고 User root를 넣어놨다면, 아래쪽 Host web1의 User deploy는 무시됩니다. 이미 user가 root로 확정된 뒤라서요.
명령줄 옵션은 config보다 우선합니다. 임시로 다른 계정으로 붙어보고 싶으면 config를 고치지 말고 이렇게 하면 됩니다.
ssh -l ubuntu web1
ssh -o IdentitiesOnly=no web1
자주 쓰는 옵션들과 실제로 쓸모 있는 조합
| 옵션 | 하는 일 | 언제 쓰나 |
|---|---|---|
IdentityFile | 사용할 개인키 지정 | 서버마다 키가 다를 때 |
IdentitiesOnly yes | 지정한 키만 시도 | 키가 여러 개일 때 필수에 가까움 |
ServerAliveInterval 60 | 60초마다 살아있음 신호 전송 | 가만히 두면 끊길 때 |
ServerAliveCountMax 3 | 응답 없어도 3번까지 참음 | 위 옵션과 세트 |
ProxyJump bastion | 점프 서버를 거쳐 접속 | 내부망 서버 진입 |
Include conf.d/* | 다른 설정 파일 불러오기 | 회사용/개인용 분리 |
ForwardAgent yes | 로컬 키를 원격에서 사용 | 서버에서 git pull 할 때 |
StrictHostKeyChecking | 호스트키 확인 정책 | 함부로 no로 두지 말 것 |
여러 키를 쓸 때 IdentitiesOnly가 필요한 이유
SSH는 기본적으로 ssh-agent에 등록된 키와 ~/.ssh 안의 기본 이름 키들을 차례대로 다 던져봅니다. 서버는 보통 인증 시도를 6번까지만 허용합니다(MaxAuthTries 기본값). 키가 7개 있으면 정작 맞는 키를 던지기 전에 잘립니다.
Received disconnect from 203.0.113.10 port 22:2: Too many authentication failures
이 메시지를 봤다면 십중팔구 이 상황입니다. IdentitiesOnly yes를 넣으면 “config에 적힌 IdentityFile만 써라”가 되어서 한 번에 통과합니다.
Host db1
HostName 10.0.5.21
User ubuntu
IdentityFile ~/.ssh/aws-db.pem
IdentitiesOnly yes
점프 서버 거쳐 들어가기: ProxyJump
내부망 서버에 바로 못 붙고 bastion(踏み台) 서버를 한 번 거쳐야 하는 구조가 흔합니다. 예전엔 ProxyCommand에 nc를 쓰는 복잡한 한 줄이 필요했는데, 지금은 ProxyJump 한 줄이면 됩니다.
Host bastion
HostName 203.0.113.5
User jump
IdentityFile ~/.ssh/id_ed25519
Host app-internal
HostName 10.0.10.42
User deploy
IdentityFile ~/.ssh/id_ed25519
ProxyJump bastion
이제 ssh app-internal 한 번으로 bastion을 자동으로 거쳐 들어갑니다. bastion에 먼저 붙어서 거기서 다시 ssh를 칠 필요가 없습니다. scp와 rsync도 그대로 이 경로를 탑니다.
Host * 에 넣을 것과 넣지 말 것
공통 블록에는 “어느 서버든 해로울 게 없는 것”만 넣습니다.
Host *
ServerAliveInterval 60
ServerAliveCountMax 3
AddKeysToAgent yes
반대로 User, IdentityFile, Port는 Host *에 넣지 마세요. 서버마다 달라야 하는 값인데 위쪽에서 확정되면 개별 블록이 무력화됩니다. StrictHostKeyChecking no도 넣지 마세요. 중간자 공격 경고를 통째로 꺼버리는 설정입니다.
Include 로 파일 쪼개기
회사 서버 30대, 개인 서버 5대를 한 파일에 넣으면 관리가 안 됩니다. Include로 쪼갭니다.
mkdir -p ~/.ssh/conf.d
chmod 700 ~/.ssh/conf.d
~/.ssh/config 맨 위에:
Include conf.d/*.conf
Host *
ServerAliveInterval 60
그다음 ~/.ssh/conf.d/work.conf, ~/.ssh/conf.d/personal.conf로 나눠 씁니다. Include는 파일 맨 위에 둬야 합니다. 아래 두면 위쪽 값이 이미 확정된 뒤라 포함된 내용이 안 먹힐 수 있습니다.
ssh 말고 다른 도구들도 이 별칭을 그대로 씁니다
여기가 config의 진짜 이득입니다. SSH를 내부적으로 쓰는 도구들은 전부 이 별칭을 인식합니다.
# 파일 업로드 — 포트, 키 옵션 필요 없음
scp file.tar.gz web1:/tmp/
# 디렉터리 동기화
rsync -avz ./dist/ web1:/var/www/html/
# sftp
sftp web1
# 원격 명령 한 줄 실행
ssh web1 'uptime'
ssh web1 'sudo systemctl status nginx'
원래 rsync로 비표준 포트에 붙으려면 rsync -avz -e "ssh -p 2222 -i ~/.ssh/id_ed25519" ... 이렇게 써야 했습니다. config에 있으면 rsync가 호출하는 ssh가 알아서 읽어갑니다. -e 옵션이 통째로 사라집니다.

Git 계정 여러 개 쓸 때
GitHub 개인 계정과 회사 계정을 한 PC에서 쓰면 키가 충돌합니다. 별칭으로 분리하면 깔끔합니다.
Host github-personal
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519_personal
IdentitiesOnly yes
Host github-work
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519_work
IdentitiesOnly yes
HostName은 둘 다 github.com입니다. 별칭만 다릅니다. 이제 remote 주소에서 github.com 자리를 별칭으로 바꿔줍니다.
git remote set-url origin git@github-work:myorg/myrepo.git
git remote -v
연결 확인은 이렇게 합니다.
ssh -T git@github-work
# Hi username! You've successfully authenticated...
탭 자동완성
ssh we 까지 치고 Tab을 누르면 web1이 완성돼야 정상입니다. 안 되면 bash-completion이 없는 경우가 많습니다.
# Debian/Ubuntu
sudo apt install bash-completion
# RHEL 계열
sudo dnf install bash-completion
zsh는 기본 컴플리션이 ~/.ssh/config의 Host를 읽습니다. 다만 Host * 같은 와일드카드도 후보로 뜨는 경우가 있는데 정상 동작입니다.
여기서 막히면 보통 이 세 가지입니다
1. Bad owner or permissions
Bad owner or permissions on /home/user/.ssh/config
가장 흔합니다. 파일 권한이 너무 열려 있거나 소유자가 내가 아닌 경우입니다.
chmod 600 ~/.ssh/config
chown $USER:$USER ~/.ssh/config
chmod 700 ~/.ssh
WSL을 쓰면서 config를 /mnt/c/Users/... 아래 윈도우 파일시스템에 두면 이 에러가 계속 납니다. 윈도우 NTFS는 리눅스 퍼미션을 제대로 표현 못 해서 chmod가 먹지 않거나 재부팅하면 풀립니다. 해결책은 WSL 내부 홈(\\wsl$\... 쪽, 즉 ~/.ssh/)에 두는 겁니다.
sudo로 파일을 만들었다가 소유자가 root가 된 경우도 같은 에러를 냅니다. ls -l ~/.ssh/config로 소유자를 먼저 확인하세요.
2. Could not resolve hostname
ssh: Could not resolve hostname web1: Name or service not known
config를 아예 못 읽었거나, 그 이름의 Host 블록이 없다는 뜻입니다. ssh가 web1을 실제 도메인으로 알고 DNS 조회를 시도한 겁니다.
확인 순서는 이렇습니다.
ls -l ~/.ssh/config
ssh -G web1 | head -3
윈도우 메모장으로 만들면 파일명이 config.txt가 되는 일이 잦습니다. 탐색기에서 확장자 표시를 켜고 확인하세요. 또 Host 줄에 오타가 있거나(Hostname web1처럼 Host 대신 씀), 별칭 뒤에 눈에 안 보이는 공백이 붙은 경우도 있습니다.
3. Bad configuration option
/home/user/.ssh/config line 12: Bad configuration option: identityfile2
/home/user/.ssh/config: terminating, 1 bad configuration options
친절하게 줄 번호를 알려줍니다. 12번째 줄만 고치면 됩니다. 옵션 이름 자체는 대소문자를 구분하지 않지만 철자는 정확해야 합니다. Identityfile은 되고 IdentitiFile은 안 됩니다.
들여쓰기 규칙
Host 아래 옵션들의 들여쓰기는 스페이스든 탭이든 몇 칸이든 상관없습니다. 아예 안 해도 동작합니다. 딱 하나, Host 줄 자체는 들여쓰지 마세요. 들여써도 대부분 파싱되긴 하는데, 읽는 사람이 블록 경계를 착각합니다.
그래도 적용이 안 되면
ssh -G 별칭 | grep -E '^(hostname|port|user|identityfile)'
먼저 이걸로 실제 값을 봅니다. 값이 엉뚱하면 **위쪽에 있는 Host * 블록이나 다른 와일드카드 블록이 먼저 값을 잡아먹은 것**입니다. 파일을 열어서 Host *가 맨 아래에 있는지 확인하세요.
grep -n '^Host' ~/.ssh/config
Host 줄만 줄 번호와 함께 뽑아줍니다. Host *가 1번 줄 근처에 있으면 그게 원인입니다.
다음에 할 일 체크리스트
- [ ]
touch ~/.ssh/config && chmod 600 ~/.ssh/config로 파일과 권한 준비 - [ ] 가장 자주 붙는 서버 한 대를
Host / HostName / User / Port / IdentityFile5줄로 등록 - [ ]
ssh 별칭으로 접속되는지 확인, 안 되면ssh -v 별칭의Reading configuration data줄부터 확인 - [ ] 키를 두 개 이상 쓴다면 각 블록에
IdentitiesOnly yes추가 - [ ]
Host *공통 블록을 만들고ServerAliveInterval 60넣기, 반드시 파일 맨 아래에 - [ ]
grep -n '^Host' ~/.ssh/config로 와일드카드 블록이 맨 아래인지 재확인 - [ ] 나머지 서버들을 옮기고
ssh -G로 각각 값 검증 - [ ] scp / rsync 명령에서
-P,-i,-e "ssh -p ..."옵션 걷어내기 - [ ] Git 계정이 둘 이상이면
github-work같은 별칭 만들고git remote set-url로 교체 - [ ] 서버가 열 대를 넘으면
Include conf.d/*.conf로 파일 분리
한 번 정리해두면 새 서버가 늘어날 때 블록 5줄 추가하는 게 전부가 됩니다. 팀에 넘길 때도 config 파일 하나만 공유하면 됩니다(키 파일 경로는 각자 맞춰야 하지만요).

[…] 이 파일에 별칭까지 정리해두면 접속 자체가 편해지는데, 그 부분은 SSH config 파일로 서버 별칭 등록하기, ssh 서버이름 한 줄로 접속하는 설정에 따로 정리해 […]
[…] 돌려놓고 몇 분씩 기다리게 됩니다. 참고로 서버에 자주 들어가야 한다면 SSH config 파일로 서버 별칭 등록하기, ssh 서버이름 한 줄로 접속하는 설정을 해두는 편이 급할 때 […]