Featured image of post [Bash Shell] 43. ssh - 원격 셸 접속

[Bash Shell] 43. ssh - 원격 셸 접속

SSH로 원격 호스트에 셸 접속하거나 명령을 실행하는 법을 다룬다. 키 기반 인증과 known_hosts 호스트 키 검증, ~/.ssh/config 접속 관리, -L/-R 포트 포워딩을 예제로 정리하고 Bash Shell 컬렉션 44개 챕터를 마무리한다.

ssh(Secure Shell)는 암호화된 채널로 원격 호스트에 로그인하거나, 로그인 없이 원격에서 명령 한 줄만 실행할 때 쓰는 프로토콜이자 명령어다. 이 장은 44개 챕터로 이루어진 Bash Shell 컬렉션의 마지막 장이기도 하다.

이 장을 읽기 전에

선행 챕터: 이 장은 42장: scp에서 SSH 위에서 동작하는 파일 복사를 다룬 뒤 이어진다. scp가 SSH 위에서 동작하는 파일 복사였다면, 이 장은 그 SSH 프로토콜 자체 — 원격 호스트에 로그인해 셸을 얻고 명령을 실행하는 방법 — 을 다룬다. cd·ls·grep·ps 같은 지금까지 배운 명령어를 로컬 터미널에서 익숙하게 쓸 수 있다는 전제 위에서 진행한다.

이 장의 깊이: 입문–중급 난이도다. 로그인·명령 실행 기본 문법부터 키 기반 인증, 호스트 키 검증, ~/.ssh/config를 이용한 접속 관리, 포트 포워딩의 개념까지 다룬다. 다루지 않는 것: SSH 프로토콜 내부의 키 교환 알고리즘·암호 스위트 협상 같은 저수준 세부 사항, sshd 서버 측 설정과 하드닝(예: PermitRootLogin, fail2ban 연동), SSH 터널을 이용한 VPN 구성의 심화 활용은 범위 밖이다. -L/-R 포트 포워딩은 개념만 간단히 소개하고 실전 활용은 각자의 네트워크 환경에 맞춰 별도로 익혀야 한다.

당신의 수준에 맞는 경로

수준읽을 부분핵심 목표
원격 서버에 처음 접속해보는 사람개요+정신 모델, 사용법·옵션, 예시의 “기본 로그인·명령 실행”ssh user@host로 접속하고 로그아웃하는 법, 명령 한 줄만 원격 실행하는 법을 익힌다
매번 비밀번호를 치는 게 지겨운 사람예시의 “키 기반 인증”, 주의사항·함정의 “키 기반 인증 vs 비밀번호 인증”ssh-keygen으로 키를 만들고 비밀번호 없이 접속하도록 전환한다
여러 서버를 자주 오가는 사람사용법·옵션, 예시의 “설정 파일과 별칭”, 주의사항·함정 전체~/.ssh/config로 서버별 별칭을 관리하고 known_hosts 경고의 의미를 정확히 판단한다

개요 + 정신 모델

ssh가 세상을 보는 방식은 단순하다 — 로컬 터미널의 표준입출력을 그대로 원격 셸의 표준입출력에 암호화된 채널로 이어 붙이는 것이다. ssh user@host를 실행하면 로컬 키보드 입력이 암호화되어 원격 호스트로 전달되고, 원격 셸이 실행한 명령의 출력이 다시 암호화되어 로컬 터미널로 돌아온다. 사용자 입장에서는 마치 그 원격 머신 앞에 앉아 있는 것처럼 cd, ls, grep, ps 같은 이 컬렉션에서 배운 모든 명령을 그대로 쓸 수 있다 — 셸은 로컬이든 원격이든 똑같이 동작하기 때문이다.

