SSH 가이드

OpenSSH 서버(sshd)와 클라이언트, 키, 파일 전송, 포트포워딩(-L · -R · -D), 점프 호스트

한 줄
SSH는 암호화된 원격 로그인이다. 서버의 sshd가 포트를 열고 기다리고, 클라이언트의 ssh가 그쪽으로 붙는다. 터널을 열어도 이 방향은 같다.

1. 서버와 클라이언트

1-1. 누가 listen 하고, 누가 connect 하는가

서버는 sshd 데몬이다. 이름 끝의 d = daemon, 요청이 올 때까지 떠 있는 프로세스다. 기본으로 22/tcp에서 listen 하고, 설정 파일은 /etc/ssh/sshd_config다. 패키지는 OpenSSH server.

클라이언트는 ssh 명령이다. listen 하지 않는다. user@host로 connect 한다.

SSH 연결 · 내 PC → 서버:22 데이터·요청 흐름 내 PC (클라이언트) ssh 명령 connect 하는 쪽 ssh user@host 설정은 ~/.ssh listen 하지 않음 서버 (sshd) 22/tcp 에서 listen d = daemon, 상주 프로세스 /etc/ssh/sshd_config 패키지 openssh-server 접속을 기다림 SSH 연결 :22 세션 데이터

연결을 여는 화살표는 항상 내 PC에서 서버 22번으로 간다. 셸의 입출력은 그 위로 오간다.

서버 설치와 확인 (Ubuntu 계열)

sudo apt install openssh-server
systemctl status ssh
sudo systemctl enable --now ssh
ss -tlnp | grep :22

status에 active (running) 또는 소켓이 포트를 받고 있으면 서버가 떠 있는 것이다. ss의 :22 줄이 listen이다. 프로세스 이름이 안 보이면 sudo ss -tlnp | grep :22.

배포판·macOS에서 이름이 다를 때
  • Ubuntu/Debian 유닛 이름은 ssh다. 프로세스 파일은 sshd다. RHEL·Fedora·Arch는 유닛 이름이 sshd인 경우가 많다. systemctl status ssh가 없으면 systemctl status sshd.
  • Ubuntu 22.10 이후, 24.04 포함 많은 설치는 ssh.socket이 22번을 받고 접속이 올 때 ssh.service를 띄운다. 접속 전에 systemctl status ssh가 inactive여도 systemctl status ssh.socket이 listening이면 정상일 수 있다. 출처는 Ubuntu Discourse의 소켓 활성화 안내.
  • 그 경우 부팅 시 켜 두는 명령은 sudo systemctl enable --now ssh.socket이다. 전통적인 enable --now ssh도 배포판에 따라 통한다. 상태 확인으로 판별한다.
  • macOS 서버는 시스템 설정 → 일반 → 공유 → 원격 로그인. 이것도 sshd가 22/tcp를 연다.

1-2. 호스트 키와 사용자 키

열쇠가 두 종류다. 섞이면 known_hosts와 authorized_keys를 반대로 고치게 된다.

처음 접속하면 지문을 믿고 저장할지 묻는다. yes는 호스트 키를 known_hosts에 넣는 뜻이다. y만으로는 안 되고 yes를 친다. 사용자 비밀번호나 내 공개키와는 다른 질문이다.

SSH 연결 · 내 PC → 서버:22 데이터·요청 흐름 내 PC (클라이언트) 개인키 id_ed25519 내 신원. 파일은 안 나감 챌린지에 서명만 보냄 ssh-agent 가 대신 서명 가능 공개키 .pub 는 복사해도 됨 서버 (sshd) authorized_keys 공개키만 보관 서명 검증 맞으면 로그인 허용 개인키는 서버에 없음 SSH 연결 :22 ① 챌린지 ② 서명 호스트 키 · 서버의 신원 서버: /etc/ssh/ssh_host_* 내 PC: ~/.ssh/known_hosts 첫 접속 yes/no 는 이 지문 사용자 키 · 나의 신원 개인키는 내 PC에만 공개키는 서버 authorized_keys 호스트 키와 다른 열쇠

서버가 챌린지를 보내고, 내 PC가 개인키로 서명한다. 서버는 authorized_keys의 공개키로 그 서명을 확인한다.

지문을 콘솔에서 대조하는 법

