핵심 요약
이 글은 같은 네트워크에서 project-dev-server 사용 준비를 마친 뒤, 집이나 사무실 밖의 외부 PC에서 project-dev-server에 SSH로 접속하기 위한 후속 절차입니다.
외부 접속에는 서버의 내부 IP 주소만으로는 부족합니다. 인터넷에서 공유기까지 찾아올 수 있는 공인 IP 주소 또는 DuckDNS 도메인이 필요하고, 공유기는 지정한 외부 포트를 서버의 SSH 포트로 전달해야 합니다. 서버에서는 SSH 공개 키 인증과 UFW 방화벽을 준비합니다.
외부 PC
-> example-project.duckdns.org:40222
-> 공유기의 공인 IPv4 주소:40222
-> 공유기 포트 전달
-> project-dev-server 192.168.1.231:22

이 글에서는 공유기의 외부 포트 40222/TCP를 서버의 192.168.1.231:22로 전달합니다. 외부 포트를 기본 SSH 포트인 22와 다르게 설정하면 무작위 스캔을 일부 줄일 수 있지만, 이것만으로 SSH가 안전해지는 것은 아닙니다.
이 글이 필요한 경우
- 외부 PC에서 집이나 사무실의
project-dev-server에 접속하려는 경우 - 인터넷 회선의 공인 IP 주소가 고정인지 유동인지 구분해야 하는 경우
- 유동 공인 IP 주소를 DuckDNS 도메인과 연결하려는 경우
- 공유기에서 SSH 포트 전달을 설정하려는 경우
- SSH 공개 키, Host Key 지문과 방화벽을 확인한 뒤 외부 접속을 시험하려는 경우
시작하기 전에
사용 환경의 명칭
| 명칭 | 의미 |
|---|---|
project-dev-server |
집이나 사무실 내부에 있는 Ubuntu 개발 서버 |
| Windows 관리 PC | 서버와 같은 네트워크에서 초기 설정과 복구에 사용하는 Windows PC |
| 외부 PC | 서버와 다른 네트워크에서 SSH로 접속할 Windows PC |
| 내부 IP 주소 | 공유기 내부에서 서버에 할당한 192.168.1.231 같은 사설 주소 |
| 공인 IP 주소 | 인터넷 서비스 제공자가 회선에 할당하고 인터넷에서 식별할 수 있는 주소 |
| 외부 SSH 포트 | 공유기가 인터넷에서 연결을 받을 예시 포트 40222 |
| 서버 SSH 포트 | project-dev-server의 OpenSSH가 수신하는 기본 포트 22 |
예시 구성 값
| 항목 | 예시 값 |
|---|---|
| 서버 사용자 이름 | apple2ne1 |
| 서버 내부 IP 주소 | 192.168.1.231 |
| DuckDNS 도메인 | example-project.duckdns.org |
| 외부 SSH 포트 | 40222/TCP |
| 서버 SSH 포트 | 22/TCP |
검증 환경
- 기준 서버: Ubuntu Server 26.04 LTS, 호스트 이름
project-dev-server - 기준 클라이언트: Windows 11 OpenSSH Client
- 서버 내부 IP 주소 예시:
192.168.1.231 - 서버 SSH 포트 예시:
22/TCP - 외부 SSH 포트 예시:
40222/TCP - DDNS 예시: DuckDNS
- 작성 기준일: 2026년 8월 10일
- 직접 수행 여부: 이 문서의 외부 회선, 공유기 포트 전달과 DuckDNS 갱신 절차는 실제 환경에서 수행하지 않음
—
1. 다른 네트워크에 있다는 의미
project-dev-server와 같은 공유기 또는 같은 내부망에 연결된 PC는 서버의 내부 IP 주소인 192.168.1.231로 직접 접속할 수 있습니다. 외부 PC는 이 주소로 직접 접속할 수 없습니다. 192.168.0.0/16, 10.0.0.0/8과 172.16.0.0/12 대역은 인터넷에서 직접 전달되지 않는 사설 주소이기 때문입니다.
외부 PC가 서버에 도달하려면 다음 조건이 모두 충족되어야 합니다.
- 서버가
192.168.1.231처럼 고정된 내부 IP 주소를 사용합니다. - 인터넷 회선에 외부에서 도달할 수 있는 고정 또는 유동 공인 IP 주소가 있습니다.
- 공인 IP 주소가 바뀐다면 DuckDNS가 현재 주소를 계속 가리키도록 갱신합니다.
- 공유기가 외부 SSH 포트를 서버의
22/TCP로 전달합니다. - 서버의 OpenSSH 서비스와 UFW가 해당 연결을 허용합니다.
- 외부 PC가 올바른 개인 키를 사용하고 서버 Host Key를 검증합니다.
2. 직접 SSH 공개와 VPN 중 선택하기
이 글은 공유기에서 포트 전달을 설정해 외부에서 SSH로 접속하는 방법을 설명합니다. 다만 설정 편의성과 공격 표면을 함께 고려하면 VPN이 더 적합할 수 있습니다.
| 방식 | 특징 |
|---|---|
| VPN | VPN에 인증된 장치만 내부 서버에 접속하도록 설정할 수 있습니다. 또한 공인 IP를 직접 사용할 수 없는 CGNAT 환경에서도 Tailscale 같은 VPN 서비스를 이용하면 외부에서 내부 서버에 접속할 수 있습니다. |
| SSH 직접 공개 | 별도 VPN 없이 접속할 수 있지만 공유기 포트, SSH 인증, 방화벽과 로그를 직접 관리해야 합니다. |
공인 IP를 사용할 수 없거나 공유기 설정을 바꿀 권한이 없다면, 포트 전달 대신 VPN을 사용하거나 인터넷 회사에 공인 IP 서비스를 신청하는 방법을 고려합니다.
3. 공인 IP 주소와 NAT 환경 확인하기
3-1. 공유기의 WAN IP 주소 확인
- Windows PC에서 공유기 관리 화면에 접속합니다.
웹 브라우저 주소창에 공유기 제조사가 제공하는 관리 주소를 입력합니다. 일반적으로 다음 주소 중 하나를 사용합니다.
192.168.0.1192.168.1.1
- 공유기 관리자 계정으로 로그인한 뒤 Internet, WAN, 인터넷 상태, Network Status 같은 메뉴를 찾습니다. 이 문서에서는
192.168.1.1로 접속합니다.

