Skip to content

SSH

SSH는 원격 서버에 안전하게 접속하기 위한 암호화 프로토콜이다.

SSH는 비밀번호 대신 공개키 비대칭 암호화 방식을 사용한다. 따라서 두 개의 키를 사용한다.

  • 개인키(Private Key): 절대 공유하지 않는 비밀 키
  • 공개키(Public Key): 누구에게나 공개해도 되는 키

공개키로 암호화한 데이터는 개인키로만 복호화할 수 있다. 반대로 개인키로 서명한 데이터는 공개키로 검증할 수 있다.

SSH 공개키 인증은 이렇게 작동한다.

  1. 클라이언트가 서버에 접속을 시도한다
  2. 서버는 등록된 공개키 목록(~/.ssh/authorized_keys)을 확인한다
  3. 서버가 무작위 챌린지 데이터를 생성해 클라이언트에게 보낸다
  4. 클라이언트는 개인키로 챌린지에 서명해 돌려보낸다
  5. 서버는 공개키로 서명을 검증한다

이 과정에서 개인키 자체는 절대 전송되지 않는다. 개인키를 가진 사람만이 올바른 서명을 만들 수 있으므로, 서버는 클라이언트가 진짜 사용자임을 확신할 수 있다.

아래 명령어로 SSH 키를 만들 수 있다. 현대적인 Ed25519 알고리즘을 사용하는 것이 권장된다.

Terminal window
ssh-keygen -t ed25519 -C "[email protected]"

RSA를 사용해야 한다면 최소 4096비트를 사용한다.

Terminal window
ssh-keygen -t rsa -b 4096 -C "[email protected]"

키를 생성하면 패스프레이즈를 입력하라고 나온다. 패스프레이즈는 개인키 자체를 암호화하는 비밀번호이다.

누군가 개인키 파일을 훔쳐가더라도 패스프레이즈 없이는 사용할 수 없다. 보안을 위해 강력한 패스프레이즈를 설정하는 것을 권장한다.

ssh에 관련된 파일은 ~/.ssh/ 디렉터리에 저장된다.

Terminal window
ls -la ~/.ssh/

각 파일의 역할은 이렇다.

  • id_ed25519: 개인키. 절대 공유하지 않는다. 권한은 600이어야 한다.
  • id_ed25519.pub: 공개키. 서버에 등록할 키이다. 누구에게나 공개해도 안전하다.
  • known_hosts: 접속했던 서버의 호스트 키 지문을 저장한다. 중간자 공격을 방지한다.
  • authorized_keys: (서버 측) 이 사용자로 접속할 수 있는 공개키 목록이다.
  • config: SSH 접속 설정을 저장한다.

공개키를 서버의 ~/.ssh/authorized_keys 파일에 추가하면 된다. 가장 간단한 방법은 ssh-copy-id를 사용하는 것이다.

Terminal window
ssh-copy-id -i ~/.ssh/id_ed25519.pub user@remote-host

이 명령은 공개키를 서버에 복사하고, 올바른 권한까지 설정해준다.

ssh-copy-id가 없다면 수동으로 할 수도 있다.

Terminal window
cat ~/.ssh/id_ed25519.pub | ssh user@remote-host "mkdir -p ~/.ssh && chmod 700 ~/.ssh && cat >> ~/.ssh/authorized_keys && chmod 600 ~/.ssh/authorized_keys"

이제 비밀번호 없이 서버에 접속할 수 있다.

매번 ssh -i ~/.ssh/work_key -p 2222 [email protected]처럼 긴 명령을 입력하는 건 번거롭다. ~/.ssh/config 파일을 사용하면 이런 설정을 저장해두고 간단한 별칭으로 접속할 수 있다.

예를 들어 이런 config 파일을 만들면:

Host myserver
HostName 192.168.1.100
User myuser
Port 2222
IdentityFile ~/.ssh/work_key