서버 모니터나 클라우드 콘솔처럼 SSH가 아닌 경로로 서버에 들어가, 호스트 공개키 지문을 본다.

ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub

클라이언트에 찍힌 SHA256: 지문과 같아야 yes다. 처음 보는 서버의 지문을 화면만 보고 습관적으로 yes 하지 않는다.

2. 기본 접속

ssh user@host
ssh -p 2222 user@host
ssh -i ~/.ssh/id_ed25519 user@host
ssh host 'uptime'
ssh -t host tmux attach
멈춘 세션
엔터로 줄을 새로 시작한 뒤 ~. 이면 클라이언트 쪽에서 세션을 끊는다. ~?는 이스케이프 목록이다. 틸드는 줄의 맨 앞에서만 특수 문자다.
이스케이프 몇 가지
  • ~. 연결 종료
  • ~? 목록
  • ~# 포워딩된 연결 목록
  • ~~ 틸드 글자 그대로 전송

메뉴얼: OpenSSH ssh(1)의 ESCAPE CHARACTERS.

3. 키 인증

내 PC에서 키 쌍을 만든다. -C는 공개키 끝에 붙는 메모(주석)다. 암호가 아니다. passphrase는 개인키 파일을 잠그는 암호라서, 비우기보다 걸고 ssh-agent에 올리는 쪽이 낫다.

ssh-keygen -t ed25519 -C "메모"

등록은 ssh-copy-id가 공개키를 서버 ~/.ssh/authorized_keys에 추가하고 권한도 맞춘다.

ssh-copy-id user@host

ssh-copy-id가 없는 환경이면 공개키를 직접 붙인다. 이 명령도 내 PC에서 실행한다.

cat ~/.ssh/id_ed25519.pub | ssh user@host \
  'mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys'
경로어디모드
~/.ssh내 PC, 서버 홈 모두700
id_ed25519 개인키내 PC600
authorized_keys서버의 그 사용자 홈600
chmod 700 ~/.ssh
chmod 600 ~/.ssh/id_ed25519

그룹이나 다른 사용자가 쓸 수 있는 개인키·authorized_keys·~/.ssh는 sshd가 무시한다. 그때 증상은 Permission denied (publickey)다.

ssh-agent

에이전트가 개인키를 메모리에 들고, ssh가 필요할 때마다 서명을 부탁한다. 접속마다 passphrase를 다시 치지 않아도 된다.

eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_ed25519

macOS는 키체인에 넣는다.

ssh-add --apple-use-keychain ~/.ssh/id_ed25519
macOS 키체인 설정, 리눅스에는 넣지 말 것

UseKeychain은 Apple OpenSSH 전용이다. 리눅스 ~/.ssh/config에 넣으면 Bad configuration option으로 ssh가 거부할 수 있다.

Host *
  UseKeychain yes
  AddKeysToAgent yes
  IdentityFile ~/.ssh/id_ed25519

예전 macOS의 ssh-add -K는 --apple-use-keychain으로 바뀌었다. 지금 쓰던 옵션은 man ssh-add로 확인한다.

비밀번호 로그인을 끄기 전에
키로 들어가는 세션을 다른 터미널에 하나 더 열어 성공한 뒤에 끈다. 지금 붙어 있는 세션은 닫지 않는다. 키가 안 되는데 비밀번호까지 막으면 그 계정으로는 다시 못 들어온다.

확인:

ssh -o PreferredAuthentications=publickey -i ~/.ssh/id_ed25519 user@host

서버 /etc/ssh/sshd_config(또는 그 드롭인)에 넣고 다시 읽힌다. 기존 세션은 reload로 끊기지 않는다.

PasswordAuthentication no
PermitRootLogin no

sudo systemctl reload ssh

유닛 이름이 sshd인 배포판은 sudo systemctl reload sshd.

설정이 안 먹는 경우 (Include, 키보드 인터랙티브)