- 그 화면에서 WAN IPv4 Address, Internet IP Address 등의 항목에 표시된 IP를 기록합니다.
- Windows PC에서 Google 등에
what is my ip라고 검색하여 외부에 표시되는 공인 IPv4 주소를 확인합니다.
예: 76.90.XXX.XXX

예: 76.90.XXX.XXX
- 두 주소를 비교합니다.
- 두 IP가 같으면 → 공유기가 공인 IPv4를 직접 받고 있을 가능성이 높습니다.
- 두 IP가 다르면 → CGNAT 또는 상위 공유기가 하나 더 있는 환경일 가능성이 있습니다.
대표적으로 다음 주소는 인터넷에서 직접 접속할 공인 IPv4 주소가 아닙니다.
10.0.0.0/8172.16.0.0/12192.168.0.0/16100.64.0.0/10CGNAT 공유 주소 대역
3-2. 공인 IP가 고정인지 유동인지 확인
두 주소가 같아 공인 IP 주소를 확인했더라도 고정 주소인지 유동 주소인지는 별도로 확인해야 합니다. 계약서나 인터넷 서비스 제공자 관리 화면에서 고정 공인 IPv4 제공 여부를 확인하는 방법이 가장 확실합니다. 한 번 확인한 주소가 며칠 동안 같다는 사실만으로 고정 IP 주소라고 단정하지 않습니다.
4. 공인 IP가 고정일 때
고정 공인 IPv4 주소를 제공받는다면 해당 주소로 접속할 수 있습니다.
제공받은 고정 공인 IPv4 주소가 203.0.113.10이라면 다음과 같이 접속합니다.
ssh -p 40222 apple2ne1@203.0.113.10
203.0.113.10은 문서용 예시 주소입니다. 고정 IP 주소를 사용하더라도 공유기 포트 전달, 서버 내부 IP 고정, UFW와 SSH 인증 준비는 그대로 필요합니다.
5. 공인 IP가 유동일 때 DuckDNS 서비스 준비하기
DuckDNS는 집이나 사무실의 유동 공인 IP 주소가 바뀌어도 example-project.duckdns.org처럼 일정한 도메인으로 접속할 수 있게 해 주는 DDNS 서비스입니다. 외부 PC에서는 변경될 수 있는 숫자 IP 대신 다음과 같이 도메인을 사용합니다.
ssh -p 40222 apple2ne1@example-project.duckdns.org