주로 사용하는 옵션 목록

  • HostName: 실제 호스트명 또는 IP 주소
  • User: 로그인할 사용자명
  • Port: SSH 포트 번호 (기본 22)
  • IdentityFile: 사용할 개인키 경로
  • ProxyJump: 점프 서버 경유
  • ForwardAgent: SSH 에이전트 포워딩 활성화
  • ServerAliveInterval: 주기적으로 연결 확인 메시지 전송 (초 단위)
  • ServerAliveCountMax: 응답 없을 때 재시도 횟수
  • StrictHostKeyChecking: 호스트 키 검증 정책 (yes/no/ask)

Config 파일은 개인키처럼 민감한 정보는 아니지만, 올바른 권한을 설정해두는 것이 좋다.

Terminal window
chmod 600 ~/.ssh/config

내부 서버에 직접 접속할 수 없고, 중간의 점프 서버를 거쳐야 하는 경우가 있다. ProxyJump를 사용하면 이를 간단하게 처리할 수 있다.

Host bastion
HostName bastion.company.com
User admin
Host internal-server
HostName 10.0.1.50
User developer
ProxyJump bastion

이제 ssh internal-server만 입력하면 bastion을 거쳐 자동으로 내부 서버에 접속된다.

SSH는 단순히 원격 접속만 하는 게 아니다. 네트워크 트래픽을 안전하게 터널링하는 데도 사용할 수 있다. 이를 포트 포워딩 또는 SSH 터널링이라고 부른다.

회사 내부 데이터베이스 서버가 있는데, 보안상 외부에서 직접 접속할 수 없다고 해보자. 하지만 SSH 서버에는 접속할 수 있다. 이럴 때 로컬 포트 포워딩을 사용한다.

Terminal window
ssh -L 3306:db.internal:3306 user@bastion-server

이 명령은 이런 의미다.

  • 내 로컬의 3306 포트를 열어둔다
  • 누군가 로컬 3306에 접속하면
  • SSH 서버(bastion-server)를 통해 db.internal:3306으로 전달한다

이제 로컬에서 localhost:3306에 접속하면, 실제로는 내부 데이터베이스에 연결된다. 모든 트래픽은 SSH로 암호화되어 안전하다.

Terminal window
mysql -h 127.0.0.1 -P 3306 -u dbuser -p

다른 예시로, 내부 웹 서비스에 접속하고 싶다면:

Terminal window
ssh -L 8080:internal-web:80 user@jump-server

이제 브라우저에서 http://localhost:8080을 열면 내부 웹 서비스가 보인다.

로컬 포트 포워딩과 반대 방향이다. 로컬에서 실행 중인 서비스를 원격 서버를 통해 외부에 노출하고 싶을 때 사용한다.

예를 들어 로컬에서 웹 애플리케이션을 개발 중인데(localhost:3000), 이를 팀원에게 보여주고 싶다고 해보자.

Terminal window
ssh -R 8080:localhost:3000 user@public-server

이제 팀원이 public-server:8080에 접속하면 당신의 로컬 3000 포트로 연결된다.

주의할 점은 서버의 sshd_config에서 GatewayPorts yes 설정이 필요할 수 있다는 것이다. 그렇지 않으면 원격 서버의 localhost에서만 접속 가능하다.

매번 특정 포트를 지정하는 게 아니라, 모든 트래픽을 SSH를 통해 터널링하고 싶다면 동적 포트 포워딩을 사용한다.

Terminal window
ssh -D 1080 user@remote-server

이제 로컬에 SOCKS 프록시가 생성된다. 브라우저나 애플리케이션의 프록시 설정을 localhost:1080 (SOCKS5)로 설정하면, 모든 네트워크 트래픽이 SSH 터널을 통해 원격 서버에서 나가게 된다.

이렇게 하면:

  • 공개 WiFi에서도 안전하게 인터넷을 사용할 수 있다
  • 지역 제한이 있는 서비스에 접속할 수 있다
  • 방화벽을 우회할 수 있다

백그라운드로 실행하고 싶다면:

Terminal window
ssh -fN -D 1080 user@remote-server
  • -f: 백그라운드로 실행
  • -N: 원격 명령을 실행하지 않고 포워딩만 수행
  • -D: SOCKS 프록시 포트 지정

