SSD Nodes Learn
가이드 Matt Connor작성자 Matt Connor · 업데이트됨 2026-07-24

Claude Code VPS tmux 사용법 및 세션 유지 방법

SSH 연결이 끊겨도 Claude Code agent가 종료되지 않도록 Linux VPS에서 tmux를 사용하여 세션을 유지하는 방법을 설명합니다. SIGHUP 신호로 인한 작업 중단 문제를 방지하고 안정적인 개발 환경을 구축하는 구체적인 절차를 확인하십시오.

문제는 CLI가 아니라 노트북 덮개입니다

Claude Code는 노트북 덮개를 닫기 전까지는 정상적으로 작동합니다. 덮개를 닫으면 SSH 세션이 종료되고, shell에 SIGHUP 신호가 전달됩니다. 이로 인해 테스트를 실행 중이던 agent가 3분 만에 함께 종료됩니다. CLI를 실행할 때는 절전 모드로 전환되지 않는 장치를 사용하십시오. 또한 프로세스가 SSH 세션의 자식 프로세스가 되지 않도록 terminal multiplexer 내부에서 실행하십시오. 이것이 핵심입니다. 설치 과정이 아니라 tmux를 사용하는 것이 가장 중요합니다.

이 페이지는 agent를 실행 상태로 유지할 서버를 운영하는 방법에 관한 내용입니다. 켜 놓을 수 있는 Linux server가 없다면 이 내용은 해당되지 않습니다. 이것이 유일한 전제 조건입니다.

tmux의 실제 동작 방식

SSH로 접속하면 sshd은 shell을 fork하고 pseudo-terminal을 할당합니다. 해당 shell에서 실행하는 모든 프로세스는 shell의 자식 프로세스가 됩니다. 연결이 끊기면 kernel은 pty를 해제합니다. 이때 shell은 SIGHUP 신호를 받으며, 자식 프로세스들에게도 hangup 신호를 보냅니다. 따라서 실행 중인 foreground process들이 종료됩니다.

tmux는 소유권 구조를 반전시킵니다. 입력하는 tmux 명령은 unix socket을 통해 terminal과 분리되어 실행 중인 tmux server와 통신하는 thin client입니다. session 내부의 shell은 sshd이 아니라 tmux server의 자식 프로세스입니다. SSH 연결을 끊어도 client는 종료되지만, server와 session 및 작업 중인 agent는 계속 실행됩니다. 다시 접속하여 tmux attach을 수행하면, 동일한 scrollback을 가진 동일한 shell로 복귀합니다. nohup 또한 hangup 상황에서 살아남지만, 다시 접속할 방법은 제공하지 않습니다. 즉, background 상태인 TUI에는 re-attach할 수 없습니다. Claude Code는 대화형 도구입니다. tmux(또는 screen)가 적합한 도구입니다.

Sizing the box

CLI는 Node 프로세스입니다. CLI 자체가 시스템 자원을 모두 점유하지는 않습니다. 시스템 자원을 점유하는 것은 사용자를 대신해 실행되는 에이전트입니다. 빌드, 전체 테스트 스위트, tsc, language server, 또는 Docker 내의 database가 이에 해당합니다. CLI가 아닌 toolchain의 규모에 맞춰 사양을 결정하십시오. swap을 설정하면 빌드 속도는 느려질 수 있으나, OOM kill로 인한 프로세스 강제 종료를 방지할 수 있습니다.

sudo fallocate -l 4G /swapfile
sudo chmod 600 /swapfile
sudo mkswap /swapfile
sudo swapon /swapfile
echo '/swapfile none swap sw 0 0' | sudo tee -a /etc/fstab