공유기 또는 서버가 변경되는 유동 공인 IP 주소를 DuckDNS에 주기적으로 알려 주면, DuckDNS 도메인이 현재 공인 IP 주소를 계속 가리키게 됩니다.
작동 방식
- 설정 방식에 따라 항상 켜져 있는 공유기 또는 Ubuntu 서버가 일정한 간격으로 현재 공인 IP 주소와 DuckDNS
Token을 갱신 API에 전달합니다.Token은 DuckDNS가 요청자를 확인하기 위해 발행한 인증값입니다. - DuckDNS는
example-project.duckdns.org의 DNS 레코드를 현재 공인 IP 주소로 갱신합니다. - 외부 PC가
example-project.duckdns.org로 SSH 접속을 시작하면 DNS 조회를 통해 서버의 현재 공인 IP 주소를 확인합니다. - 외부 PC는 확인한 공인 IP 주소의 외부 SSH 포트인
40222/TCP로 접속합니다.
- 공유기는 미리 설정한 포트 전달 규칙에 따라 연결을
project-dev-server의192.168.1.231:22로 전달합니다.
정해진 시간마다 공유기 또는 서버에서 갱신 프로그램이 같은 요청을 보내므로 공인 IP 주소가 바뀌어도 example-project.duckdns.org가 새 주소를 가리킬 수 있습니다. DNS 캐시와 갱신 주기 때문에 변경 직후에는 이전 주소가 잠시 반환될 수 있습니다.
5-1. DuckDNS 도메인과 Token 생성 준비
- DuckDNS에 로그인합니다.

- 사용할 하위 도메인
example-project.duckdns.org를 등록합니다.

- 화면에 표시된
Token을 암호처럼 안전하게 보관합니다. - 도메인의 현재 IP 값을 확인합니다.
도메인: example-project
전체 주소: example-project.duckdns.org
Token 자리표시자 YOUR_DUCKDNS_TOKEN
Token 유출이 의심되면 상단 이메일 주소 오른쪽의 III 메뉴에서 재발행합니다.


5-2. 갱신 위치 선택
공유기가 DuckDNS 또는 사용자 지정 DDNS 갱신을 지원하면 공유기에서 갱신하도록 하는 편이 자연스럽습니다. 서버가 잠시 꺼져도 갱신을 계속할 수 있습니다. 공유기가 지원하지 않으면 항상 켜져 있는 project-dev-server에서 DuckDNS 갱신 요청을 실행하게 합니다.
5-3. 서버에 DuckDNS 갱신 스크립트 준비
command -v curl
systemctl is-active cron

command -v curl로 curl 명령이 설치되어 있고 실행 가능한지 확인합니다. 정상적으로 설치되어 있다면 일반적으로 curl의 설치 경로가 표시됩니다.
systemctl is-active cron으로 cron 서비스의 실행 상태를 확인합니다. 실행 중이면 active, 중지되었으면 inactive, 실행에 실패했으면 failed가 표시됩니다.
필요한 패키지가 없다면 설치합니다.
sudo apt update
sudo apt install curl cron
패키지 설치와 서비스 실행 상태를 확인한 뒤 스크립트를 만듭니다.
mkdir -p ~/.config/duckdns
chmod 700 ~/.config/duckdns
nano ~/.config/duckdns/update.sh

다음 내용을 입력하고 도메인과 Token 자리표시자만 실제 값으로 바꿉니다.
#!/usr/bin/env bash
set -eu
DOMAIN="example-project"
DUCKDNS_CREDENTIAL="YOUR_DUCKDNS_TOKEN"
curl --fail --silent --show-error \
"https://www.duckdns.org/update/${DOMAIN}/${DUCKDNS_CREDENTIAL}" \
--output "$HOME/.config/duckdns/update.log"

Nano 편집기에서 입력을 마치면 Ctrl+O, Enter, Ctrl+X를 순서대로 눌러 저장하고 종료합니다.
chmod 700 ~/.config/duckdns/update.sh
~/.config/duckdns/update.sh
cat ~/.config/duckdns/update.log

OK가 표시되면 DuckDNS가 요청을 처리한 것입니다. KO 또는 curl 오류가 나타나면 도메인, Token, DNS와 인터넷 연결을 확인합니다.
Token이 들어 있는 ~/.config/duckdns/update.sh는 프로젝트 폴더 밖에 있으므로 Git이 추적하지 않습니다. 따라서 프로젝트의 .gitignore에 추가할 필요는 없습니다. 대신 chmod 700 ~/.config/duckdns/update.sh로 파일 권한을 제한하여 다른 사용자가 읽지 못하게 합니다.
5-4. 5분마다 자동 갱신하기
crontab은 사용자별 예약 작업을 관리합니다. crontab -e를 실행하면 현재 로그인한 사용자의 기존 Crontab을 편집기에 불러오며, 아직 등록한 작업이 없으면 빈 Crontab을 만듭니다. /var/spool/cron/crontabs/ 아래의 파일을 직접 열어 수정하지 않습니다.
1단계: 사용자 Crontab 불러오기
crontab -e

