본문으로 건너뛰기 SSH 원격 접속 자주 쓰는 명령어와 실무 팁 | ssh-config·포트포워딩·rsync·tmux

SSH 원격 접속 자주 쓰는 명령어와 실무 팁 | ssh-config·포트포워딩·rsync·tmux

SSH 원격 접속 자주 쓰는 명령어와 실무 팁 | ssh-config·포트포워딩·rsync·tmux

이 글의 핵심

우분투를 메인으로 쓰면서 다른 서버에 SSH로 매일 접속하는 개발자를 위한 실전 명령어 모음입니다. 키 생성·에이전트 관리, ~/.ssh/config로 접속 단축하기, ProxyJump로 배스천 경유하기, 로컬·리모트·다이나믹 포트 포워딩, scp/rsync/sftp의 실무 차이, tmux로 연결이 끊겨도 작업을 살려두는 법, 자주 만나는 에러 해결까지 다룹니다.

들어가며

리눅스 서버를 다루는 개발자라면 하루에도 몇 번씩 ssh user@host를 입력합니다. 문제는 서버가 하나둘 늘어나기 시작하면서부터입니다. IP 주소를 매번 외워야 하고, 서버마다 다른 사용자명과 키 파일을 기억해야 하고, 사내망 깊숙이 있는 서버에 접속하려면 배스천 호스트를 두 번 세 번 거쳐야 하는 상황이 옵니다. 이런 반복 작업을 그대로 두면 접속 한 번에 필요한 타이핑이 계속 늘어나고, 실수로 잘못된 서버에 접속하는 사고도 생깁니다.

이 글은 SSH 프로토콜 보안 원격 접속에서 다룬 키 교환·서버 인증 같은 프로토콜 이론은 다루지 않습니다. 대신 우분투 데스크톱을 메인으로 쓰면서 매일 터미널에서 다른 서버에 SSH로 접속하는 개발자가 실제로 손에 익혀야 할 명령어와 워크플로에 집중합니다. ~/.ssh/config로 접속을 단축하는 법, 여러 단계의 서버를 한 번에 경유하는 법, 포트 포워딩으로 원격 DB를 로컬처럼 쓰는 법, 연결이 끊겨도 작업이 죽지 않게 하는 법 같은 실전 팁을 순서대로 정리했습니다.

대상 독자

  • 우분투(또는 다른 리눅스 데스크톱)를 메인으로 쓰면서 여러 서버에 SSH로 접속하는 백엔드/DevOps 개발자
  • ssh [email protected] -p 2222 -i ~/.ssh/some_key처럼 매번 긴 명령어를 치는 데 지친 사람
  • 배스천 호스트를 경유해 내부망 서버에 접속해야 하는 환경에서 일하는 사람
  • 원격 서버의 DB나 관리 콘솔을 로컬 브라우저/클라이언트로 접속하고 싶은 사람

이 글을 읽으면

  • ssh-keygen, ssh-agent, ssh-add로 비밀번호나 패스프레이즈를 매번 입력하지 않고 접속할 수 있습니다.
  • ~/.ssh/configProxyJumpssh myserver 한 마디로 원하는 서버에 접속할 수 있습니다.
  • 로컬/리모트/다이나믹 포트 포워딩으로 원격 자원을 안전하게 로컬에 끌어올 수 있습니다.
  • scp, rsync, sftp 중 상황에 맞는 도구를 고를 수 있습니다.
  • tmux와 ServerAliveInterval로 연결이 끊겨도 작업을 잃지 않는 습관을 갖게 됩니다.
  • “Host key verification failed”, “Permission denied (publickey)” 같은 흔한 에러를 스스로 해결할 수 있습니다.

실전 경험에서 배운 교훈

SSH를 매일 쓰다 보면 처음에는 몰랐던 습관들이 하나씩 쌓입니다. 가장 크게 시간을 아껴준 것은 ~/.ssh/config를 제대로 정리한 것이었습니다. 서버가 서너 대일 때는 ssh user@ip -p port -i key 조합을 외워서 치는 게 그럭저럭 버틸 만하지만, 열 대가 넘어가면 어느 서버가 어떤 포트와 키를 쓰는지 헷갈리기 시작합니다. 이 상태에서 급하게 배포하다가 엉뚱한 서버에 명령을 실행할 뻔한 적도 있었는데, config 파일에 Host 별칭을 정리해두고부터는 그런 실수가 사라졌습니다.

