Home Lab 구축기 (3): Windows 설정

최규민

최규민

2025년 6월 12일3 분 소요

Home Lab 구축기 (3): Windows 설정


지난 포스트에서는 WSL 설치 및 SSH서버 패키지 설치를 다루었습니다.


우리가 카페에서 집에 있는 딥러닝 서버(데스크탑)에 접속하려면 지난 포스트에서 설정한 포트를 통해야 하는데요,

Windows에서는 방화벽을 통해 이 접근을 차단하고 있습니다.


따라서

  1. Windows의 방화벽 설정을 통해, 딥러닝 서버(데스크탑)에 지정된 포트로의 접근을 허용하는 인바운드 규칙을 추가해야 합니다.

또한, 기본적으로 WSL의 IP 주소는 동적이라서,

  1. 포트를 통한 외부 접근이 우리의 데스크탑에 도달할 수 있도록 WSL IP를 동적 추출해야 합니다.\

이를 위해서, (1)과 (2)를 가능케 하는 PowerShell 스크립트를 작성하고, 이 .ps1 코드파일이 딥러닝 서버(데스크탑)가 켜질 때마다 자동으로 실행되게 하는 작업이 필요합니다.


이 지점은 네트워킹 모드에 따라 설정이 달라지는데, WSL2에는 기본적으로 두 가지 네트워킹 모드가 있습니다.

NAT 모드 (기본값)Mirrored 모드
WSL IP부팅마다 변경됨Windows와 동일
포트포워딩netsh portproxy 필요불필요 (WSL 포트가 Windows에서 바로 접근 가능)
.wslconfig별도 설정 없음networkingMode=mirrored 필요

본인의 .wslconfig 파일(C:\Users\{사용자명}\.wslconfig)에 networkingMode=mirrored가 설정되어 있다면 Mirrored 모드 스크립트를,
없다면 NAT 모드 스크립트를 사용하세요.

bash
# .wslconfig 확인 (CMD에서)
type %USERPROFILE%\.wslconfig
💡
Windows Insider 빌드(26xxx)를 사용 중이라면 Mirrored 모드를 추천합니다.

NAT 모드에서 네트워크가 불안정해지는 경우가 잦기 때문입니다.


🪟Windows 설정

방화벽 설정

인터넷 블로그들을 통해 여러 쉘 스크립트를 찾을 수 있었습니다.


이들 중 필요한 것만 골라내고, 예외 처리 등 안정성을 강화하여 아래와 같은 코드를 완성할 수 있었습니다.

NAT 모트

bash
# 1. WSL 시작 (systemd가 ssh.service를 자동 시작)
# 참고: 관리자 권한은 작업 스케줄러의 "가장 높은 수준의 권한으로 실행" 옵션으로 보장됨
Write-Host "🔄 WSL 시작 중..."
wsl -d Ubuntu -u root echo "WSL started" | Out-Null
Start-Sleep -Seconds 5  # systemd 초기화 대기

# 2. WSL2 IP 동적 추출
Write-Host "🔍 WSL2 IP 추출 중..."
$wsl_ip = (wsl hostname -I).Split()[0].Trim()
if (-not $wsl_ip) {
    Write-Error "WSL2 IP 추출 실패 - WSL 인스턴스가 실행 중인지 확인하세요."
    exit 1
}
Write-Host "📍 WSL2 IP: $wsl_ip"

# 3. 포트 설정
$ports = @({포트번호입력})