개인키에 패스프레이즈를 설정하면 보안은 강화되지만, 서버에 접속할 때마다 패스프레이즈를 입력해야 하는 불편함이 있다. SSH 에이전트는 이 문제를 해결한다.

SSH 에이전트는 개인키를 메모리에 저장해두고, 필요할 때 자동으로 제공한다. 한 번만 패스프레이즈를 입력하면, 세션이 유지되는 동안 계속 사용할 수 있다.

에이전트를 시작한다.

Terminal window
eval "$(ssh-agent -s)"

개인키를 에이전트에 추가한다. 이때 패스프레이즈를 한 번 입력한다.

Terminal window
ssh-add ~/.ssh/id_ed25519

이제 서버에 접속할 때 패스프레이즈를 다시 입력할 필요가 없다.

등록된 키를 확인하려면:

Terminal window
ssh-add -l

모든 키를 에이전트에서 제거하려면:

Terminal window
ssh-add -D

점프 서버를 거쳐 최종 서버에 접속할 때, 점프 서버에는 개인키를 두고 싶지 않다. 이럴 때 에이전트 포워딩을 사용한다.

Terminal window
ssh -A user@jump-host

-A 옵션을 사용하면 로컬의 SSH 에이전트를 점프 서버에서도 사용할 수 있다. 점프 서버에서 다른 서버로 SSH 접속할 때, 로컬의 개인키로 인증한다. 개인키 자체는 전송되지 않고, 인증 요청만 로컬 에이전트로 포워딩된다.

Config 파일에 설정할 수도 있다.

Host jump-host
ForwardAgent yes

하지만 주의할 점이 있다. 점프 서버가 해킹당하면 공격자가 에이전트를 악용할 수 있다. 신뢰할 수 있는 서버에만 에이전트 포워딩을 사용하자.

SSH 서버를 운영한다면 보안 설정을 강화해야 한다. 서버의 /etc/ssh/sshd_config 파일을 편집해 설정을 변경할 수 있다.

  • 공개키 인증이 훨씬 안전하다. 비밀번호는 추측당하거나 무차별 대입 공격에 취약하다.

    PasswordAuthentication no
  • 루트 계정으로 직접 접속하는 것은 위험하다. 일반 사용자로 접속한 후 필요할 때만 sudo를 사용하는 것이 안전하다.

    PermitRootLogin no
  • 기본 포트 22번은 자동화된 공격의 주요 타겟이다. 포트를 변경하면 단순한 스캔 공격을 피할 수 있다.

    Port 2222
  • 특정 사용자만 SSH 접속을 허용한다. 또는 특정 그룹만 허용할 수도 있다.

    AllowUsers alice bob
    AllowGroups ssh-users
  • 기타 유용한 설정

    공개키 인증 활성화 (보통 기본값):

    PubkeyAuthentication yes

    빈 비밀번호 금지:

    PermitEmptyPasswords no

    X11 포워딩 비활성화 (필요없다면):

    X11Forwarding no

설정을 변경했다면 SSH 서비스를 재시작해야 적용된다.

Terminal window
sudo systemctl restart sshd

서버만이 아니라 클라이언트 측도 신경써야 한다.

개인키 파일의 권한이 너무 느슨하면 SSH가 사용을 거부한다. 올바른 권한으로 설정하자.

Terminal window
chmod 700 ~/.ssh
chmod 600 ~/.ssh/id_ed25519
chmod 644 ~/.ssh/id_ed25519.pub

개인키에 패스프레이즈를 추가하거나 변경하려면:

Terminal window
ssh-keygen -p -f ~/.ssh/id_ed25519

개인키가 유출되더라도 패스프레이즈가 있으면 즉시 사용할 수 없다. 그 사이에 키를 교체할 시간을 벌 수 있다.

SSH는 원격 접속만이 아니라 파일 전송에도 사용된다.

SCP(Secure Copy)는 SSH를 기반으로 한 파일 복사 도구다.