두 번째로 크게 배운 것은 에이전트 포워딩(-A)을 함부로 켜면 안 된다는 점입니다. 편하다고 아무 서버에나 ForwardAgent yes를 켜두면, 그 서버가 침해당했을 때 공격자가 내 로컬 에이전트에 등록된 키로 다른 서버까지 연쇄적으로 접근할 수 있습니다. 이 위험을 알고 나서는 정말 필요한 배스천 호스트에서만, 그것도 그때그때 -A 플래그로 켜는 방식으로 바꿨습니다.

세 번째는 tmux 없이 장시간 작업하다가 와이파이가 잠깐 끊기면서 진행 중이던 빌드나 마이그레이션 스크립트가 통째로 죽어버린 경험입니다. 그 이후로는 원격 서버에서 오래 걸리는 작업을 할 때는 무조건 tmux 세션 안에서 시작하는 것을 습관으로 만들었습니다. 이 글의 각 섹션에는 이런 시행착오에서 나온 실전 팁을 함께 담았습니다.

1. SSH 키 생성과 관리

ssh-keygen — Ed25519를 권장하는 이유

SSH 키를 새로 만들 때는 RSA보다 Ed25519 타입을 우선 권장합니다.

# Ed25519 키 생성 (권장)
ssh-keygen -t ed25519 -C "[email protected]" -f ~/.ssh/id_ed25519

# 레거시 시스템 호환이 꼭 필요한 경우에만 RSA
ssh-keygen -t rsa -b 4096 -C "[email protected]" -f ~/.ssh/id_rsa

Ed25519는 타원곡선 기반 서명 알고리즘으로, 같은 수준의 보안 강도를 훨씬 짧은 키 길이로 제공합니다. RSA 4096비트 키와 비교했을 때 키 파일 크기가 작고, 서명·검증 연산 속도도 빠르며, 사람이 실수로 짧은 키 길이를 선택할 여지가 아예 없다는 점도 장점입니다(RSA는 -b 옵션으로 약한 길이를 지정하는 실수가 가능하지만 Ed25519는 키 길이 선택 자체가 없습니다). 다만 아주 오래된 SSH 서버(2014년 이전 OpenSSH)나 일부 레거시 네트워크 장비는 Ed25519를 지원하지 않을 수 있으므로, 그런 환경에서만 RSA를 예외적으로 씁니다.

키를 만들 때 패스프레이즈를 반드시 입력하는 것을 권장합니다. 패스프레이즈 없이 만든 개인키는 노트북이 분실되거나 파일이 유출됐을 때 그대로 로그인 수단이 되어버립니다. 패스프레이즈를 걸어도 매번 입력하지 않고 쓸 수 있는 방법은 바로 다음 섹션의 ssh-agent입니다.

ssh-copy-id로 공개키 배포하기

키를 만들었다면 공개키를 서버의 ~/.ssh/authorized_keys에 등록해야 합니다. 직접 cat으로 붙여넣을 수도 있지만, ssh-copy-id를 쓰면 실수 없이 한 줄로 끝납니다.

ssh-copy-id -i ~/.ssh/id_ed25519.pub [email protected]

# 포트가 기본값(22)이 아닌 경우
ssh-copy-id -i ~/.ssh/id_ed25519.pub -p 2222 [email protected]

ssh-copy-id는 내부적으로 비밀번호(또는 기존 키)로 한 번 접속한 뒤, 원격 서버의 ~/.ssh 디렉터리를 700 권한으로 만들고 authorized_keys 파일에 지정한 공개키를 append한 뒤 파일 권한을 600으로 맞춰줍니다. 이 권한 설정을 손으로 직접 하다가 실수하면(예: authorized_keys가 644로 남아있는 경우) 서버의 sshd가 “권한이 너무 열려 있다”고 판단해 해당 파일을 아예 무시해버리는 경우가 있습니다. ssh-copy-id를 쓰면 이런 권한 실수를 원천적으로 피할 수 있습니다.

ssh-agent와 ssh-add — 매번 비밀번호를 치지 않는 법

패스프레이즈로 보호된 키를 쓰면 접속할 때마다 패스프레이즈를 입력해야 하는데, 하루에도 몇 번씩 접속한다면 금방 피곤해집니다. ssh-agent는 개인키를 메모리에 올려두고 서명 요청이 올 때마다 대신 처리해주는 백그라운드 프로세스입니다.

# 에이전트 시작 (보통 데스크톱 세션에서는 자동으로 이미 실행 중)
eval "$(ssh-agent -s)"

# 키를 에이전트에 등록 — 이때 패스프레이즈를 한 번만 입력
ssh-add ~/.ssh/id_ed25519

# 등록된 키 목록 확인
ssh-add -l

# 등록된 모든 키 제거 (자리를 비울 때)
ssh-add -D