디스크 용량도 확인하십시오. repos, node_modules, Docker images는 빠르게 누적됩니다. 만약 toolchain이 container를 넘어 KVM guest나 local Kubernetes node와 같은 가상 머신(VM)을 실행한다면, 사양에 CPU virtualisation extensions이 포함되어 있는지 확인하십시오. running nested virtualization on a VPS는 guest 내부에서 설정하는 것이 아니라, provider가 활성화해 주어야 하는 기능이기 때문입니다.

비루트(non-root) 사용자 우선 생성

전용 홈 디렉터리를 가진 사용자를 생성하고, 해당 사용자의 위치에 public key를 배치합니다:

sudo adduser --disabled-password --gecos "" agent
sudo install -d -m 700 -o agent -g agent /home/agent/.ssh
sudo cp ~/.ssh/authorized_keys /home/agent/.ssh/authorized_keys
sudo chown agent:agent /home/agent/.ssh/authorized_keys
sudo chmod 600 /home/agent/.ssh/authorized_keys

의도적으로 agentsudo 그룹에 포함되지 않습니다. 시스템 패키지가 필요한 경우에만 설치합니다. 이 결정만으로도 잘못된 shell command가 host를 손상시킬 수 있는 대부분의 경로를 차단할 수 있습니다.

상시 가동 중인 서버를 위한 SSH 보안 관리