이 정신 모델이 이 컬렉션 전체를 관통하는 마지막 통찰이다. 1부에서 배운 파일 탐색, 2부의 텍스트 처리, 4부의 프로세스 제어, 5부의 스크립팅은 전부 “지금 로그인한 셸에서” 통하는 능력인데, ssh는 그 “지금 로그인한 셸"의 위치를 로컬 머신에서 지구 반대편 서버로 바꿔치기하는 도구일 뿐이다. ssh user@host "ps aux | grep nginx"처럼 명령 인자를 함께 주면 로그인 셸을 열지 않고 그 명령만 원격에서 실행한 뒤 결과만 돌려받고 즉시 연결을 끊는다 — 이 경우 원격 셸이 명령을 해석하고 실행하는 주체이며, ssh는 그 결과를 안전하게 실어 나르는 파이프 역할만 한다.

사용법 · 옵션

기본 문법은 ssh [옵션] [사용자@]호스트 [명령]이다. 명령을 생략하면 원격 호스트에 로그인 셸을 열고, 명령을 주면 그 명령만 실행한 뒤 종료한다.

인증과 접속 대상

옵션설명
-i FILE인증에 사용할 개인키 파일 지정 (기본: ~/.ssh/id_rsa, ~/.ssh/id_ed25519 등)
-l USER접속 사용자 지정 (user@host 대신 사용 가능)
-p PORT접속 포트 지정 (기본 22)
-F FILE기본 ~/.ssh/config 대신 다른 설정 파일 사용

실행 방식 제어

옵션설명
-t원격에서 의사 터미널(pty) 강제 할당(파이프로 명령을 넘길 때 대화형 프로그램을 띄우려면 필요)
-v상세 디버그 로그 출력(-vvv까지 단계적으로 늘려 접속 실패 원인 추적)
-q오류 외 메시지 억제
-N원격 명령을 실행하지 않고 포워딩 채널만 연다(포트 포워딩과 함께 사용)
-o KEY=VALUEssh_config 옵션을 명령줄에서 즉시 오버라이드

포트 포워딩

옵션설명
-L 로컬포트:대상호스트:대상포트로컬 포트로 들어온 연결을 SSH 터널을 통해 원격에서 대상 호스트:포트로 전달(로컬 포워딩)
-R 원격포트:대상호스트:대상포트원격 포트로 들어온 연결을 SSH 터널을 통해 로컬에서 대상 호스트:포트로 전달(리모트 포워딩)
-D 로컬포트로컬 포트를 SOCKS 프록시로 열어 모든 트래픽을 SSH 터널로 우회(다이내믹 포워딩)

예시

기본 로그인·명령 실행

1
2
3
4
5
6
7
8
# 원격 호스트에 로그인 셸 열기
ssh user@example.com

# 로그인 없이 명령 한 줄만 실행하고 즉시 종료
ssh user@example.com "uptime"

# 원격에서 파이프로 여러 명령 조합 실행
ssh user@example.com "ps aux | grep nginx | wc -l"

키 기반 인증

1
2
3
4
5
6
7
8
# ed25519 키 쌍 생성(권장 알고리즘, 파일은 ~/.ssh/id_ed25519[.pub])
ssh-keygen -t ed25519 -C "user@laptop"

# 생성한 공개키를 원격 host의 authorized_keys에 등록
ssh-copy-id -i ~/.ssh/id_ed25519.pub user@example.com

# 등록 후에는 비밀번호 없이 키만으로 접속
ssh user@example.com

설정 파일과 별칭

1
2
3
4
5
# ~/.ssh/config에 별칭을 등록해두면 (Host myserver ...) 아래처럼 짧게 접속
ssh myserver

# 특정 설정 파일과 상세 로그로 접속 실패 원인 진단
ssh -F ~/.ssh/config.work -v myserver

원격 명령 결과를 로컬로 리다이렉션

1
2
3
4
5
# 원격 디렉터리를 tar로 묶어 표준출력으로 스트리밍한 뒤 로컬 파일로 저장
ssh user@example.com "tar czf - /var/log" > backup.tar.gz

# 로컬 파일을 표준입력으로 흘려보내 원격 파일로 그대로 저장
cat local.sql | ssh user@example.com "cat > /tmp/remote.sql"

포트 포워딩

1
2
3
4
5
# 로컬 8080 포트 접속을 원격 서버를 거쳐 원격 내부망의 DB 3306 포트로 전달
ssh -L 8080:db.internal:3306 user@example.com