한 번 ssh-add로 등록하면 그 이후부터는 같은 로그인 세션 안에서 ssh를 몇 번을 실행하든 패스프레이즈를 다시 묻지 않습니다. 다만 ssh-agent는 메모리에서만 동작하기 때문에 로그아웃하거나 재부팅하면 등록된 키가 모두 사라지고, 다시 로그인 시마다 ssh-add를 반복해야 하는 번거로움이 있습니다.

이 번거로움을 해결하려면 데스크톱 환경의 자격증명 저장소와 연동하는 것이 좋습니다. GNOME을 쓰는 우분투에서는 gnome-keyring이 로그인 시 자동으로 ssh-agent 역할을 대신하도록 구성되어 있는 경우가 많아서, 최초 한 번만 패스프레이즈를 입력하고 “키링에 저장”을 선택하면 이후 로그인마다 자동으로 키가 로드됩니다. 여러 대의 컴퓨터에서 같은 키를 쓰거나 팀 단위로 SSH 키를 안전하게 관리하고 싶다면 1Password의 SSH agent 기능도 대안입니다. 1Password가 자체적으로 개인키를 볼트에 암호화 저장하고, SSH_AUTH_SOCK 환경변수를 1Password의 소켓으로 연결해주면 별도의 ssh-add 없이도 생체 인증이나 마스터 비밀번호만으로 서명을 처리해줍니다.

2. ~/.ssh/config로 접속 단축하기

Host 별칭으로 긴 명령어를 짧게

서버가 늘어날수록 진가를 발휘하는 것이 ~/.ssh/config 파일입니다. 이 파일에 서버별 설정을 미리 적어두면 ssh myserver처럼 별칭 하나로 접속할 수 있습니다.

# ~/.ssh/config

Host myserver
    HostName 203.0.113.10
    User deploy
    Port 2222
    IdentityFile ~/.ssh/id_ed25519

Host staging
    HostName staging.internal.example.com
    User ubuntu
    IdentityFile ~/.ssh/id_ed25519_work

Host db-server
    HostName 10.0.1.50
    User admin
    IdentityFile ~/.ssh/id_ed25519_work
# 이제 아래 명령 하나로 접속 끝
ssh myserver

Host 아래에 정의한 HostName, User, Port, IdentityFile은 각각 실제 접속 명령의 -p, -i 옵션과 user@host 부분을 대신합니다. 서버마다 사용자명이 다르고(어떤 서버는 ubuntu, 어떤 서버는 회사 계정명), 키 파일도 프로젝트별로 나눠 쓰는 경우가 흔한데, 이 정보를 config 파일에 한 번 적어두면 다시는 외울 필요가 없습니다. scp, rsync -e ssh, VS Code Remote-SSH 확장까지도 이 config 파일을 그대로 읽어서 활용하므로, 한 번 정리해두면 여러 도구에서 동시에 이득을 봅니다.

여러 서버를 패턴으로 관리하기

같은 성격의 서버가 여러 대라면 와일드카드 패턴으로 공통 설정을 한 번에 적용할 수 있습니다.

Host internal-*
    User app
    IdentityFile ~/.ssh/id_ed25519_work
    ProxyJump bastion

Host internal-api
    HostName 10.0.2.10

Host internal-worker
    HostName 10.0.2.20

internal-* 패턴은 internal-api, internal-worker처럼 이름이 internal-로 시작하는 모든 Host 항목에 공통 설정(User, IdentityFile, ProxyJump)을 자동으로 적용합니다. 개별 서버는 HostName만 지정하면 되므로 서버가 늘어나도 반복 작업이 거의 없습니다. config 파일은 위에서부터 순서대로 매칭되는 설정을 계속 누적 적용하는 방식이라, 구체적인 Host 항목을 패턴보다 먼저 적어두면 개별 값이 우선 적용됩니다.

ProxyJump(-J)로 배스천 경유 접속

사내망 안쪽 서버는 외부에서 바로 접속할 수 없고, 반드시 배스천(점프) 호스트를 거쳐야 하는 경우가 많습니다.

# 커맨드라인에서 직접 지정
ssh -J [email protected] [email protected]

# config 파일에 고정해두면 매번 -J를 안 써도 됨
Host bastion
    HostName bastion.example.com
    User deploy
    IdentityFile ~/.ssh/id_ed25519

Host internal-db
    HostName 10.0.0.5
    User app
    ProxyJump bastion
    IdentityFile ~/.ssh/id_ed25519