처음 실행하면 사용할 편집기를 선택하는 화면이 나타날 수 있습니다. 목록에서 /bin/nano에 해당하는 번호를 입력하고 Enter를 누릅니다. 편집기 선택 화면이 나타나지 않거나 다른 편집기가 열리면 다음 명령으로 이번 작업에서 Nano를 지정할 수 있습니다.
EDITOR=nano crontab -e
이 명령에는 sudo를 붙이지 않습니다. project-dev-server에 로그인한 현재 사용자의 Crontab에 등록해야 $HOME이 해당 사용자의 홈 폴더를 가리킵니다. sudo crontab -e를 실행하면 root 사용자의 별도 Crontab이 열립니다.
2단계: 5분 주기 작업 입력
Nano에 기존 예약 작업이 표시되면 내용을 삭제하지 말고 커서를 이용하여 파일의 마지막 빈 줄로 이동하여 다음 한 줄을 복사하여 추가합니다.
*/5 * * * * "$HOME/.config/duckdns/update.sh" >/dev/null 2>&1

앞의 */5는 매시간 0분부터 5분 간격으로 실행한다는 뜻입니다. 뒤의 경로는 앞에서 만든 DuckDNS 갱신 스크립트이며, 표준 출력과 오류 출력은 화면에 표시하지 않습니다. 실행 결과는 스크립트가 $HOME/.config/duckdns/update.log에 기록합니다.
3단계: Nano에서 저장하고 종료
다음 순서로 입력합니다.
Ctrl+O를 눌러 저장을 시작합니다.- 화면 아래에 표시된 임시 파일 이름은 바꾸지 않고
Enter를 누릅니다. Ctrl+X를 눌러 Nano를 종료합니다.
편집기를 정상적으로 종료하면 Crontab이 설치되며 다음과 비슷한 메시지가 표시될 수 있습니다.
crontab: installing new crontab

저장하지 않고 종료하면 변경 내용이 등록되지 않습니다. 다시 crontab -e를 실행하면 현재 등록된 내용을 불러와 수정할 수 있습니다.
4단계: 저장된 예약 작업 확인
다음 명령은 현재 사용자의 Crontab을 읽기 전용으로 출력합니다.
crontab -l
출력에 다음 줄이 정확히 한 번 표시되는지 확인합니다.
*/5 * * * * "$HOME/.config/duckdns/update.sh" >/dev/null 2>&1

같은 줄이 여러 번 있으면 crontab -e로 다시 열어 중복된 줄만 제거하고 저장합니다. crontab -r은 현재 사용자의 Crontab 전체를 삭제하므로 이 작업에서는 사용하지 않습니다.
5단계: cron 서비스와 자동 실행 결과 확인
먼저 cron 서비스가 실행 중인지 확인합니다.
systemctl is-active cron

active가 표시되면 최대 5분 정도 기다린 뒤 로그 파일의 수정 시간과 내용을 확인합니다.
stat ~/.config/duckdns/update.log
cat ~/.config/duckdns/update.log

로그 파일의 수정 시간이 예약 작업을 저장한 뒤의 시간으로 바뀌고 내용이 OK이면 자동 갱신이 실행된 것입니다. 5분이 지나도 수정 시간이 바뀌지 않으면 다음 명령으로 스크립트를 직접 실행하여 먼저 스크립트 자체가 정상인지 확인합니다.
~/.config/duckdns/update.sh
cat ~/.config/duckdns/update.log
직접 실행은 성공하지만 자동 실행만 되지 않으면 crontab -l의 경로와 문법, systemctl is-active cron 결과를 다시 확인합니다.
5-5. 외부 PC에서 DNS 확인
Resolve-DnsName example-project.duckdns.org
같은 네트워크에서 실행하더라도 Resolve-DnsName은 example-project.duckdns.org의 공개 DNS 레코드를 조회합니다. 다만 실제 SSH 외부 접속은 공유기의 NAT Loopback 지원 여부에 영향을 받을 수 있으므로 다른 네트워크에서 별도로 확인합니다.

반환된 IPv4 주소가 현재 공인 IPv4 주소와 일치하는지 확인합니다. DNS가 맞아도 포트 전달이 없으면 SSH에는 접속할 수 없습니다.
6. project-dev-server 내부 IP 고정 확인
공유기의 포트 전달 대상은 192.168.1.231처럼 서버의 내부 IP 주소입니다. 이 주소가 DHCP로 바뀌면 규칙이 다른 장치를 가리키거나 연결이 끊깁니다.
hostname -I
ip -br address
ip route