# 원격 서버의 9000 포트로 들어온 연결을 로컬 3000 포트(로컬 개발 서버)로 전달
ssh -R 9000:localhost:3000 user@example.com

주의사항 · 함정

키 기반 인증 vs 비밀번호 인증. 비밀번호 인증은 사람이 기억할 수 있는 짧은 문자열에 의존하므로 온라인 무차별 대입 공격에 상대적으로 취약하고, 접속할 때마다 비밀번호를 서버로 전송해야 한다. 키 기반 인증은 개인키를 로컬에만 두고 서버에는 공개키만 등록하기 때문에 서버가 뚫려도 개인키 자체는 유출되지 않으며, 개인키에 암호구문(passphrase)을 걸고 ssh-agent로 세션 동안만 메모리에 올려두면 매번 암호구문을 치지 않고도 안전하게 여러 서버를 오갈 수 있다. 실무에서 서버 관리자가 sshd_configPasswordAuthentication을 아예 no로 꺼두는 경우가 많은 이유이기도 하다.

known_hosts와 호스트 키 검증. SSH는 접속할 때마다 서버가 제시하는 호스트 키를 로컬 ~/.ssh/known_hosts에 기록된 값과 대조한다. 처음 접속하는 서버라면 ssh가 “The authenticity of host … can’t be established"라는 경고와 함께 지문(fingerprint)을 보여주고 계속할지 묻는데, 이때 무작정 yes를 누르지 않고 서버 관리자가 사전에 공유한 지문과 실제로 일치하는지 확인하는 것이 원칙이다 — 그렇지 않으면 첫 접속 자체가 중간자 공격(man-in-the-middle)에 노출된 채로 신뢰를 형성하는 셈이 된다(이 방식을 TOFU, trust-on-first-use라 부른다). 반대로 이미 known_hosts에 등록된 서버인데 호스트 키가 갑자기 바뀌었다는 경고(“REMOTE HOST IDENTIFICATION HAS CHANGED!")가 뜨면, 서버를 재설치했거나 IP를 재할당받은 정당한 상황일 수도 있지만 실제 중간자 공격의 신호일 수도 있으므로 서버 관리자에게 먼저 확인 없이 known_hosts에서 해당 줄을 지우고 넘어가서는 안 된다.

~/.ssh/config로 접속 정보 관리. 여러 서버를 오가다 보면 ssh -i ~/.ssh/work_key -p 2222 deploy@203.0.113.10 같은 긴 명령을 매번 치게 되는데, ~/.ssh/configHost 블록으로 별칭을 등록하면 이를 대폭 줄일 수 있다.

1
2
3
4
5
Host myserver
    HostName 203.0.113.10
    User deploy
    Port 2222
    IdentityFile ~/.ssh/work_key

이렇게 등록하면 이후에는 ssh myserver 한 줄로 접속하고, scp·rsync처럼 SSH를 내부적으로 쓰는 다른 명령에서도 같은 별칭을 그대로 쓸 수 있다. 다만 여러 Host 블록에 같은 패턴(Host *)이 겹치면 먼저 나온 설정이 우선 적용되므로, 구체적인 호스트 블록을 파일 앞쪽에, 공통 기본값(Host *)은 파일 맨 뒤에 두는 순서를 지켜야 한다.

-L/-R 포트 포워딩. -L(로컬 포워딩)은 로컬 머신에서 열리는 포트를 SSH 터널을 통해 원격 쪽 목적지로 연결하고, -R(리모트 포워딩)은 반대로 원격 머신에서 열리는 포트를 터널을 통해 로컬 쪽 목적지로 연결한다. 둘 다 “어느 쪽에서 포트가 열리는가"로 방향을 기억하면 헷갈리지 않는다. 다만 -R은 기본적으로 원격 서버의 sshd_config에서 GatewayPorts 설정이 꺼져 있으면 원격 호스트 자신에게서만(루프백으로만) 접근 가능하도록 제한되므로, 외부에서 접근하게 하려면 서버 측 설정도 함께 확인해야 한다.

흔한 오개념