ProxyJump는 SSH 클라이언트가 먼저 배스천 호스트에 접속한 뒤, 그 연결 안에서 다시 최종 목적지로 SSH 연결을 이어가는 방식입니다. 예전에는 이를 위해 ProxyCommandncssh -W를 조합한 복잡한 설정을 썼지만, OpenSSH 7.3부터 추가된 ProxyJump(또는 -J)로 훨씬 간단하게 표현할 수 있게 됐습니다. 여러 단계를 거쳐야 한다면 -J bastion1,bastion2 처럼 쉼표로 연결해 다단 점프도 가능합니다. 배스천 호스트 하나를 config에 고정해두고 내부 서버들이 이를 참조하도록 만들어두면, 나중에 배스천 주소가 바뀌더라도 한 곳만 수정하면 됩니다.

3. 자주 쓰는 SSH 옵션

-v / -vvv — 접속이 안 될 때 원인 찾기

접속이 실패하거나 예상과 다르게 동작할 때 가장 먼저 켜야 하는 옵션이 디버그 출력입니다.

# 기본 디버그 (인증 단계만 대략 확인)
ssh -v user@host

# 아주 상세한 디버그 (키 협상, 시도한 키 목록까지 전부 출력)
ssh -vvv user@host

-v를 한 번 쓰면 접속 과정의 주요 단계(알고리즘 협상, 어떤 인증 방식을 시도했는지)가 출력되고, -v를 세 번(-vvv) 겹치면 클라이언트가 시도한 개별 키 파일 목록, 서버가 어떤 인증 방식을 허용했는지까지 전부 볼 수 있습니다. “Permission denied” 에러만 보고는 원인을 알 수 없을 때, -vvv 출력에서 “Offering public key” 다음 줄에 서버의 응답이 뭐라고 왔는지를 보면 키가 아예 전달되지 않은 것인지, 전달됐지만 서버가 거부한 것인지 구분할 수 있습니다.

-A (에이전트 포워딩)와 보안 주의점

배스천 호스트를 거쳐 그 너머의 서버에 다시 접속해야 할 때, 배스천에 개인키를 복사해두지 않고도 로컬의 에이전트를 그대로 이어 쓸 수 있게 해주는 옵션이 -A입니다.

ssh -A [email protected]
# 배스천 안에서 다시 ssh internal-server 실행 시
# 로컬 ssh-agent에 등록된 키로 인증 가능

편리하지만 반드시 주의해야 할 점이 있습니다. -A를 켜면 배스천 서버에 있는 동안 그 서버의 루트 권한을 가진 누군가(또는 침해한 공격자)가 에이전트 소켓에 접근해 내 로컬 키로 서명 요청을 대신 보낼 수 있습니다. 개인키 자체가 유출되는 것은 아니지만, 배스천이 살아있는 동안에는 마치 내 키를 가진 것처럼 다른 서버에 접근당할 위험이 있습니다. 그래서 신뢰할 수 있는 배스천에서만, 그것도 꼭 필요한 순간에만 -A를 켜는 것을 권장하며, ~/.ssh/configForwardAgent yes를 전역으로 켜두는 것은 피하는 것이 좋습니다.

-N / -f — 셸 없이 백그라운드로 포워딩만 유지하기

포트 포워딩만 필요하고 원격 셸에 로그인할 필요는 없을 때 쓰는 조합입니다.

# 셸을 열지 않고(-N) 백그라운드로(-f) 포워딩만 유지
ssh -N -f -L 15432:localhost:5432 user@db-bastion

-N은 원격 명령을 실행하지 않겠다는 의미로, 포트 포워딩이나 터널 목적으로만 연결을 쓸 때 불필요한 셸 세션을 열지 않게 해줍니다. -f는 인증이 끝난 뒤 프로세스를 백그라운드로 보내 터미널을 계속 점유하지 않도록 합니다. 이 두 옵션을 조합하면 터미널 창 하나를 계속 띄워둘 필요 없이, 필요할 때 실행해두고 잊어버려도 되는 상시 터널을 만들 수 있습니다. 다만 백그라운드로 넘어간 뒤에는 Ctrl+C로 끊을 수 없으므로, 종료하려면 ps aux | grep ssh로 프로세스를 찾아 kill해야 합니다.

4. 포트 포워딩 실전

로컬 포트 포워딩(-L) — 원격 DB를 로컬에서 접속하기

사내망 안에만 있는 데이터베이스에 로컬 PC의 DB 클라이언트(DBeaver, TablePlus 등)로 접속하고 싶을 때 가장 많이 쓰는 패턴입니다.

# 로컬 15432 포트 → (bastion을 거쳐) → 원격 DB 서버의 5432 포트
ssh -L 15432:db-internal.example.com:5432 [email protected]