공유기 관리 화면에서 project-dev-server의 네트워크 어댑터 MAC 주소에 192.168.1.231을 예약했는지 확인합니다. 가능하면 공유기의 DHCP Reservation, Address Reservation, Static Lease와 비슷한 기능을 우선 사용합니다.
Netplan 설정과 netplan try 복구 절차는 같은 네트워크에서 project-dev-server 사용 준비를 참조합니다.
7. UFW에서 외부 SSH 연결 허용
공유기는 외부 40222를 서버의 22로 바꾸므로 서버 UFW에서는 실제로 수신하는 22/TCP를 허용합니다.
같은 네트워크에서 project-dev-server 사용 준비에서는 192.168.1.0/24 네트워크만 SSH 접속을 허용했습니다. 외부 PC의 공인 IP는 바뀔 수 있으므로 이번에는 인터넷에서 공유기의 포트 전달을 거쳐 들어오는 SSH 연결도 허용합니다.
현재 규칙을 확인하고 22/TCP를 허용합니다.
sudo ufw status numbered
sudo ufw allow 22/tcp comment 'OpenSSH from external networks'
sudo ufw status numbered

기존 192.168.1.0/24 허용 규칙은 삭제하지 않습니다. 외부 PC에서 접속이 확인될 때까지 현재 SSH 연결도 유지합니다.
8. 공유기 포트 전달 설정
공유기 관리 화면에서 Port Forwarding, Virtual Server, NAT, 포트 전달과 비슷한 메뉴를 찾습니다.
| 설정 | 예시 값 |
|---|---|
| 규칙 이름 | project-dev-server-ssh |
| 프로토콜 | TCP |
| 외부 포트 | 40222 |
| 내부 IP 주소 | 192.168.1.231 |
| 내부 포트 | 22 |
| 활성화 | 사용 |
외부 포트 40222를 사용해도 서버의 OpenSSH 포트는 22로 유지할 수 있습니다. 공유기가 외부 40222/TCP 연결만 내부 192.168.1.231:22로 변환합니다.

외부 포트가 공유기 원격 관리 기능이나 다른 전달 규칙과 충돌하지 않는지도 확인합니다.
9. 외부 PC의 SSH 키와 서버 지문 준비
외부 PC에서 SSH 키를 준비하고, 서버 콘솔 또는 기존의 신뢰된 내부 연결에서 서버 Host Key 지문을 확인합니다.
9-1. 외부 PC의 키 확인
Get-ChildItem $env:USERPROFILE\.ssh
ssh-keygen -lf $env:USERPROFILE\.ssh\id_ed25519.pub

키가 없다면 새 Ed25519 키 쌍을 만듭니다.
ssh-keygen -t ed25519 -C "external-pc-project-dev-server"
개인 키를 보호할 Passphrase를 설정합니다. .pub 파일은 공개 키이고 확장자가 없는 파일은 개인 키입니다. 개인 키는 서버나 다른 사람에게 전달하지 않습니다.
9-2. 서버 Host Key 지문 확인
서버 콘솔 또는 이미 신뢰한 내부 연결에서 확인합니다.
sudo ssh-keygen -lf /etc/ssh/ssh_host_ed25519_key.pub
표시된 SHA256:... 값을 안전하게 기록합니다. 외부 PC가 처음 접속할 때 제시하는 지문과 비교합니다. 사용자 공개 키 인증과 서버 Host Key 확인의 차이는 SSH 공개 키 인증과 Host Key의 차이를 참조합니다.
10. 외부 SSH 연결로 공개 키 등록
외부 PC를 서버와 다른 네트워크에 연결하고 PowerShell에서 먼저 외부 포트를 확인합니다.
Test-NetConnection example-project.duckdns.org -Port 40222

TcpTestSucceeded : True이면 암호화된 SSH 연결을 통해 공개 키를 등록합니다.
외부 Windows PC에서 authorized_keys에 넣기
서버가 유동 공인 IP 주소를 사용하면 DuckDNS에서 만든 example-project.duckdns.org로 접속합니다.
Get-Content $env:USERPROFILE\.ssh\id_ed25519.pub | ssh -p 40222 apple2ne1@example-project.duckdns.org "umask 077; mkdir -p ~/.ssh; cat >> ~/.ssh/authorized_keys; chmod 700 ~/.ssh; chmod 600 ~/.ssh/authorized_keys"

등록 후 접속을 확인합니다.
ssh -p 40222 apple2ne1@example-project.duckdns.org

접속을 종료합니다.
exit
고정 공인 IP 주소를 사용한다면 명령의 example-project.duckdns.org를 해당 IP 주소로 바꿀 수 있습니다.
최초 등록 연결에서는 9-2에서 기록한 서버 Host Key 지문을 비교하고 현재 구축한 서버의 계정 암호로 인증합니다. 공개 키는 암호화된 SSH 연결 안에서 전달되며 개인 키는 전송되지 않습니다. 등록이 끝나면 외부 Windows PC의 새 PowerShell 창에서 공개 키 인증을 확인합니다.
ssh -i $env:USERPROFILE\.ssh\id_ed25519 -p 40222 apple2ne1@example-project.duckdns.org