# 4. 방화벽 규칙 처리
foreach ($port in $ports) {
    $rule_name = "WSL2_Port_${port}"

    if (Get-NetFirewallRule -DisplayName $rule_name -ErrorAction SilentlyContinue) {
        Remove-NetFirewallRule -DisplayName $rule_name -Confirm:$false
        Write-Host "🔴 기존 방화벽 규칙 제거: $rule_name"
    }

    New-NetFirewallRule `
        -DisplayName $rule_name `
        -Direction Inbound `
        -LocalPort $port `
        -Protocol TCP `
        -Action Allow
    Write-Host "🟢 새 방화벽 규칙 추가: $rule_name"
}

# 5. 포트포워딩 설정
netsh interface portproxy reset | Out-Null
Write-Host "🧹 모든 포트포워딩 초기화"

foreach ($port in $ports) {
    netsh interface portproxy add v4tov4 `
        listenport=$port `
        listenaddress=0.0.0.0 `
        connectport=$port `
        connectaddress=$wsl_ip
    Write-Host "🔵 포트포워딩 설정: $port → $wsl_ip"
}

# 6. SSH 서비스 상태 확인
Write-Host "🔍 SSH 서비스 상태 확인..."
$ssh_status = wsl -d Ubuntu -u root service ssh status
Write-Host $ssh_status

# 7. 결과 검증
Write-Host "`n✅ 최종 설정 상태"
Write-Host "======================"
netsh interface portproxy show v4tov4
Get-NetFirewallRule -DisplayName "WSL2_Port_*" | Format-Table DisplayName, Enabled, Direction, Action

Write-Host "🔄 WSL Ubuntu 지속 실행 설정..."
wsl -d Ubuntu -- bash -c "nohup sleep infinity &" | Out-Null

Mirrored 모드 스크립트

Mirrored 모드에서는 WSL과 Windows가 네트워크 스택을 공유하기 때문에, 포트포워딩이 필요 없습니다.

WSL에서 열린 포트는 Windows에서 바로 접근 가능합니다.

다만, 부팅 직후에는 WSL 서비스나 네트워크가 아직 준비되지 않은 상태일 수 있습니다.
제로 부팅 환경에 따라 WSL 서비스 시작에 수십 초가 걸리기도 하고, 네트워크 IP 할당이 늦어지기도 합니다.

따라서 스크립트 내부에서 각 단계의 준비 상태를 직접 확인하는 대기 루프를 넣어야 안정적으로 동작합니다.

⚠️
Mirrored 모드에서 netsh portproxy를 설정하면 WSL 안의 sshd와 포트가 충돌하여 SSH 서버가 시작되지 않습니다.

이전에 NAT 모드용 포트포워딩을 사용했다면 반드시 netsh interface portproxy reset으로 정리해주세요.

bash
# 로그 설정
$logPath = "C:\Users\catuscio\Documents\SSH\wsl_boot.log"
$useTranscript = $false

try {
    Start-Transcript -Path $logPath -Force -ErrorAction Stop
    $useTranscript = $true
} catch {}

function Write-Log {
    param([string]$Message)
    $line = "[$(Get-Date -Format 'yyyy-MM-dd HH:mm:ss')] $Message"
    Write-Host $line
    if (-not $useTranscript) {
        try { Add-Content -Path $logPath -Value $line -ErrorAction SilentlyContinue } catch {}
    }
}

function Exit-Script {
    param([int]$Code)
    if ($useTranscript) { try { Stop-Transcript } catch {} }
    exit $Code
}

Write-Log "스크립트 시작"

# 1. WSL 서비스 준비 대기 (WSLService 또는 LxssManager)
Write-Log "WSL 서비스 대기 중..."
$maxWait = 60
$waited = 0
while ($waited -lt $maxWait) {
    $svc = Get-Service -Name "WSLService" -ErrorAction SilentlyContinue
    if (-not $svc) { $svc = Get-Service -Name "LxssManager" -ErrorAction SilentlyContinue }
    if ($svc -and $svc.Status -eq "Running") {
        Write-Log "WSL 서비스 준비 완료 ($($svc.Name))"
        break
    }
    Start-Sleep -Seconds 5
    $waited += 5
    Write-Log "WSL 서비스 대기 중... (${waited}s, 상태: $($svc.Status))"
}
if ($waited -ge $maxWait) {
    Write-Log "WSL 서비스 타임아웃"
    Exit-Script 1
}

# 2. 네트워크 준비 대기 (DHCP IP 할당 여부)
Write-Log "네트워크 대기 중..."
$maxWait = 120
$waited = 0
while ($waited -lt $maxWait) {
    $ip = (Get-NetIPAddress -AddressFamily IPv4 -ErrorAction SilentlyContinue |
           Where-Object { $_.PrefixOrigin -eq "Dhcp" })
    if ($ip) {
        Write-Log "네트워크 준비 완료: $($ip.IPAddress)"
        break
    }
    Start-Sleep -Seconds 5
    $waited += 5
    Write-Log "네트워크 대기 중... (${waited}s)"
}
if ($waited -ge $maxWait) {
    Write-Log "네트워크 타임아웃"
    Exit-Script 1
}

# 3. WSL 시작 (systemd가 ssh.service를 자동 시작)
Write-Log "WSL 시작 중..."
$maxRetry = 5
$success = $false
for ($i = 1; $i -le $maxRetry; $i++) {
    $output = wsl -d Ubuntu -- bash -c "nohup sleep infinity > /dev/null 2>&1 & disown; echo WSL_READY" 2>&1
    $exitCode = $LASTEXITCODE
    Write-Log "WSL 시도 $i - exit: $exitCode, output: $output"
    if ($exitCode -eq 0) {
        Write-Log "WSL 시작 성공 (시도 $i)"
        $success = $true
        break
    }
    Write-Log "WSL 시작 실패 (시도 $i), 10초 후 재시도..."
    Start-Sleep -Seconds 10
}
if (-not $success) {
    Write-Log "WSL 시작 포기"
    Exit-Script 1
}

# 4. systemd 초기화 대기 후 SSH 확인
Write-Log "systemd 초기화 대기 중..."
$maxWait = 30
$waited = 0
while ($waited -lt $maxWait) {
    $ssh_check = wsl -d Ubuntu -- bash -c "systemctl is-active ssh 2>/dev/null" 2>&1
    if ($ssh_check -match "active") {
        Write-Log "systemd + SSH 준비 완료 (${waited}s)"
        break
    }
    Start-Sleep -Seconds 2
    $waited += 2
}
if ($waited -ge $maxWait) {
    Write-Log "systemd 대기 타임아웃, SSH 수동 시작 시도"
}

$sshReady = $false
for ($i = 1; $i -le 6; $i++) {
    $ssh_status = wsl -d Ubuntu -u root -- service ssh status 2>&1
    Write-Log "SSH 상태 확인 (시도 $i): $ssh_status"
    if ($ssh_status -match "running" -or $ssh_status -match "active") {
        $sshReady = $true
        break
    }
    wsl -d Ubuntu -u root -- service ssh start 2>&1
    Start-Sleep -Seconds 5
}

if ($sshReady) {
    Write-Log "SSH 서비스 정상 동작 확인"
} else {
    Write-Log "SSH 서비스 상태 불확실 - 수동 확인 필요"
}

# 5. 방화벽 규칙 확인
$ports = @(2498)
foreach ($port in $ports) {
    $rule_name = "WSL2_Port_${port}"
    if (-not (Get-NetFirewallRule -DisplayName $rule_name -ErrorAction SilentlyContinue)) {
        New-NetFirewallRule -DisplayName $rule_name -Direction Inbound -LocalPort $port -Protocol TCP -Action Allow | Out-Null
        Write-Log "방화벽 규칙 추가: $rule_name"
    } else {
        Write-Log "방화벽 규칙 이미 존재: $rule_name"
    }
}

Write-Log "스크립트 완료"
Exit-Script 0
💡
이 스크립트는 반드시 UTF-8 with BOM으로 저장해야 합니다.

Windows PowerShell 5.1은 BOM이 없는 UTF-8 파일을 시스템 로케일(한국어 환경에서는 CP949)로 해석하는데요, 스크립트에 한글 문자열이 포함되어 있으면 파서 에러가 발생하여 첫 줄부터 실행이 안 됩니다. 로그 파일도 생성되지 않아서 원인 파악이 매우 어렵습니다.

VS Code를 사용하고 계시다면, 우측 하단의 인코딩 표시를 클릭하여 UTF-8 with BOM을 선택한 뒤 저장하면 됩니다.


여기서

bash
# 4. 포트 설정
$ports = @({포트번호입력})

에는 본인의 포트 번호를 입력하면 됩니다. 가령, $ports = @(22)


위 스크립트를 wsl2_port_forward.ps1로 저장 후,

초기 설정을 위해 저장한 디렉토리에서 Powershell을 관리자 권한으로 열고 아래 커맨드를 입력합니다.

파일 명은 마음대로 정하셔도 좋지만, 이 포스트에서는 wsl2_port_forward.ps1로 진행하겠습니다.
bash
Set-ExecutionPolicy RemoteSigned -Scope CurrentUser
.\wsl_port_forward.ps1

경고문이 뜰 텐데 ‘모두 허용’으로 하면 됩니다.


💡
만약 저장한 디렉토리에서 파워쉘을 관리자 권한으로 실행하는 법을 모른다면, 해당 코드파일이 있는 경로까지 cd 커맨드를 이용해 이동하면 됩니다.


작업 스케쥴러 설정

이 스크립트는 부팅 시 WSL IP가 바뀌는 것을 잡아내는 역할을 하므로, 부팅 할 때마다 동작해야 합니다.

따라서 작업 스케쥴러에서 이 코드가 부팅 후 15초 뒤(안정성 확보)에 실행되도록 설정해줍니다.

작업 추가를 누르고, 일반탭에서 다음과 같이 설정합니다.

이 때 윈도우 비밀번호를 타이핑 해야합니다.


이렇게 하는 이유는 당연하지만 원격 접속 시 윈도우 로그인을 못하기 때문입니다.


가장 높은 수준의 권한으로 실행 도 꼭 체크해주세요.

트리거 편집에서는 다음과 같이 설정합니다.


이렇게 하는 이유는 당연하지만 원격 접속 시 윈도우 로그인을 못하기 때문입니다.

  • 프로그램은 Powershell이므로 Powershell 경로를 입력합니다.
    대부분
    %SystemRoot%\system32\WindowsPowerShell\v1.0\powershell.exe 입니다.
  • 인수 추가는 다음과 같습니다.
    -NoProfile -ExecutionPolicy Bypass -File "{방금 만든 ps1 파일 경로}”



이렇게 설정하면 매번 딥러닝 서버(데스크탑)이 켜질 때마다, 자동으로 Windows 시스템이 SSH 접속을 위한 준비를 하게 됩니다.


다음 포스트에서는 이렇게 만들어진 WSL2 SSH 딥러닝 서버에 VSCode를 이용해 접속하는 방법을 알아보겠습니다🤗






최규민

최규민

AI Engineer

사파(邪派)식 AI-Native LLM SWE