이 명령을 실행한 뒤, DB 클라이언트에서 localhost:15432로 접속하면 실제로는 SSH 터널을 통해 bastion.example.com이 바라보는 db-internal.example.com:5432로 트래픽이 암호화되어 전달됩니다. 여기서 헷갈리기 쉬운 점은 db-internal.example.com이라는 주소는 내 로컬 PC가 아니라 접속한 bastion 서버 입장에서 유효한 주소라는 것입니다. bastion이 사내망 안에 있어서 db-internal이라는 이름을 DNS로 풀 수 있다면, 로컬 PC는 그 이름을 전혀 몰라도 접속이 가능합니다. DB 포트를 인터넷에 직접 노출하지 않고도 필요한 사람만 SSH 터널로 안전하게 접근하게 만드는 대표적인 방법입니다.

리모트 포트 포워딩(-R) — 로컬 서비스를 원격에서 접속 가능하게

방향이 반대인 경우도 있습니다. 로컬 PC에서 개발 중인 서버를 외부에서(또는 다른 팀원이 원격 서버를 통해) 잠깐 접속해보고 싶을 때 씁니다.

# 원격 서버의 9090 포트 → 내 로컬 PC의 3000 포트
ssh -R 9090:localhost:3000 [email protected]

이 명령을 실행하면 public-host.example.com의 9090 포트로 들어온 트래픽이 SSH 터널을 거쳐 내 로컬 PC의 3000번 포트(예: 로컬에서 띄운 개발 서버)로 전달됩니다. 데모를 보여주거나 웹훅(webhook) 콜백을 잠깐 테스트할 때 유용하지만, 기본적으로 원격 서버의 sshd_config에서 GatewayPorts가 제한되어 있으면 그 서버의 localhost에서만 접근 가능하고 외부에는 열리지 않을 수 있습니다. 상시 운영용으로는 ngrok 같은 전용 터널링 서비스가 더 적합하고, -R은 팀 내부에서 빠르게 임시로 공유할 때 주로 씁니다.

다이나믹 포워딩(-D) — SOCKS 프록시로 쓰기

특정 포트 하나가 아니라, 여러 목적지로 가는 트래픽 전체를 SSH 터널로 흘려보내고 싶을 때는 다이나믹 포워딩을 씁니다.

ssh -D 1080 [email protected]

-D 1080을 실행하면 로컬 1080 포트가 SOCKS5 프록시 서버처럼 동작합니다. 브라우저나 curl의 프록시 설정을 socks5://localhost:1080으로 지정하면, 그 이후의 모든 요청이 bastion.example.com을 거쳐 나가게 됩니다. -L이 목적지를 하나로 고정하는 것과 달리, -D는 목적지를 그때그때 정할 수 있어 사내망 안의 여러 웹 콘솔(모니터링 대시보드, 내부 관리자 페이지 등)을 브라우저 하나로 넘나들며 확인해야 할 때 특히 편리합니다.

5. 파일 전송: scp vs rsync vs sftp

scp — 간단하고 빠른 일회성 전송

# 파일 하나 업로드
scp -i ~/.ssh/id_ed25519 ./build.tar.gz user@host:/var/app/

# 디렉터리 통째로 다운로드
scp -r user@host:/var/log/myapp ./local-logs

scp는 사용법이 단순하고 별도 설정 없이도 바로 쓸 수 있어 한두 개의 파일을 빠르게 옮길 때 적합합니다. 다만 전송 도중 연결이 끊기면 처음부터 다시 시작해야 하고, 이미 있는 파일과 달라진 부분만 골라 보내는 기능이 없습니다. 큰 파일을 전송하다가 네트워크가 불안정해서 매번 처음부터 다시 올리는 상황을 겪었다면, 다음에 설명할 rsync로 바꾸는 것을 권장합니다.

rsync -e ssh — 재개 가능한 델타 전송

# rsync로 SSH를 전송 채널로 사용
rsync -avz -e "ssh -i ~/.ssh/id_ed25519" ./dist/ user@host:/var/app/dist/

# 전송 중 끊긴 대용량 파일을 이어받기(partial)
rsync -avz --partial --progress -e ssh user@host:/data/backup.tar.gz ./

# 삭제된 파일까지 동기화하고 싶을 때 (원본에 없으면 대상에서도 삭제)
rsync -avz --delete -e ssh ./dist/ user@host:/var/app/dist/