접속을 종료합니다.
exit
현재 구축한 서버의 계정 암호를 묻지 않고 접속되면 공개 키 인증이 동작한 것입니다. 키 Passphrase를 설정했다면 개인 키의 Passphrase는 요구할 수 있습니다.
11. SSH 서버 보안 설정 확인
공개 키 인증이 실제로 성공한 뒤에만 암호 인증 제한을 검토합니다. 설정을 바꾸는 동안 현재 SSH 연결과 서버 콘솔을 유지합니다.
sudo sshd -T | grep -E '^(port|pubkeyauthentication|passwordauthentication|kbdinteractiveauthentication|permitrootlogin) '
port 22→ SSH는 22번 포트를 사용permitrootlogin prohibit-password→ root는 암호 로그인이 금지되지만 SSH 키 로그인은 허용될 수 있음pubkeyauthentication yes→ SSH 키 로그인 허용passwordauthentication yes→ 암호 로그인도 아직 허용kbdinteractiveauthentication yes→ 키보드 대화형 인증도 아직 허용
passwordauthentication 또는 kbdinteractiveauthentication이 yes이면 공개 키 이외의 인증 방식이 아직 허용된 상태입니다. 실제 출력값을 확인한 뒤 다음 설정을 적용합니다.
SSH 접속 설정을 위해 아래 사항을 먼저 확인합니다.
sudo ls -ld /etc/ssh/sshd_config.d
sudo ls -la /etc/ssh/sshd_config.d
grep -n '^Include' /etc/ssh/sshd_config

