hswork.win

Claude가 내 홈서버에 직접 SSH로 들어오게 만들기까지

2026-08-15 · 인프라

집에 미니PC 한 대를 상시 가동 서버로 두고 도커로 서비스 몇 개를 굴리고 있다. 개발은 윈도우 PC에서 하고 배포와 운영은 미니PC에서 돌아간다. 그러다 보니 컨테이너 상태 하나 확인하려 해도 터미널을 열고 접속해서 결과를 복사해 붙여넣는 일을 매번 반복해야 했다.

그래서 클로드가 미니PC에 직접 접속하도록 만들어보기로 했다. 결론부터 말하면 됐지만 예상 못 한 벽을 세 개나 넘어야 했다.

없는 옵션을 믿고 시작했다

처음 생각한 방법은 단순했다. 명령 실행용 도구에 접두사 설정이 있길래 모든 명령 앞에 접속 명령을 붙이도록 지정하면 자동으로 원격에서 실행될 거라고 봤다.

설정 화면에는 정상 동작 중이라고 떴다. 그런데 테스트를 해보니 이상했다. 날짜를 물었더니 윈도우 명령 프롬프트가 새 날짜를 입력하라고 되물었다. 리눅스에만 있는 명령을 넣으니 그런 명령이 없다는 윈도우식 오류가 나왔다. 반대로 윈도우 전용 명령은 멀쩡히 돌아갔다.

접두사가 전혀 붙지 않았고 명령이 전부 내 윈도우 PC에서 실행되고 있었던 것이다. 양쪽에 다 존재하는 명령만 성공한 것처럼 보여서 처음엔 눈치채기 어려웠다.

공식 문서를 열어보니 답이 나왔다. 그 도구가 지원하는 옵션은 로그 관련 두 개뿐이었다. 내가 쓴 접두사 옵션은 아예 존재하지 않았다. 모르는 인자라 조용히 무시되고 있었다. 설정을 잘못 쓴 게 아니라 애초에 불가능한 방식이었다.

키는 제대로 깔렸는데 여전히 실패했다

접두사를 포기하고 명령을 보낼 때마다 접속 명령으로 직접 감싸는 방식으로 바꿨다. 이번엔 연결 실패 코드가 떨어졌다.

자동화 도구는 비밀번호 프롬프트에 답할 수 없으니 키 인증이 필요하다. 윈도우에서 키를 만들고 서버에 등록했다. 서버에서 확인해보니 키도 한 줄로 깔끔히 들어갔고 폴더 권한도 정상이었다.

내 터미널에서 직접 실행해봤다. 비밀번호 없이 바로 붙었다. 그런데 똑같은 명령을 도구를 통해 실행하면 여전히 실패했다. 그것도 오류 메시지가 한 줄도 없이.

오류 메시지가 통째로 사라지고 있었다

디버깅이 막힌 진짜 이유는 실패 원인을 볼 수가 없어서였다. 상세 로그 옵션을 붙여도, 오류 출력을 합쳐도, 파일로 빼돌려도 아무것도 나오지 않았다. 도구 자체의 로그에도 연결 상태만 찍히고 명령 실행 내역은 없었다.

그래서 아주 단순한 실험을 하나 했다. 표시 명령과 컴퓨터 이름 조회를 한 줄에 나란히 놓고 실행했다. 앞뒤의 표시 명령은 멀쩡히 찍혔는데 가운데 컴퓨터 이름만 사라졌다.

명령 프롬프트 자체에 내장된 명령은 출력이 오는데 별도 실행 파일로 도는 명령만 출력이 유실되고 있었다. 명령 프롬프트를 한 단계 거치면 그 아래에서 뜬 프로그램의 출력이 어디로도 연결되지 않는 구조였다. 이게 그동안 접속 오류를 한 글자도 못 본 이유였다.

다행히 이 도구에는 명령 프롬프트를 거치지 않고 실행 파일을 직접 띄우는 모드가 따로 있었다. 그걸로 바꾸자 출력이 잡히기 시작했다.

핑이 거짓말을 했다

이제 오류를 볼 수 있게 됐으니 다시 확인했다. 미니PC로 핑을 보내니 대상 호스트에 연결할 수 없다는 응답이 왔다. 서버가 죽었나 싶었다.