rsync는 파일을 통째로 다시 보내는 대신, 전송할 파일을 블록 단위로 쪼개 이미 상대편에 존재하는 블록과 비교한 뒤 바뀐 블록만 전송하는 델타 전송 알고리즘을 씁니다. 이 덕분에 소스 코드 배포처럼 이전 버전과 크게 다르지 않은 파일을 반복해서 올릴 때 전송량이 훨씬 줄어듭니다. --partial 옵션을 켜면 전송 중 연결이 끊겨도 이미 받은 부분을 지우지 않고 남겨두었다가, 다음 실행 시 그 지점부터 이어받습니다. 대용량 파일이나 네트워크가 불안정한 환경(원격 사무실, 모바일 핫스팟 경유 등)에서 파일을 옮겨야 한다면 scp보다 rsync를 우선 고려하는 것이 좋습니다. -a는 권한·심볼릭 링크·수정 시각을 보존하는 archive 모드, -v는 진행 상황 출력, -z는 전송 중 압축을 의미합니다.

--delete 옵션은 강력한 만큼 위험하기도 합니다. 로컬 디렉터리를 원본으로 삼아 원격을 동기화할 때, 로컬에 없는 파일이 원격에만 있다면 그 파일까지 삭제해버립니다. 배포 스크립트에 이 옵션을 넣기 전에는 반드시 --dry-run을 먼저 붙여 실제로 무엇이 삭제될지 미리 확인하는 습관을 들이는 것을 권장합니다.

sftp — 대화형으로 파일을 탐색하며 옮기기

원격 서버의 디렉터리 구조를 보면서 파일을 골라 옮기고 싶을 때는 대화형 세션인 sftp가 편리합니다.

sftp user@host

sftp> ls /var/app/uploads
sftp> cd /var/app/uploads
sftp> get report.csv          # 다운로드
sftp> put ./local.bin /remote/path/   # 업로드
sftp> lcd ~/Downloads         # 로컬 작업 디렉터리 변경
sftp> lls                     # 로컬 디렉터리 목록
sftp> exit

sftp는 FTP와 비슷한 명령어 체계(ls, cd, get, put)를 제공하지만 전송 채널 자체는 SSH를 그대로 씁니다. l이 붙은 명령(lcd, lls)은 로컬 쪽을 조작한다는 뜻으로, 원격과 로컬 디렉터리를 오가며 어떤 파일이 있는지 눈으로 확인한 뒤 골라서 옮기고 싶을 때 scp나 rsync보다 직관적입니다. 스크립트로 자동화할 파일 전송에는 scp/rsync가 낫고, 사람이 직접 탐색하며 옮길 때는 sftp가 낫다고 구분하면 됩니다.

6. 세션 유지: tmux와 ServerAliveInterval

tmux/screen — 연결이 끊겨도 작업을 살려두기

노트북을 닫거나 와이파이가 잠깐 끊기는 순간 SSH 세션이 죽으면, 그 안에서 실행 중이던 빌드나 마이그레이션 스크립트도 함께 죽습니다. tmux(또는 screen)는 원격 서버 안에서 독립적으로 돌아가는 터미널 세션을 만들어, SSH 연결이 끊겨도 그 세션 자체는 서버에서 계속 실행되도록 해줍니다.

# 원격 서버에 접속한 뒤
tmux new -s deploy

# (긴 작업을 실행 중인 상태에서 연결이 끊기거나 Ctrl+B, D로 detach)

# 다시 접속했을 때 세션 재연결
tmux attach -t deploy

# 현재 떠 있는 세션 목록 확인
tmux ls

tmux new -s deploy로 이름을 붙인 세션을 만들고 그 안에서 작업을 시작하면, 이후 SSH 연결이 어떤 이유로든 끊기더라도 deploy라는 이름의 세션은 원격 서버의 메모리 안에서 계속 살아있습니다. 다시 접속했을 때 tmux attach -t deploy만 실행하면 마치 한 번도 끊긴 적 없는 것처럼 화면이 그대로 이어집니다. 오래 걸리는 배포 스크립트, 데이터베이스 마이그레이션, 대용량 빌드처럼 중간에 끊기면 곤란한 작업은 반드시 tmux 세션 안에서 시작하는 것을 권장합니다. 우분투 최신 배포판에는 tmux가 기본 설치되어 있지 않은 경우가 많으므로 sudo apt install tmux로 미리 설치해두면 좋습니다.

ServerAliveInterval — 유휴 상태에서 연결이 끊기는 것 방지

작업하지 않고 가만히 열어둔 SSH 세션은 방화벽이나 NAT 장비의 유휴 타임아웃 정책 때문에 자동으로 끊기는 경우가 많습니다.

# ~/.ssh/config
Host *
    ServerAliveInterval 30
    ServerAliveCountMax 4