공용 인터넷에 연결되어 있고 agent와 소스 코드를 보유한 서버에서 password auth를 사용하는 것은 불필요한 위험입니다. 이 기능을 비활성화하십시오. Ubuntu 24.04 및 Debian 13에서 /etc/ssh/sshd_config/etc/ssh/sshd_config.d/*.conf을(를) 포함하므로, 메인 설정 파일을 수정하는 대신 파일을 생성하십시오.

# /etc/ssh/sshd_config.d/10-hardening.conf
PasswordAuthentication no
KbdInteractiveAuthentication no
PermitRootLogin no

설정을 검증하고 다시 로드하십시오. 테스트를 위해 두 번째 터미널에서 세션을 유지한 상태로 현재 세션을 열어두어야 합니다.

sudo sshd -t && sudo systemctl restart ssh

Ubuntu 24.04의 주의 사항: sshd는 socket-activated 방식입니다. 인증 설정은 systemctl restart ssh에 적용되지만, listening Port을(를) 변경할 경우 systemctl daemon-reloadssh.socket의 재시작이 필요합니다.

다음은 방화벽 설정입니다. SSH를 활성화하기 전에 SSH를 허용해야 합니다. 그렇지 않으면 접속이 차단됩니다.

sudo ufw allow OpenSSH
sudo ufw default deny incoming
sudo ufw default allow outgoing
sudo ufw enable

fail2ban의 이점을 명확히 이해하고 설치하십시오. password auth를 비활성화하면 brute force 공격은 성공할 수 없으므로, 실패한 시도들이 journal에 기록되는 것을 방지할 수 있습니다.

# /etc/fail2ban/jail.local
[sshd]
enabled = true
backend = systemd
maxretry = 5
bantime = 1h

마지막으로 sudo apt install unattended-upgradessudo dpkg-reconfigure -plow unattended-upgrades를 사용하여 자동으로 패치를 적용하십시오. tmux와의 상호작용에 주의하십시오. Unattended-Upgrade::Automatic-Reboot을(를) 활성화한 상태에서 커널 업데이트로 인해 서버가 재부팅되면 모든 세션이 종료됩니다. 자동 업데이트를 비활성화하고, 실행 중인 작업이 없을 때 직접 재부팅하십시오.

Ubuntu에 Node.js 및 Claude Code 설치하기

Claude Code는 Node CLI이므로 최신 버전의 Node가 필요합니다. 배포판 패키지는 버전이 낮은 경우가 많습니다. Ubuntu 및 Debian에서는 NodeSource를 사용하는 것이 일반적이며, 서명된 repo를 제공합니다 (apt-key은 더 이상 사용되지 않습니다):

curl -fsSL https://deb.nodesource.com/setup_24.x | sudo -E bash -
sudo apt install -y nodejs
node --version

주의할 점이 있습니다: CLI를 반드시 agent 사용자로 설치해야 하며, 절대 sudo npm -g으로 설치하지 마십시오. root 소유의 global prefix를 사용하면 나중에 권한 오류가 발생하며 npm cache에 root 소유의 파일이 남게 됩니다. 먼저 npm의 prefix를 사용자의 home 디렉토리로 설정하십시오:

mkdir -p ~/.npm-global
npm config set prefix ~/.npm-global
echo 'export PATH="$HOME/.npm-global/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc
npm install -g @anthropic-ai/claude-code
claude --version

export 설정은 ~/.profile이 아니라 ~/.bashrc에 작성해야 합니다. 또한 파일 상단의 "If not running interactively, don't do anything" 가드 문구보다 에 위치해야 합니다. tmux는 non-login shell을 시작할 수 있으며, 이 경우 ~/.bashrc을 읽고 ~/.profile을 건너뜁니다. ~/.profile은 login shell에서만 실행됩니다. nvm과 같은 version manager를 통해 사용자별 Node를 사용하는 것도 동일한 결과를 얻습니다. 핵심 목표는 npm install -gsudo을 필요로 하지 않게 만드는 것입니다. npm을 그대로 사용하거나, 현재 기본값으로 문서화된 Anthropic의 native install script를 사용하십시오. 설치 방법은 변경될 수 있으므로, 명령어를 붙여넣기 전에 Anthropic의 설치 문서를 확인하십시오.

실행하려면 repo 내부에서 claude를 실행하십시오. 처음 실행 시 인증 과정이 진행됩니다. headless 서버에는 브라우저가 없으므로, 로컬 PC에서 열 수 있는 URL과 터미널에 입력할 코드가 제공됩니다. (환경 변수에 API key를 설정하는 방법도 있습니다.) 어떤 방식이든 인증 정보는 서버에 저장됩니다. 이제 사람들이 자주 건너뛰는 단계로 넘어갑니다.

blast radius 논의

shell 접근 권한이 있는 agent는 shell과 동일합니다. 해당 agent가 실행되는 사용자가 읽을 수 있는 모든 파일을 읽을 수 있으며, 해당 사용자가 접근할 수 있는 모든 곳으로 파일을 전송할 수 있습니다. 이는 도구의 결함이 아니라 도구의 정의입니다. 따라서 agent가 실행되는 계정의 권한이 개별 설정보다 더 중요합니다.

  • 전용 비특권 사용자. sudo 그룹을 포함하지 않으며, 개인 계정과 홈 디렉토리를 공유하지 않습니다.
  • 서버 내 운영 환경 자격 증명 금지. ~/.aws/credentials에 운영 키를 보관하지 마십시오. .env를 운영 환경에서 복사해 오지 마십시오. 중요한 데이터에 쓰기 권한이 있는 데이터베이스 비밀번호를 제공하지 마십시오. agent에는 staging 또는 read-only 자격 증명을 제공하십시오.
  • 범위가 제한된 token. 단일 repository로 제한된 세밀한 GitHub token을 사용하십시오. 읽기 권한만 필요한 경우 deploy key를 사용하십시오.

Claude Code에는 권한 확인 프롬프트를 완전히 건너뛰는 flag가 포함되어 있습니다. 노트북이나 일회성 프로젝트에서는 사용자의 선택입니다. 하지만 token을 보유한 서버에서는 잘못된 명령어로 인해 git push --force가 발생하는 것을 막는 마지막 방어선이 사라집니다. 이 flag가 실제로 변경하는 내용과, 내장 sandbox부터 일회용 VPS에 이르기까지 해당 flag로 실행되는 agent를 격리하는 방법은 running Claude Code safely on a server에서 다룹니다.

Deploy key vs SSH agent forwarding

laptop의 key를 git이 사용할 수 있도록 ssh -A하고 싶은 유혹이 생길 수 있습니다. 하지만 이 방식이 부여하는 권한을 이해해야 합니다. agent forwarding은 로컬 SSH agent의 socket을 해당 사용자로 실행되는 프로세스에 노출합니다. agent를 포함하여 agent으로 실행되는 모든 프로세스는 연결이 유지되는 동안 도달 가능한 모든 host에 대해 key를 사용하여 서명할 수 있습니다. 이는 단순히 "이 repository 하나만 git pull 하도록 허용하는 것"보다 훨씬 넓은 범위입니다.

대신 서버에서 직접 key를 생성하십시오. 이를 repository별 deploy key로 등록하십시오(agent가 push를 해야 하는 경우에만 write access 허용). 그리고 해당 서버에서의 commit을 식별할 수 있도록 git identity를 설정하십시오.

ssh-keygen -t ed25519 -C "agent deploy key" -f ~/.ssh/id_ed25519_repo
cat ~/.ssh/id_ed25519_repo.pub   # paste into the repo's Deploy Keys
git config --global user.name "Agent (build box)"
git config --global user.email "agent@example.com"

The tmux workflow

sudo apt install tmux를 설치한 후, 최소한의 ~/.tmux.conf을 구성합니다:

set -g mouse on
set -g history-limit 50000
set -g default-terminal "tmux-256color"

일상적인 사용에는 다음 네 가지 명령어가 필요합니다:

tmux new -A -s claude     # attach to session "claude", creating it if absent
# ...run `claude` inside it, work normally...
# Ctrl-b then d           -> detach; everything keeps running
tmux ls                   # list sessions
tmux attach -t claude     # reattach, from this machine or any other
tmux kill-session -t claude

tmux new -A -s claude은 반드시 기억해야 합니다. 이 명령어는 세션이 존재하면 연결하고, 존재하지 않으면 새로 생성합니다. 따라서 하루 업무를 시작하거나 연결이 끊긴 후 다시 시작할 때 모두 이 명령어 하나로 해결할 수 있습니다. 별칭(alias)으로 등록하여 사용하십시오. 세션 내부에서 Ctrl-b c는 윈도우를 열고, Ctrl-b nCtrl-b p는 윈도우를 전환하며, Ctrl-b [는 이전 내용을 스크롤하기 위한 copy mode로 진입합니다 (q은 종료합니다).

종료하지 않고 계속 유지하는 세션에 대해 주의할 점이 있습니다. 에이전트는 매 턴마다 전체 대화 내용을 다시 전송합니다. 세션을 일주일 동안 실행해 두기 전에 장기간 유지되는 Claude Code 세션의 토큰 소모량을 확인하십시오.

Failure modes

"My session is gone." tmux lsno server running on /tmp/tmux-1000/default을(를) 출력합니다. 이는 프로세스가 tmux 내부에서 실행되지 않았음을 의미합니다. SSH로 접속한 후 claude를(를) 직접 실행했으며, 연결이 끊기면서 프로세스가 종료된 것입니다. 복구할 수 있는 데이터가 없습니다. 이를 방지하려면 로그인 후 가장 먼저 tmux new -A -s <project>를(를) 실행하십시오.

Pane 크기가 매우 작아집니다. tmux는 연결된 클라이언트 중 가장 작은 크기에 맞춰 세션 크기를 조정합니다. 다른 기기에서 연결된 기존 클라이언트가 화면 크기를 제한할 수 있습니다. 접속 시 다른 클라이언트를 강제로 종료하려면 tmux attach -d -t claude를(를) 사용하십시오.

빌드 중 Killed가(이) 출력됩니다. 스택 트레이스 없이 단어 하나만 출력됩니다. sudo dmesg -T | grep -i -E 'out of memory|killed process'를(를) 통해 확인하십시오. 커널의 OOM killer가 가장 큰 프로세스를 선택한 것입니다. Node 환경에서는 FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory가(이) 나타날 수 있습니다. 해결 방법은 다음과 같습니다: swap 추가(위 참조), 테스트 및 컴파일 병렬성 제한, NODE_OPTIONS=--max-old-space-size=...를(를) 사용하여 Node heap 크기 확장, 또는 VPS 사양 업그레이드. OOM killer가 빌드 프로세스 대신 tmux server를 선택하여 세션이 종료될 수도 있습니다. systemd-oomd가(이) 실행 중인 경우, 동일한 방식으로 사용자 slice 전체를 종료할 수 있습니다.

npm error code EACCES / permission denied, mkdir '/usr/lib/node_modules/...'. root 권한의 prefix에 전역 설치를 시도했습니다. 위에서 설명한 ~/.npm-global prefix를 사용하십시오. 이전에 sudo npm를(를) 실행했다면 Your cache folder contains root-owned files이(가) 나타날 수 있습니다. 이 경우 sudo chown -R $(id -u):$(id -g) ~/.npm로 복구하십시오.

claude: command not found — 간헐적으로 발생. PATH export 설정이 "If not running interactively, don't do anything" 가드 아래의 ~/.bashrc에 위치하여, 비대화형 쉘(non-interactive shell)에서 이를 건너뜁니다. 해당 export를 가드 위로 이동하고 ~/.profile이(가) 아닌 ~/.bashrc에 유지하십시오. tmux는 ~/.bashrc은(는) 읽지만 ~/.profile은(는) 읽지 않는 non-login shell을 시작할 수 있습니다.

접속 후 색상이 깨짐. TERM 불일치 문제입니다. 위쪽의 default-terminal 라인을 적용하여 해결하십시오.

재부팅 후 세션이 사라짐. 버그가 아닙니다. tmux server는 프로세스이며, 재부팅 시 프로세스가 종료됩니다. uptime를(를) 확인하십시오.

규모가 커질 때 발생하는 문제

프로젝트 증가. 각 repo마다 해당 이름을 가진 tmux session을 하나씩 생성하십시오. tmux ls이 대시보드 역할을 수행합니다. 명명 규칙을 지키지 않으면 session이 0, 1, 2처럼 무질서해집니다. Port 할당도 마찬가지입니다. 6개의 repo가 모두 :3000을 사용하려 한다면, 수동 할당을 중단하십시오. Traefik reverse proxy를 사용하여 Docker Compose 하위의 여러 앱을 호스트 이름으로 라우팅하여 디스패칭을 처리하십시오.

사용자 증가. tmux socket은 사용자별로 생성됩니다. 따라서 동일한 서버를 사용하는 두 명의 개발자는 각자의 tmux server를 가지며 서로의 session을 볼 수 없습니다. 공유 socket을 통해 하나의 session을 공유하면 모든 사용자가 동일한 Unix user의 shell에 입력하게 됩니다. 이는 감사(audit) 및 권한 문제를 야기합니다. 별도의 user를 사용하는 것이 가장 확실하고 올바른 방법입니다.

비대화형 작업. tmux는 사용자가 접속하는 대화형 session을 위한 도구입니다. 감시자가 없는 상태에서 정해진 일정에 따라 실행되는 작업은 systemd unit과 timer를 사용해야 합니다. 이 방식을 사용하면 로깅, 재시작 정책, 부팅 시 자동 실행 기능을 기본적으로 사용할 수 있습니다. cron 방식의 작업을 실행하기 위해 tmux를 사용하는 것은 해당 작업이 service로 동작해야 함을 의미합니다.

마지막 참고 사항: agent가 시작될 때 dev server를 0.0.0.0이 아닌 127.0.0.1에 바인딩하도록 설정하십시오. ufw에서 port를 개방하는 대신 SSH tunnel (ssh -L 3000:127.0.0.1:3000 agent@your-server)을 통해 접속하십시오. 6개 이상의 port를 포워딩해야 하거나, 여러 기기에서 동시에 프리뷰를 확인해야 하는 경우, VPS에 self-hosted WireGuard VPN 구축을 고려하십시오. 이 경우 dev server는 private interface에 바인딩되며, ufw는 public interface로부터의 모든 접속을 차단합니다. 방화벽은 port를 계속 개방하지 않을 때만 유효합니다.

Claude Code가 유일한 선택지는 아닙니다: VPS에서 coding AI agent 실행 시 Aider와 Goose도 고려 대상입니다.

FAQ

SSH 연결이 끊겨도 Claude Code가 계속 실행되나요?

tmux 내부에서 실행한 경우에만 가능합니다. SSH shell에서 직접 실행한 프로세스는 해당 shell의 자식 프로세스이며, 연결이 끊기면 pty와 함께 종료됩니다. tmux 내부에서는 shell이 분리된 tmux server에 속하므로, agent가 작업을 계속 수행하며 tmux attach을 통해 이전 스크롤백 상태로 다시 연결됩니다. 로그인 후 매번 tmux new -A -s <project>를 첫 번째 명령어로 실행하면 이 문제를 방지할 수 있습니다.

sudo npm install -g으로 CLI를 설치해야 하나요?

아니요. root 소유의 global prefix를 사용하면 이후 설치 시 EACCES 오류가 발생하며 npm cache에 root 소유의 파일이 생성됩니다. npm의 prefix를 ~/.npm-global로 설정하거나 nvm 같은 version manager를 사용하십시오. 그 다음 권한이 없는 agent 사용자로 설치한 뒤, 대화형 가드 상단에서 ~/.bashrc으로부터 ~/.npm-global/binPATH로 export하십시오. 이미 sudo npm을 실행했다면 sudo chown -R $(id -u):$(id -g) ~/.npm로 cache를 복구하십시오.

agent가 실행 중인 서버에서 ssh -A agent forwarding을 사용해도 안전한가요?

작업에 필요한 것보다 훨씬 많은 권한을 부여합니다. Forwarding을 하면 로컬 SSH agent의 socket이 해당 사용자로 실행되는 모든 프로세스에 노출됩니다. 따라서 연결이 유지되는 동안 서버의 모든 프로세스가 사용자의 키를 사용하여 접근 가능한 모든 host에 대해 서명을 요청할 수 있습니다. 서버에서 ed25519 키를 생성하고, agent가 실제로 push를 해야 하는 경우에만 쓰기 권한을 갖도록 저장소별 deploy key로 등록하십시오.

빌드 시 Killed만 출력되는 이유는 무엇인가요?

stack trace 없이 단어 하나만 출력된다면 kernel OOM killer가 발생한 것입니다. sudo dmesg -T | grep -i -E 'out of memory|killed process'로 이를 확인하십시오. Node 환경에서는 JavaScript heap out of memory이 출력될 수 있습니다. 다음 해결 방법을 순서대로 시도하십시오: swapfile 추가, test 및 compiler parallelism 제한, NODE_OPTIONS=--max-old-space-size=... 상향, 그리고 VPS 사양 업그레이드. OOM killer가 빌드 프로세스가 아닌 tmux server를 선택하여 세션 전체가 종료될 수 있음에 주의하십시오.

tmux를 사용해야 하나요, 아니면 systemd service를 사용해야 하나요?

tmux는 사용자가 연결하여 모니터링하고 명령어를 입력하는 interactive session에 적합하며, agent session이 바로 이와 같습니다. 아무도 모니터링하지 않는 스케줄 기반 작업은 systemd unit과 timer를 사용해야 합니다. 이 방식은 로깅, restart policy, 부팅 시 자동 실행 기능을 기본적으로 제공합니다. 만약 cron 방식의 작업을 위해 tmux를 사용하려 한다면, 해당 작업은 service로 구현해야 합니다.