Ubuntu 기본 구성에는 보통 다음 줄이 있습니다.
Include /etc/ssh/sshd_config.d/*.conf
위와 같이 sshd_config.d 폴더와 Include 설정이 존재하면 새 설정 파일을 만듭니다. 이 파일은 Ubuntu가 기본으로 제공하는 파일이 아니라 사용자가 새로 만드는 설정 파일입니다.
sudo nano /etc/ssh/sshd_config.d/10-project-dev-server.conf
다음 설정을 적용하면 root 계정의 직접 로그인, 암호 인증과 키보드 대화형 인증을 사용할 수 없으므로 공개 키 접속과 복구 수단을 먼저 확인합니다.
PubkeyAuthentication yes
PasswordAuthentication no
KbdInteractiveAuthentication no
PermitRootLogin no

PubkeyAuthentication yesPasswordAuthentication noKbdInteractiveAuthentication noPermitRootLogin no
SSH 접속에 공개 키 인증을 허용합니다.
암호로 SSH 로그인하는 것을 막습니다.
키보드 대화형 인증을 막습니다.
root 계정으로 직접 SSH 로그인하는 것을 막습니다.
저장한 뒤에는 다음 순서로 검사합니다.
sudo sshd -t
sudo systemctl reload ssh
systemctl is-active ssh

sudo sshd -tsudo systemctl reload sshsystemctl is-active ssh
SSH 설정 파일에 문법 오류가 있는지 검사합니다. 아무 메시지도 나오지 않으면 보통 정상입니다.
SSH 서비스를 끄지 않고 변경된 설정을 다시 불러옵니다.
SSH 서비스가 현재 실행 중인지 확인합니다. active가 나오면 정상입니다.
서비스가 active 상태여도 원하는 인증 정책이 적용되었다는 뜻은 아니므로 다음 명령으로 최종 적용값을 확인합니다.
sudo sshd -T | grep -E '^(port|pubkeyauthentication|passwordauthentication|kbdinteractiveauthentication|permitrootlogin)'

새 PowerShell 창에서 외부 공개 키 접속이 다시 성공하는지 확인하기 전에는 기존 연결을 닫지 않습니다.
새 PowerShell 창에서 다음 두 검사를 구분해 확인합니다.
ssh -i "$env:USERPROFILE\.ssh\id_ed25519" -p 40222 apple2ne1@example-project.duckdns.org

ssh -o PubkeyAuthentication=no -o PreferredAuthentications=password,keyboard-interactive -p 40222 apple2ne1@example-project.duckdns.org

첫 번째 명령은 개인 키를 직접 지정하므로 공개 키 인증으로 성공해야 합니다. 두 번째 명령은 공개 키 인증을 끄고 암호 또는 키보드 대화형 인증만 시도하므로 접속에 실패해야 합니다. 이 단계에서는 아직 외부용 SSH 별칭을 만들지 않았으므로 도메인과 포트를 직접 지정합니다.
12. 외부 PC에서 별칭으로 간편하게 SSH 접속하기
notepad $env:USERPROFILE\.ssh\config

Host project-dev-server-remote
HostName example-project.duckdns.org
User apple2ne1
Port 40222
IdentityFile ~/.ssh/id_ed25519
IdentitiesOnly yes

고정 공인 IP를 사용한다면 HostName에 실제 공인 IP 주소를 입력합니다. 내부 접속 별칭 project-dev-server가 이미 있다면 덮어쓰지 않고 외부용 별칭을 별도로 만듭니다.
ssh -G project-dev-server-remote | Select-String 'hostname|user|port|identityfile'

VS Code로 접속하려면 다음 화면 순서대로 진행합니다.






13. 외부 네트워크에서 시험하기
같은 공유기 안에서는 NAT Loopback 지원 여부에 따라 공인 주소 접속이 실패하거나 내부로 우회될 수 있습니다. 휴대전화 테더링처럼 서버와 다른 네트워크를 사용합니다.
Test-NetConnection example-project.duckdns.org -Port 40222
ssh project-dev-server-remote

처음 접속하면 외부 주소에 대한 서버 Host Key 확인 메시지가 나타납니다. 화면에 표시된 SHA256:... 지문을 서버 콘솔로 확인한 값과 비교하고 정확히 일치할 때만 yes를 입력합니다.
접속 후 로그인 기록을 확인합니다.
sudo journalctl -u ssh --since "15 minutes ago" --no-pager

14. 서버 IP 주소 또는 Host Key가 바뀌었을 때
REMOTE HOST IDENTIFICATION HAS CHANGED 경고는 이전에 저장한 Host Key와 현재 서버가 제시한 Host Key가 다를 때 나타납니다. 암호 오류나 단순한 포트 연결 실패와는 다른 서버 신원 확인 문제입니다.
현재 Host Key 지문을 안전한 경로로 확인하고 경고가 발생한 원인을 판정한 뒤에만 기존 기록을 갱신합니다. 자세한 확인 및 복구 절차는 REMOTE HOST IDENTIFICATION HAS CHANGED 경고 이해와 해결을 참조합니다.
완료 확인표
- [x] 서버 내부 IP 주소가 DHCP 예약 또는 검증된 Netplan 설정으로 고정되어 있다.
- [x] 공유기 WAN 주소와 외부에서 보이는 공인 IPv4 주소를 비교했다.
- [x] CGNAT 또는 이중 NAT 여부를 확인했다.
- [x] 유동 공인 IP라면 DuckDNS 갱신 결과가
OK이고 DNS 주소가 현재 공인 IP를 가리킨다. - [x] DuckDNS
Token과 개인 키가 문서, 로그와 Git 저장소에 노출되지 않았다. - [x] 외부 PC의 공개 키가 암호화된 SSH 연결을 통해 서버 사용자의
authorized_keys에 등록되어 있다. - [x] 외부 PC에서 공개 키 인증이 성공한 뒤 암호 인증과 키보드 대화형 인증을 비활성화했다.
- [x] 서버 ED25519 Host Key 지문을 별도 경로로 확인했다.
- [x] 공유기는 단일 외부 TCP 포트만
192.168.1.231:22로 전달한다. - [x] UFW가 서버의
22/TCP를 허용하고 불필요한 포트를 열지 않았다. - [x] 외부 PC에서
Test-NetConnection과 SSH 접속이 성공했다. - [x] 외부 PC가 제시받은 Host Key 지문이 서버 콘솔의 값과 일치했다.
자주 발생하는 문제
DuckDNS 주소가 현재 공인 IP를 가리키지 않는다
cat ~/.config/duckdns/update.log
stat ~/.config/duckdns/update.log
crontab -l
systemctl is-active cron
KO가 표시되면 도메인과 Token을 다시 확인합니다. Token 자체는 화면이나 지원 요청에 붙여 넣지 않습니다.
TcpTestSucceeded : False가 표시된다
systemctl is-active ssh로 SSH 서비스를 확인합니다.- 같은 네트워크에서 내부 IP SSH 접속을 확인합니다.
- 서버 내부 IP가 포트 전달 대상과 같은지 확인합니다.
- 공유기 규칙의 프로토콜, 외부 포트, 내부 포트와 활성 상태를 확인합니다.
- UFW가
22/TCP를 허용하는지 확인합니다. - CGNAT나 이중 NAT인지 확인합니다.
- 실제 외부 네트워크에서 다시 시험합니다.
Connection refused가 표시된다
systemctl is-active ssh
sudo ss -lntp | grep ':22'
Permission denied (publickey)가 표시된다
ls -ld ~/.ssh
ls -l ~/.ssh/authorized_keys
sudo journalctl -u ssh --since "15 minutes ago" --no-pager
ssh -vv project-dev-server-remote
상세 로그에는 사용자 이름, 호스트 이름과 경로가 포함될 수 있으므로 그대로 공개하지 않습니다.
REMOTE HOST IDENTIFICATION HAS CHANGED가 표시된다
경고를 무시하거나 기존 키를 즉시 삭제하지 않습니다. 서버 콘솔에서 Host Key 지문을 다시 비교합니다. 안전한 복구 절차는 REMOTE HOST IDENTIFICATION HAS CHANGED 해결 방법을 참조합니다.
내부에서는 되지만 외부에서는 접속되지 않는다
공인 IP, CGNAT, 이중 NAT, DuckDNS, 공유기 포트 전달과 인터넷 서비스 제공자의 인바운드 포트 제한을 확인합니다.
같은 네트워크에서 DuckDNS 주소로 접속되지 않는다
공유기가 NAT Loopback을 지원하지 않을 수 있습니다. 내부에서는 내부 IP 또는 내부용 SSH 별칭을 사용하고 외부 연결은 실제 다른 네트워크에서 검증합니다.
운영과 보안 점검
sudo apt update
apt list --upgradable
sudo journalctl -u ssh --since today --no-pager
반복되는 실패가 많으면 공개 범위 축소, VPN 전환, 출발지 IP 제한과 추가 인증을 검토합니다. 외부 SSH가 더 이상 필요하지 않으면 공유기의 포트 전달 규칙을 비활성화하거나 삭제합니다.
주의 사항과 한계
- 공유기 UI와 메뉴 이름은 제조사와 펌웨어에 따라 다릅니다.
- DuckDNS는 바뀌는 공인 IP와 도메인을 연결할 뿐, CGNAT를 우회하거나 공유기 포트를 자동으로 열지 않습니다.
- IPv6로 직접 공개할 때는 IPv4 NAT 포트 전달과 다른 방화벽 구성이 필요합니다. 이 글은 IPv4 기준입니다.
- 인터넷 서비스 제공자의 인바운드 포트와 서버 운영 정책을 확인합니다.
- 개발 웹 서버의 포트를 그대로 인터넷에 공개하지 않습니다. 필요한 서비스는 인증, TLS와 리버스 프록시를 별도로 설계합니다.
- 서버 콘솔 또는 같은 네트워크의 복구 수단을 유지합니다.
다음 단계 안내
외부 SSH 접속을 확인한 뒤 VS Code를 사용하려면 Windows VS Code에서 Linux 서버 Remote – SSH 연결하기를 참조합니다.
작성 및 검증 정보
- 작성자: apple2ne1
- 검토자: apple2ne1
- 직접 수행 여부: 미수행
- 마지막 검토일: 2026년 8월 19일
- 검증 방법: DuckDNS API 명세와 Linux
cron안내, Ubuntu OpenSSH·UFW 문서 및 Microsoft Windows OpenSSH 키 관리 문서 대조 - 추가 확인 항목: 공유기 모델별 UI, 회선의 공인 IP·CGNAT 여부, DuckDNS 자동 갱신, 외부 PC 접속과 Host Key 지문
참고 자료 및 출처
- DuckDNS HTTP API Specification — 도메인,
Token, IP 매개변수와 HTTPS 갱신 형식 확인 — 2026-08-10 - DuckDNS Install — Linux
cron,curl, 5분 갱신과 결과 로그 확인 방법 확인 — 2026-08-10 - Ubuntu Manpage: @@INLINE_0@@ — 사용자 Crontab 불러오기, 편집, 저장 및 조회 동작 확인 — 2026-08-18
- Ubuntu Server: OpenSSH server — OpenSSH 설정 조각,
sshd -t, Ed25519 키와authorized_keys확인 — 2026-08-10 - Ubuntu Server: Firewall — UFW 활성화, 포트 및 출발지 주소 허용과 상태 확인 방법 확인 — 2026-08-10
- Microsoft Learn: Key-Based Authentication in OpenSSH for Windows — Windows OpenSSH 키 생성과 개인 키 보호 원칙 확인 — 2026-08-10
관련 문서
- prepare-project-dev-server-on-local-network-ko
- windows-vscode-linux-remote-ssh-setup-ko
- ssh-public-key-and-host-key-ko
- ssh-port-basics-ko
- ssh-remote-host-identification-changed-ko
- winscp-install-and-use-ko