OpenSSH는 같은 키워드의 먼저 나온 값을 쓴다. Ubuntu 클라우드 이미지는 sshd_config 맨 위의 Include /etc/ssh/sshd_config.d/*.conf가 먼저라, 50-cloud-init.conf 같은 파일의 PasswordAuthentication yes가 본문 아래의 no를 이긴다. 적용된 값만 믿는다.

sudo sshd -T | grep -Ei 'passwordauthentication|kbdinteractive|permitrootlogin'

비밀번호가 키보드 인터랙티브로 남아 있으면 KbdInteractiveAuthentication no도 같이 끈다. /run/sshd가 없어 sshd -t가 실패하는 소켓 활성 환경이면 sudo mkdir -p /run/sshd 후 다시 검사한다. 포트를 바꿨다면 소켓 활성 Ubuntu에서는 sudo systemctl daemon-reload 다음 sudo systemctl restart ssh.socket까지 해야 listen 포트가 바뀌는 경우가 있다.

4. ~/.ssh/config

호스트 별칭을 만들어 두면 ssh myserver만으로 사용자, 포트, 키, 점프, 포워딩이 붙는다. 파일은 내 PC의 ~/.ssh/config, 모드는 600.

chmod 600 ~/.ssh/config
Host myserver
  HostName 203.0.113.10
  User ubuntu
  Port 22
  IdentityFile ~/.ssh/id_ed25519
  ProxyJump bastion
  LocalForward 8080 localhost:8080
  ServerAliveInterval 60

Host bastion
  HostName bastion.example.com
  User ubuntu
  IdentityFile ~/.ssh/id_ed25519

Host *
  AddKeysToAgent yes
  ServerAliveInterval 30
  ServerAliveCountMax 3

그 다음부터는 ssh myserver. LocalForward의 localhost도 명령행 -L과 같이 서버 입장의 localhost다. Host *는 맨 아래에 둔다.

먼저 나온 값이 이긴다, IdentitiesOnly

ssh_config도 키워드마다 처음 값만 쓴다. Host *를 파일 맨 위에 두고 User를 적으면, 아래의 호스트별 User는 무시된다.

키가 여러 개면 기본 키를 먼저 시도하다 MaxAuthTries에 걸릴 수 있다. 그 호스트에는 IdentitiesOnly yes를 넣어 IdentityFile만 쓰게 한다.

클라이언트에 적용된 결과를 보려면 ssh -G myserver. 개인키 내용이 아니라 설정 해석 결과다.

5. 파일 전송

scp, sftp, rsync -e ssh 모두 내 PC에서 명령을 실행한다. SSH 연결도 내 PC → 서버:22 다. 바뀌는 것은 파일의 방향이다.

SSH 연결 · 내 PC → 서버:22 파일 방향 내 PC의 파일 a.txt 명령을 여기서 실행 서버의 경로 /tmp/a.txt 수신 측 SSH :22 파일 로컬 → 원격 내 PC로 저장 ./b.txt 명령은 여전히 내 PC 서버의 파일 /tmp/b.txt 보내는 쪽 SSH :22 파일 원격 → 로컬

위는 업로드, 아래는 다운로드. 실선(SSH)은 두 경우 모두 내 PC에서 서버로 나간다.

scp

# 로컬 → 원격
scp a.txt user@host:/tmp/
# 원격 → 로컬
scp user@host:/tmp/a.txt .
# 디렉터리. 포트는 대문자 P
scp -r -P 2222 ./site user@host:/var/www/
포트 문자의 대소문자
ssh -p 2222는 소문자다. scp -P 2222는 대문자다. scp -p는 포트가 아니라 타임스탬프·권한 유지다.

sftp

대화형이다. 프롬프트도 내 PC의 sftp 클라이언트다.

sftp user@host
sftp> get remote.txt
sftp> put local.txt
sftp> bye

rsync

rsync -avz -e ssh ./src/ user@host:/dst/
rsync -avz -e ssh user@host:/dst/ ./src/

-a는 아카이브(권한·시간 등), -v는 목록, -z는 전송 압축, -e ssh는 운반을 SSH로 한다. -P는 진행률과 이어받기(--partial --progress)지 포트가 아니다. 다른 포트는 -e "ssh -p 2222".

명령 (내 PC에서)방향의미
scp a.txt user@host:/tmp/로컬 → 원격파일 업로드
scp user@host:/tmp/a.txt .원격 → 로컬파일 다운로드
sftp 다음 put로컬 → 원격대화형 업로드
sftp 다음 get원격 → 로컬대화형 다운로드
rsync -avz -e ssh ./src/ user@host:/dst/로컬 → 원격src 내용을 dst 안으로
rsync -avz -e ssh ./src user@host:/dst/로컬 → 원격dst 아래 src 디렉터리가 생김
rsync -avz -e ssh user@host:/dst/ ./src/원격 → 로컬원격 내용을 로컬로

소스 경로 끝의 슬래시가 "그 디렉터리 자체"와 "그 안의 내용"을 가른다.

--delete
rsync --delete는 소스에 없는 파일을 목적지에서 지운다. 방향을 반대로 적으면 로컬이 지워질 수 있다. 먼저 --dry-run(-n)으로 목록만 본다.

6. 포트포워딩

문법은 세 가지다. bind 주소는 생략하면 보통 루프백이다.

ssh -L [bind:]로컬포트:목적지호스트:목적지포트 user@server
ssh -R [bind:]서버포트:목적지호스트:목적지포트 user@server
ssh -D [bind:]로컬포트 user@server

-L의 목적지 이름은 서버가 해석한다. -R의 목적지 이름은 내 PC가 해석한다. 연결 자체는 둘 다 내 PC → 서버:22.

6-1. -L 같은 포트 8080:localhost:8080

ssh -L 8080:localhost:8080 user@server
숫자 두 개의 주인
앞의 8080은 내 PC가 여는 포트다. 브라우저 주소는 http://localhost:8080이고, 이 localhost는 내 PC다. 뒤의 localhost:8080은 서버가 자기 자신에게 접속할 주소다. 서버에서만 127.0.0.1:8080으로 떠 있는 Jupyter·웹앱에, 내 브라우저가 터널로 닿는다.
SSH 연결 · 내 PC → 서버:22 데이터·요청 흐름 내 PC (클라이언트) 앞의 8080 = 여기서 listen 브라우저 http://localhost:8080 ssh 가 여는 포트 127.0.0.1:8080 -L 의 앞 숫자 서버 (sshd) 뒤의 localhost = 이 기계 sshd listen :22 서버의 앱 127.0.0.1:8080 Jupyter 등. 뒤의 포트 연결 :22 요청

브라우저는 내 PC의 8080만 본다. 서버 sshd가 자기 127.0.0.1:8080으로 이어 준다.

명령브라우저 (내 PC)서버가 접속하는 곳
-L 8080:localhost:8080 localhost:8080 서버의 localhost:8080
-L 9000:localhost:8080 localhost:9000 서버의 localhost:8080

포트를 바꾸면 내 PC에서 여는 번호만 바뀐다. 도착 앱의 포트는 뒤 숫자다. 앱이 IPv4로만 떠 있으면 목적지 호스트를 localhost 대신 127.0.0.1로 적는다.

6-2. -L 로 서버 너머 제3 호스트

ssh -L 5432:db.internal:5432 user@bastion

내 PC의 5432로 들어온 연결을, 배스천이 db.internal:5432로 넘긴다. 그 이름은 내 PC DNS가 아니라 배스천이 해석한다. 데이터베이스는 배스천에서만 보여도 된다.

SSH 연결 · 내 PC → 서버:22 데이터·요청 흐름 내 PC (클라이언트) listen :5432 psql localhost 앞의 5432 DB 주소를 몰라도 됨 ssh 는 배스천으로 배스천 (sshd) 수신 :22 이름 해석은 여기 db.internal 서버 입장 목적지 SSH 종착점 대상 (제3 호스트) db.internal 포트 5432 배스천이 TCP 접속 내 PC가 직접 아님 DB 서비스 SSH :22 서버→DB

실선은 내 PC에서 배스천 22번까지만 SSH다. DB로 가는 TCP는 배스천이 연다.

6-3. -R 리모트(역) 포워딩

ssh -R 9000:localhost:3000 user@vps

서버의 9000으로 들어온 요청이 내 PC의 localhost:3000으로 나간다. NAT 뒤의 개발 PC를, 밖에서 직접 포트포워드 하지 않고 보여주고 싶을 때 쓴다. SSH를 시작하는 쪽은 여전히 내 PC다. 요청만 반대다.

SSH 연결 · 내 PC → 서버:22 데이터·요청 흐름 내 PC (클라이언트) 앱 listen :3000 개발 서버·웹앱 NAT 뒤에 있어도 됨 SSH는 내가 연다 뒤 localhost = 여기 서버 (sshd / VPS) 수신 :22 listen :9000 기본 바인드 127.0.0.1 여기로 들어온 요청이 터널을 타고 되돌아감 SSH 연결 :22 요청은 역방향 요청 경로: 서버 :9000 → 터널 → 내 PC :3000 GatewayPorts 기본 no. 서버 루프백에서만 :9000 이 열린다.

실선은 내 PC → VPS:22. 점선 요청은 VPS의 9000에서 내 PC의 3000으로 거꾸로 간다.

서버 sshd_config의 GatewayPorts 기본값은 no다. 그래서 -R 9000:...은 서버의 127.0.0.1:9000에만 묶인다. VPS 밖에서 vps:9000으로는 안 닿고, 서버 안에서 curl localhost:9000만 된다. 밖에 열려면 서버에서 GatewayPorts를 바꾸고 reload 한 뒤, 바인드 주소를 지정한다.

# 서버 sshd_config — clientspecified 가 클라이언트가 적은 주소만 허용
GatewayPorts clientspecified

sudo systemctl reload ssh

# 내 PC
ssh -R 0.0.0.0:9000:localhost:3000 user@vps
GatewayPorts no / yes / clientspecified
  • no (기본): 리모트 포워딩은 서버 루프백만. 클라이언트가 0.0.0.0을 적어도 무시된다.
  • clientspecified: -R에 적은 bind 주소를 서버가 존중한다.
  • yes: 리모트 포워딩을 와일드카드에 묶는다. 범위가 더 넓다.

이 키워드는 서버 sshd_config다. 내 PC ~/.ssh/config의 GatewayPorts는 -L/-D용이라 다른 설정이다. 메뉴얼: sshd_config(5) GatewayPorts, ssh(1)의 -R.

6-4. -D 동적 SOCKS

ssh -D 1080 user@server

내 PC의 127.0.0.1:1080에 SOCKS5 프록시가 열린다. 브라우저는 목적지를 직접 열지 않고 이 프록시로 보내고, 서버가 그 TCP를 대신 연다. 고정된 목적지 포트가 없다.

SSH 연결 · 내 PC → 서버:22 데이터·요청 흐름 내 PC (클라이언트) SOCKS5 listen 127.0.0.1:1080 브라우저가 여기를 봄 ssh -D 1080 목적지는 고정 아님 서버 (sshd) 수신 :22 밖으로 대신 접속 DNS도 서버에서 socks5-hostname 내부망 이름 가능 임의 목적지 예: 웹 :443 서버가 TCP를 염 사이트마다 달라짐 -L 처럼 고정 포트 아님 SOCKS가 목적지를 전달 SSH :22 서버가 접속

브라우저 프록시만 127.0.0.1:1080으로 둔다. 실제 사이트 접속은 서버가 나간다.

브라우저 SOCKS5 호스트 127.0.0.1, 포트 1080. curl은 이름 해석을 프록시(서버)에 맡기는 옵션을 쓴다.

curl --socks5-hostname 127.0.0.1:1080 https://example.com

이 옵션 이름은 --socks5-hostname이다. 서버에서만 풀리는 내부 호스트 이름에 필요하다. --socks5만 쓰면 이름을 내 PC에서 먼저 찾는다.

6-5. 공통 옵션, 바인드 주소, 종료

ssh -N -f \
  -o ExitOnForwardFailure=yes \
  -o ServerAliveInterval=60 \
  -L 127.0.0.1:8080:localhost:8080 user@server

bind를 비우거나 127.0.0.1로 두면 내 PC의 그 포트는 나만 쓴다. 0.0.0.0이나 -g는 같은 LAN의 다른 기기도 내 PC의 포워딩 포트로 들어올 수 있다. 공개 와이파이에서는 열지 않는다. -L/-D의 이 동작은 내 PC 설정이고, -R을 서버 밖에 여는 것은 서버 GatewayPorts다.

이미 쓰는 포트면 bind: Address already in use다. 번호를 바꾸거나, 그 포트를 가진 프로세스를 끝낸다.

ss -tlnp | grep 8080
lsof -i :8080
kill PID

포그라운드 터널이면 그 터미널에서 Ctrl-C, 또는 엔터 다음 ~.. -f로 사라진 프로세스는 ss/lsof의 PID를 kill한다.

1024 아래 포트, localhost와 127.0.0.1

listen 포트가 1024 미만이면 그 기계에서 root가 필요하다. 개발용은 8080, 9000, 1080처럼 높은 포트를 쓴다.

localhost가 ::1로 먼저 풀리고 앱은 127.0.0.1에만 떠 있으면 연결이 거절된다. 목적지 칸에 127.0.0.1을 적는다.

7. 점프 호스트

내부 서버가 내 PC에서 직접 안 보일 때, 배스천을 TCP 중계로 쓴다. 한 번의 명령으로 배스천을 거쳐 내부에 로그인한다.

ssh -J user@bastion user@internal
ssh -J user@a,user@b user@internal

-J a,b는 a 다음 b 다음 최종 호스트다. 설정 파일에서는 ProxyJump bastion 또는 ProxyJump bastion1,bastion2. 안쪽 SSH는 내 PC와 internal 사이에서 암호화되고, 배스천은 그 TCP를 중계한다.

SSH 연결 · 내 PC → 서버:22 데이터·요청 흐름 안쪽 SSH · 암호는 내 PC ↔ 내부 내 PC (클라이언트) ssh -J 를 실행 키는 여기에만 배스천에 안 줌 두 SSH를 모두 시작 배스천 (sshd) 수신 :22 internal:22 로 TCP 중계 안쪽 내용은 못 봄 ProxyJump / -J 내부 서버 (sshd) 수신 :22 내 PC에서 직접 불가 로그인 종착점 배스천 경유 SSH :22

바깥 연결은 배스천:22. 점선은 내 PC에서 내부 서버까지 가는 안쪽 SSH다.

agent forwarding (-A)
ssh -A는 배스천이 내 ssh-agent로 서명하게 한다. 배스천 관리자는 그 세션 동안 내 자격으로 다른 서버에 들어갈 수 있다. 점프만 필요하면 -J / ProxyJump를 쓴다. -A는 그 호스트를 믿을 때만, Host 블록으로 한정한다.
ForwardAgent를 어쩔 수 없이 쓸 때

기본값은 전달 안 함이다. 필요할 때만 그 호스트에 ForwardAgent yes. 전역 Host *에 넣지 않는다. ssh-add -c는 서명을 쓸 때마다 확인을 묻게 할 수 있다(에이전트·데스크톱에 따라 동작이 다르다).

8. 치트시트

실행 위치가 서버인 줄만 서버에서 친다. 나머지는 내 PC다. SSH를 여는 방향은 내 PC → 서버다.

명령어디서 실행어디 → 어디용도
ssh user@host내 PC내 PC → 서버:22로그인 셸
ssh -p 2222 user@host내 PC내 PC → 서버:2222다른 포트. 소문자 p
ssh -i ~/.ssh/id_ed25519 user@host내 PC내 PC → 서버:22그 개인키로 인증
ssh host 'uptime'내 PC내 PC → 서버, 명령 한 줄원격 명령 후 종료
ssh -t host tmux attach내 PC내 PC → 서버 TTYtmux에 붙기
~. / ~?SSH 세션, 줄의 맨 앞클라이언트가 세션 종료멈춘 연결 끊기 / 도움말
ssh-keygen -t ed25519 -C "메모"내 PC로컬에 키 쌍 생성개인키·공개키 만들기
ssh-copy-id user@host내 PC공개키 → 서버 authorized_keys키 등록
ssh-add ~/.ssh/id_ed25519내 PC개인키 → ssh-agent에이전트에 적재
ssh myserver내 PCconfig의 HostName으로별칭 접속
scp a.txt user@host:/tmp/내 PC로컬 파일 → 원격업로드
scp -P 2222 user@host:/tmp/a.txt .내 PC원격 파일 → 로컬다운로드. 포트는 대문자 P
sftp user@host내 PCput / get 양방향대화형 전송
rsync -avz -e ssh ./src/ user@host:/dst/내 PC로컬 내용 → 원격 디렉터리동기화. 끝 슬래시 주의
ssh -L 8080:localhost:8080 user@server내 PC내 :8080 → 서버의 127.0.0.1:8080같은 포트 로컬 포워딩
ssh -L 5432:db.internal:5432 user@bastion내 PC내 :5432 → 배스천 너머 DB제3 호스트. 이름은 서버가 해석
ssh -R 9000:localhost:3000 user@vps내 PC서버 :9000 → 내 PC :3000역터널. 연결은 내 PC가 시작
ssh -D 1080 user@server내 PCSOCKS → 서버가 임의 목적지동적 프록시
ssh -J user@bastion user@internal내 PC내 PC → 배스천 → internal점프. 여러 단은 -J a,b
ssh -N -f -o ExitOnForwardFailure=yes -L ...내 PC터널만, 백그라운드셸 없이 포워딩. 실패 시 종료
ssh -v user@host내 PC디버그 로그를 내 터미널에접속 실패 추적. 더 보면 -vvv
ssh-keygen -R host내 PCknown_hosts에서 그 호스트 삭제호스트 키 변경 후. 원인 확인 전제
systemctl status ssh서버서버 로컬 systemdsshd 상태. 배포판에 따라 sshd

9. 트러블슈팅

Permission denied (publickey)

서버가 공개키 인증을 거절했거나, 제시한 키를 인정하지 않았다. 흔한 원인은 네 가지다.

클라이언트에서 ssh -v user@host로 시작하고, 부족하면 -vvv. 앞쪽 키 교환 덤프보다 끝부분이 원인이다.

# 따라갈 줄 (예시)
debug1: Connecting to host [203.0.113.10] port 22.
debug1: Offering public key: /home/me/.ssh/id_ed25519 ED25519
debug1: Server accepts key: ...
# 거절이면 대신 이런 결말
debug1: Authentications that can continue: publickey
user@host: Permission denied (publickey).

Offering public key에 기대한 파일이 없으면 -i가 안 먹은 것이다. Server accepts key 없이 끝나면 서버의 authorized_keys·권한·사용자를 본다. 서버 로그:

sudo journalctl -u ssh -n 50 --no-pager

유닛이 sshd면 -u sshd. Failed publickey와 Accepted publickey를 찾는다.

REMOTE HOST IDENTIFICATION HAS CHANGED

known_hosts의 호스트 키와 지금 서버가 내민 키가 다르다. 서버를 재설치하면 호스트 키가 바뀌어 이 경고가 난다. 같은 증상은 중간자 공격에서도 난다. 재설치·이미지 교체를 콘솔 등 다른 경로로 확인한 뒤에만 지운다.

ssh-keygen -R host
포트가 22가 아닐 때

known_hosts 키는 [host]:2222 형태다. 대괄호가 필요하다.

ssh-keygen -R '[host]:2222'

Connection refused 와 Connection timed out

메시지의미먼저 볼 곳
Connection refused 그 IP까지는 갔고, 그 포트는 거절했다. listen 하는 프로세스가 없다. 서버에서 systemctl status ssh(또는 sshd, ssh.socket). 포트가 22가 아니면 클라이언트 -p.
Connection timed out 응답이 없다. 패킷이 버려진다. IP·보안 그룹·방화벽·경로. sshd를 고쳐도 타임아웃은 그대로인 경우가 많다.

ssh -v의 Connecting to ... port N으로 붙으려는 주소와 포트를 확인한다. 여기서 기대한 포트가 아니면 config의 Port나 HostName이 다른 블록에 걸린 것이다.

-v 출력을 읽는 순서
  1. Connecting to — 호스트와 포트가 의도한 값인가.
  2. identity file / Offering public key — 어떤 개인키를 꺼내는가.
  3. Server accepts key 또는 Authentications that can continue — 서버가 받았는가.
  4. 마지막 줄의 Permission denied (...) — 괄호 안이 서버가 아직 허용하는 방법이다.

-vvv는 같은 흐름에 알고리즘 협상까지 붙는다. -v로 포트·키가 맞는지 본 다음에 올린다.

한눈에

  • sshd가 listen, ssh가 connect. 터널·점프·scp도 연결은 내 PC → 서버:22.
  • 호스트 키는 known_hosts, 사용자 공개키는 authorized_keys. 개인키는 내 PC에만.
  • -L 8080:localhost:8080의 앞은 내 포트, 뒤는 서버 자신의 포트.
  • -R은 요청만 거꾸로. 밖에 열려면 서버 GatewayPorts.
  • -D는 SOCKS. -J는 점프. -A는 에이전트를 그 서버에 맡긴다.
  • 비밀번호 로그인을 끄기 전에 키 세션을 하나 더 열어 둔다.

10. 출처

SSH 가이드 · Tech / Program · OpenSSH 서버·클라이언트·포트포워딩