로컬 파일을 원격으로 전송:

Terminal window
scp local_file.txt user@remote:/path/to/destination/

디렉터리 전체를 복사하려면 -r 옵션:

Terminal window
scp -r local_directory user@remote:/path/to/destination/

원격에서 로컬로 다운로드:

Terminal window
scp user@remote:/path/to/file.txt ~/Downloads/

여러 파일을 한 번에:

Terminal window
scp file1.txt file2.txt user@remote:/destination/

SCP는 간단하지만, 큰 파일이나 디렉터리를 전송할 때는 비효율적이다. rsync는 변경된 부분만 전송해서 훨씬 빠르다.

Terminal window
rsync -avz -e ssh /local/path/ user@remote:/remote/path/

옵션의 의미:

  • -a: 아카이브 모드. 권한, 타임스탬프, 심볼릭 링크 등을 보존한다
  • -v: 진행 상황을 상세히 출력한다
  • -z: 전송 중 압축한다
  • -e ssh: SSH를 사용해 전송한다

rsync는 증분 백업에 아주 유용하다. 처음엔 모든 파일을 전송하지만, 다음부터는 변경된 파일만 전송한다.

삭제된 파일도 동기화하려면 --delete:

Terminal window
rsync -avz --delete -e ssh /local/path/ user@remote:/remote/path/

드라이런(실제로 실행하지 않고 무엇이 바뀔지 미리 확인):

Terminal window
rsync -avz --dry-run -e ssh /local/path/ user@remote:/remote/path/

SSH로 접속하지 않고도 원격 명령을 실행할 수 있다.

Terminal window
ssh user@remote-host 'ls -la /var/log'

여러 명령을 실행하려면:

Terminal window
ssh user@remote-host 'cd /var/www && git pull && pm2 restart app'

로컬 스크립트를 원격에서 실행:

Terminal window
ssh user@remote-host 'bash -s' < local_script.sh

이런 식으로 배포 자동화 스크립트를 만들 수 있다.

SSH Multiplexing으로 성능 개선하기

Section titled “SSH Multiplexing으로 성능 개선하기”

같은 서버에 여러 번 접속할 때, 매번 새 연결을 맺는 건 비효율적이다. SSH Multiplexing은 하나의 TCP 연결을 재사용해 여러 SSH 세션을 처리한다.

~/.ssh/config에 추가:

Host *
ControlMaster auto
ControlPath ~/.ssh/sockets/%r@%h-%p
ControlPersist 600

소켓 디렉터리를 만든다:

Terminal window
mkdir -p ~/.ssh/sockets

이제 첫 번째 SSH 접속이 연결을 열고, 이후 접속들은 그 연결을 재사용한다. ControlPersist 600은 마지막 세션이 종료된 후에도 10분간 연결을 유지한다.

git push나 scp를 반복할 때 눈에 띄게 빨라지는 걸 느낄 수 있다.

SSH와 함께 사용하면 좋은 도구들을 소개한다.

SSH 연결이 끊어지면 자동으로 재연결한다. 포트 포워딩을 계속 유지해야 할 때 유용하다.

Terminal window
autossh -M 0 -f -N -L 3306:db.internal:3306 user@bastion

WiFi와 모바일 네트워크를 오가거나 네트워크가 불안정한 환경에서도 끊김 없는 연결을 제공한다. UDP를 사용하고, 로컬에서 즉시 에코하기 때문에 반응이 빠르다.

Terminal window
mosh user@hostname

SSH 세션 안에서 터미널 멀티플렉서를 사용하면, 연결이 끊어져도 작업이 계속 실행된다. 다시 접속하면 세션에 다시 붙을 수 있다.

Terminal window
tmux
# 작업...
# Ctrl+B D로 detach
# 나중에 다시 접속
tmux attach

원격 디렉터리를 로컬에 마운트한다. 원격 파일을 로컬 파일처럼 편집할 수 있다.

Terminal window
sshfs user@remote:/remote/path ~/local/mount/point

참고