ServerAliveInterval 30은 30초 동안 서버로부터 아무 응답이 없으면 클라이언트가 먼저 살아있는지 확인하는 패킷(keepalive)을 보내라는 설정입니다. ServerAliveCountMax 4는 이 확인 패킷에 4번 연속 응답이 없으면 연결이 끊긴 것으로 간주하고 세션을 종료하라는 뜻입니다. 이 설정을 Host *(모든 호스트에 적용)에 넣어두면 방화벽의 유휴 타임아웃보다 먼저 클라이언트가 keepalive를 보내게 되어, 가만히 로그를 보고 있거나 잠깐 자리를 비운 사이에 세션이 끊기는 문제를 크게 줄일 수 있습니다. tmux와 함께 쓰면 설령 연결이 끊기더라도 작업 내용은 서버 쪽에 그대로 남아 있으니 이중으로 안전합니다.

7. VS Code Remote-SSH로 원격 서버를 로컬처럼 개발하기

터미널만으로 원격 서버를 다루는 대신, VS Code의 Remote-SSH 확장을 쓰면 원격 파일 시스템을 로컬 디렉터리처럼 탐색하고 편집·디버깅까지 할 수 있습니다. 앞서 정리한 ~/.ssh/config의 Host 항목을 Remote-SSH 확장이 그대로 읽어서 접속 목록에 보여주므로, 이 글의 2번 섹션에서 설정을 미리 해두면 별도 작업 없이 바로 활용할 수 있습니다.

# ~/.ssh/config에 등록해둔 Host라면
# VS Code에서 F1 → "Remote-SSH: Connect to Host..." → devbox 선택

Remote-SSH 확장을 우분투 데스크톱에서 쓸 때 흔히 겪는 캐시 문제, .vscode-server 버전 불일치, ProxyJump와의 연동 방법 같은 세부 사항은 이 글에서는 짧게만 언급합니다. 더 자세한 트러블슈팅은 우분투 VS Code 개발자 팁 글의 “Remote-SSH로 원격 우분투 서버 개발” 섹션에서 다루고 있으니 함께 참고하시기 바랍니다.

8. 자주 만나는 에러와 해결

Host key verification failed

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@

이 경고는 SSH 클라이언트가 ~/.ssh/known_hosts에 저장해둔 서버의 호스트 키와, 지금 접속하려는 서버가 제시한 호스트 키가 다를 때 나타납니다. 서버를 재설치했거나, 클라우드에서 같은 IP를 재사용하는 새 인스턴스를 발급받았을 때 흔히 발생합니다. 정말로 서버가 바뀐 것이 맞다면 아래처럼 기존 항목을 지우고 새로 접속해 새 지문(fingerprint)을 등록하면 됩니다.

# 특정 호스트의 known_hosts 항목만 제거
ssh-keygen -R server.example.com

# 다시 접속하면 새 호스트 키 등록 여부를 물어봄
ssh server.example.com

다만 이 경고를 아무 확인 없이 습관적으로 무시하고 지워버리는 것은 위험합니다. 원래 서버가 그대로인데 이 경고가 뜬다면 중간자 공격(MITM)일 가능성도 있으므로, 서버를 재설치하거나 IP를 재발급받은 사실을 스스로 알고 있는 경우에만 known_hosts 항목을 지워야 합니다.

Permission denied (publickey)

이 에러는 원인이 다양해서 -vvv로 디버그 로그를 먼저 확인하는 것이 가장 빠릅니다. 흔한 원인은 다음과 같습니다.

  • 원격 서버의 권한 문제: ~/.ssh 디렉터리가 700, authorized_keys 파일이 600이 아니면 sshd가 파일을 신뢰하지 않고 무시합니다.
  • 에이전트에 키가 너무 많음: ssh-add -l로 등록된 키가 여러 개면 서버가 “Too many authentication failures”로 먼저 거절할 수 있습니다. IdentitiesOnly yesIdentityFile을 config에 명시해 특정 키만 시도하도록 제한하면 해결됩니다.
  • 계정명 불일치: user@host의 계정명이 실제 서버 계정과 다르면 공개키가 맞아도 거부됩니다.
# ~/.ssh/config에 특정 키만 쓰도록 강제
Host myserver
    HostName 203.0.113.10
    User deploy
    IdentityFile ~/.ssh/id_ed25519
    IdentitiesOnly yes

IdentitiesOnly yes는 ssh-agent에 다른 키가 아무리 많이 등록되어 있어도 config에 명시한 IdentityFile만 시도하도록 강제하는 옵션입니다. 회사 키와 개인 키를 함께 에이전트에 등록해둔 상태에서 특정 서버가 계속 “Too many authentication failures”로 거부한다면 이 옵션을 추가하는 것으로 대부분 해결됩니다.

접속이 느릴 때 — UseDNS와 GSSAPI

접속하는 데 몇 초씩 멈춰 있는 느낌이 든다면, 인증 자체보다 서버 쪽의 부가 검증 절차가 원인인 경우가 많습니다.