그런데 응답을 보낸 주소가 미니PC가 아니라 내 윈도우 PC 자신이었다. 목적지가 답한 게 아니었다. 내 PC가 길을 못 찾겠다고 스스로 돌려준 오류였다. 게다가 결과 마지막 줄에는 손실 없음이라고 찍혀 있었다. 정상 응답을 받았다는 뜻이 아니라 오류 메시지를 받은 걸 센 것이다. 이 줄만 보면 성공으로 착각하기 딱 좋다.

내 터미널에서 핑을 보내면 멀쩡히 응답이 왔다. 그래서 핑 말고 접속 포트를 직접 두드려봤다. 짧은 스크립트로 22번 포트에 연결을 열었더니 서버의 접속 안내 문구가 그대로 돌아왔다.

네트워크도 방화벽도 포트도 전부 정상이었다. 핑이 쓰는 통로만 막혀 있었을 뿐이다. 진단 도구 하나를 믿고 엉뚱한 데를 파고 있었던 셈이다.

범인은 접속 프로그램이었다

남은 용의자는 하나였다. 윈도우에 기본으로 들어 있는 접속 프로그램이다.

확인해보니 이 프로그램은 로그 파일 경로를 지정해줘도 그 파일조차 만들지 못하고 즉시 죽었다. 인자를 읽기도 전에 시작 단계에서 실패한다. 반면 같은 환경에서 다른 접속 프로그램을 띄우니 버전 정보를 멀쩡히 출력했다.

콘솔 없이 자식 프로세스로 뜰 때 이 프로그램이 시작에 실패하는 것으로 보인다. 네트워크도 키도 권한도 아니고 프로그램 하나가 원인이었다.

실행 파일을 아예 빼버렸다

그 프로그램을 안 쓰면 된다. 접속 규약을 코드로 직접 구현한 라이브러리를 쓰면 외부 실행 파일이 필요 없다.

먼저 될지부터 확인했다. 라이브러리를 설치하고 짧은 스크립트로 접속해 명령 몇 개를 실행해봤다. 커널 정보와 서버 이름, 접속 계정이 그대로 돌아왔다. 들어간 것이다.

검증이 끝났으니 설정을 접속 전용 도구로 교체했다. 서버 주소와 계정, 키 파일 경로만 넘겨주면 되는 구조였다. 앱을 재시작하니 명령 실행뿐 아니라 파일 올리고 내리는 기능까지 함께 붙었다.

이제 컨테이너 상태를 물어보면 목록과 로그, 상세 설정을 한 번에 훑어서 답이 온다. 예전 같으면 내가 터미널을 열고 명령을 세 번 치고 결과를 복사해 붙여넣어야 했던 흐름이다. 설정 파일을 내려받아 고친 뒤 다시 올리는 것도 한자리에서 끝난다.

남은 것

세 번 막혔고 원인은 매번 예상과 달랐다.

첫 번째는 없는 옵션을 믿은 것이다. 설정 화면에 정상 동작이라 떠 있어도 그건 프로그램이 살아 있다는 뜻일 뿐, 내가 준 설정이 먹혔다는 보장이 아니다. 문서에서 옵션 목록을 확인하는 데 일 분이면 될 일을 몇 시간 돌아왔다.

두 번째는 침묵하는 실패였다. 오류 메시지가 안 보이면 원인을 추측할 게 아니라 왜 안 보이는지부터 풀어야 한다. 명령 세 개를 한 줄에 늘어놓은 실험 하나가 전환점이었다.

세 번째는 익숙한 진단 도구를 의심하지 않은 것이다. 핑이 실패했다고 네트워크가 죽은 게 아니었다. 핑과 실제 접속은 다른 통로를 쓴다. 포트를 직접 두드려보니 곧바로 답이 나왔다.

마지막으로 보안 한 가지. 이번에 만든 키에는 암호를 걸지 않았다. 자동화를 하려면 어쩔 수 없는 선택이지만 키 파일이 새어 나가면 서버가 그대로 열린다. 개인 환경이라 이대로 뒀다. 서버 쪽 설정에 접속 출처를 특정 주소로 제한하는 옵션을 걸어두면 위험을 상당히 줄일 수 있다. 권한이 큰 통로를 여는 작업인 만큼 편의만 보고 넘어가지 않는 게 좋겠다.

← 개발 기록 목록