“SSH 접속은 항상 비밀번호를 쳐야 한다"는 생각은 가장 흔한 오해다. 비밀번호 인증은 SSH가 지원하는 여러 인증 방식 중 하나일 뿐이며, 실무에서는 키 기반 인증이 기본값에 가깝다. 처음 SSH를 접한 사람일수록 매번 비밀번호를 치는 방식만 경험하고 “원래 이런 것"이라 여기기 쉬운데, ssh-keygenssh-copy-id 두 명령이면 이 번거로움을 완전히 없앨 수 있다.

“known_hosts 경고가 뜨면 그냥 지우고 다시 접속하면 된다"는 생각도 위험한 오개념이다. 특히 이미 등록된 서버의 호스트 키가 바뀌었다는 경고는 첫 접속 시의 “신뢰 형성” 경고와 성격이 다르다 — 서버를 재설치한 정당한 사유가 있는지 먼저 확인하지 않고 ssh-keygen -R hostname으로 기존 항목을 지운 뒤 재접속하는 습관을 들이면, 실제 중간자 공격이 벌어졌을 때도 같은 방식으로 경고를 무시하고 지나칠 위험이 있다.

컬렉션을 마치며

이 43장으로 Bash Shell 컬렉션의 00장부터 43장까지, 총 44개 챕터가 모두 끝났다. 00장: 과정 개요와 커리큘럼에서 세운 계획대로, 셸 기초와 탐색(1부)에서 시작해 텍스트 처리(2부)와 파이프라인(3부)으로 도구를 조합하는 법을 익히고, 프로세스 제어(4부)와 스크립팅(5부)으로 반복 작업을 자동화하는 법을 배운 뒤, 파일 시스템 관리(6부)를 거쳐 이 장의 SSH로 그 모든 역량을 원격 시스템까지 확장했다. 로컬 터미널에서 익힌 grep, ps, find, if/for 스크립트는 SSH로 접속한 어떤 리눅스 서버에서도 그대로 통한다 — 이것이 이 컬렉션 전체가 지향한 결론이다. 처음부터 순서대로 읽지 않았거나 특정 장만 골라 읽었다면, 00장의 전체 목차 표로 돌아가 아직 비어 있는 지식(예: 배열, trap, du/df)이 있는지 다시 확인해보는 것을 권한다.

여기서 더 나아가고 싶다면 몇 가지 방향이 있다. 원격 세션이 끊겨도 작업을 이어갈 수 있게 해주는 tmuxscreen으로 세션 관리를 익히면, SSH 연결이 불안정한 환경에서도 오래 걸리는 작업을 안전하게 돌릴 수 있다. .bashrc·~/.ssh/config처럼 이 컬렉션에서 다룬 개인 설정 파일들을 Git 저장소로 버전 관리하는 dotfiles 관리 습관을 들이면, 새 서버에 접속할 때마다 환경을 처음부터 다시 맞추는 수고를 덜 수 있다. 그리고 셸 스크립트를 실제 자동화에 쓰기 시작했다면 ShellCheck 같은 정적 분석 도구를 CI나 에디터에 상시 연동해, 5부에서 배운 인용·이스케이프 함정을 사람이 매번 눈으로 잡아내는 대신 도구가 먼저 걸러내게 하는 것을 권한다.

평가 기준

  • 키 기반 인증과 비밀번호 인증의 차이, 그리고 실무에서 키 기반 인증을 권장하는 이유를 설명할 수 있다.
  • known_hosts의 역할과 호스트 키 검증이 중간자 공격을 방지하는 원리를 설명하고, 첫 접속 경고와 호스트 키 변경 경고의 의미 차이를 구분할 수 있다.
  • ~/.ssh/config로 자주 접속하는 서버의 별칭·옵션을 등록해 관리할 수 있다.
  • -L-R 포트 포워딩의 방향 차이를 설명할 수 있다.
  • 이 컬렉션 전체(00–43장)가 로컬에서 원격으로 역량을 확장하는 하나의 학습 경로였다는 것을 설명하고, 자신에게 빈 지식이 남아 있는지 00장 목차로 점검할 수 있다.

참고