# 서버 쪽 설정: /etc/ssh/sshd_config
UseDNS no
GSSAPIAuthentication no
sudo systemctl restart sshd

UseDNS yes(일부 배포판의 기본값)가 켜져 있으면, 클라이언트가 접속할 때마다 서버가 클라이언트의 IP를 역방향 DNS로 조회해 호스트명을 확인하려 시도합니다. 이 조회에 쓰는 DNS 서버가 응답하지 않거나 느리면 접속할 때마다 타임아웃을 기다리느라 지연이 생깁니다. GSSAPIAuthentication도 마찬가지로 Kerberos 기반 인증을 시도하다가 실패하기까지 기다리는 시간이 낭비되는 경우가 흔합니다. 두 설정을 서버 쪽에서 no로 끄면 이런 불필요한 대기 시간이 사라져 접속이 즉시 이루어집니다. 이 설정은 클라이언트가 아니라 접속당하는 서버sshd_config에서 바꿔야 한다는 점에 주의해야 합니다.

9. 보안 팁

매일 쓰는 명령어만큼이나 중요한 것이 서버를 안전하게 유지하는 최소한의 설정입니다. 서버를 새로 발급받았을 때 아래 항목을 함께 점검하는 것을 권장합니다.

# /etc/ssh/sshd_config

# 비밀번호 로그인 비활성화 — 반드시 공개키 접속이 되는 것을 확인한 뒤에!
PasswordAuthentication no

# root 계정으로 직접 로그인 금지
PermitRootLogin no

# 접속 가능한 사용자를 명시적으로 제한 (선택)
AllowUsers deploy admin
sudo systemctl restart sshd

PasswordAuthentication no는 무차별 대입(brute-force) 공격 자체를 무력화하는 가장 효과적인 설정입니다. 다만 이 설정을 적용하기 전에 반드시 새 터미널 창을 하나 더 열어 공개키로 접속이 실제로 되는지 확인해야 합니다. 기존 세션을 닫기 전에 확인하지 않으면, 키 등록에 오류가 있었을 경우 서버에 접근할 방법이 완전히 사라질 수 있습니다. PermitRootLogin no는 root로의 직접 로그인을 막아 공격자가 자동화 스크립트로 가장 흔한 계정명(root)을 노리는 시도를 원천 차단합니다. 관리자는 일반 계정으로 접속한 뒤 sudo로 권한을 올려 작업하는 것이 표준적인 방식입니다.

여기에 더해 fail2ban을 설치해두면, 짧은 시간 내에 로그인 실패를 반복하는 IP를 자동으로 일정 시간 차단해줍니다.

sudo apt install fail2ban -y
sudo systemctl enable --now fail2ban

fail2ban은 SSH 로그(journalctl 또는 /var/log/auth.log)를 지켜보다가 설정된 임계치(기본적으로 몇 분 내 5회 실패 등)를 넘으면 해당 IP를 방화벽 규칙으로 즉시 차단합니다. 공개키 인증만 허용해두었더라도 인터넷에 노출된 22번 포트로는 여전히 스캐너들의 접속 시도가 끊이지 않으므로, 로그가 무의미하게 쌓이는 것을 줄이고 싶다면 fail2ban을 함께 켜두는 것을 권장합니다. 더 자세한 서버 초기 세팅 순서(사용자 생성, 방화벽 설정 등)는 우분투 필수 명령어 가이드의 초기 세팅 체크리스트 섹션에서 확인할 수 있습니다.

마치며

SSH를 매일 쓰는 개발자에게 가장 큰 시간 절약은 결국 반복을 없애는 것입니다. 키 관리를 에이전트에 맡기고, 접속 정보를 ~/.ssh/config에 정리해두고, 배스천 경유는 ProxyJump로 표준화하고, 오래 걸리는 작업은 tmux 세션 안에서 시작하는 습관만 들여도 하루에 SSH로 낭비하는 시간이 눈에 띄게 줄어듭니다. 여기에 포트 포워딩으로 사내망 자원을 안전하게 로컬로 끌어오는 법까지 익혀두면, 대부분의 원격 작업을 터미널 하나로 편하게 처리할 수 있습니다.

이 글에서 다루지 않은 SSH 프로토콜 자체의 키 교환·인증 흐름이 궁금하다면 SSH 프로토콜 보안 원격 접속을, 우분투 서버 운영 전반이 필요하다면 우분투 필수 명령어 가이드를, VS Code로 원격 개발 환경을 세팅하는 세부 사항은 우분투 VS Code 개발자 팁을 이어서 참고하시기 바랍니다.

같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.