EQVPS
대시보드시작하기

VPS에서 열린 포트 확인하는 법: 무엇이 대기 중인지, 인터넷에서 무엇이 보이는지, 그리고 Docker의 함정

서버가 대기 중인 모든 포트를 확인하고, 그 뒤의 프로세스를 찾고, 공격자처럼 외부에서 서버를 스캔한 뒤 필요 없는 것을 닫습니다. 방화벽을 켜도 열려 있는 Docker 포트도 다룹니다. 모든 수치는 새 Ubuntu 24.04 서버에서 측정했습니다.

열어 두고 잊어버린 포트는 서버에 침입하는 고전적인 경로입니다. 공개 주소에 있는 데이터베이스, 디버그 대시보드, 아무도 원하지 않은 포트를 게시한 컨테이너. 명령 두 개와 함정 하나만 알면 확인은 1분이면 끝납니다. 아래 내용은 모두 당사 이미지로 만든 새 Ubuntu 24.04 서버에서 실행하고 두 번째 머신에서 스캔한 것이므로, 출력은 여러분의 서버에서 보게 될 것과 같습니다.

1. 무엇이 대기 중인가

명령은 ss입니다. 옵션 글자는 TCP, UDP, 대기 중인 소켓, 숫자 포트, 프로세스 이름을 뜻합니다.

sudo ss -tulpn

새 서버에서는 6줄이 출력됩니다.

Netid State  Local Address:Port  Process
udp   UNCONN    127.0.0.54:53    systemd-resolve
udp   UNCONN 127.0.0.53%lo:53    systemd-resolve
tcp   LISTEN    127.0.0.54:53    systemd-resolve
tcp   LISTEN 127.0.0.53%lo:53    systemd-resolve
tcp   LISTEN       0.0.0.0:22    sshd
tcp   LISTEN          [::]:22    sshd

중요한 열은 Local Address입니다. 누가 접속할 수 있는지 알려 줍니다.

주소접근 가능한 대상
127.0.0.1:… 또는 127.0.0.53이 서버의 프로그램만
0.0.0.0:…방화벽이 막지 않으면 IPv4로 누구나
[::]:…IPv6로 누구나(보통 IPv4도)
10.0.0.5:…(특정 주소)그 주소에 닿을 수 있는 누구나

즉 새 서버가 외부에 노출하는 것은 IPv4와 IPv6의 포트 22 SSH 하나뿐입니다. 127.0.0.53의 DNS 리졸버는 비공개입니다.

인터넷에서 보일 수 있는 것만 나열하려면 루프백 주소를 걸러 냅니다.

sudo ss -tlnp | awk 'NR>1 && $4 !~ /^(127\.|\[::1\])/ {print $4, $6}'

설치할 때마다 실행하세요. nginx, Docker, 작은 Python 앱을 설치했더니 목록이 2줄에서 10줄로 늘었고, 그중에는 0.0.0.0:8081의 docker-proxy와 임의 localhost 포트의 containerd 소켓이 있었습니다.

2. 어떤 프로세스가 포트를 쓰고 있나

포트가 사용 중이고 누가 쓰는지 알고 싶을 때:

sudo lsof -i :80 -P -n
sudo fuser 80/tcp
sudo ss -tlnp 'sport = :80'

가장 읽기 쉬운 것은 lsof입니다. nginx의 경우 root인 마스터 프로세스와 www-data인 워커가 IPv4와 IPv6 모두 *:80에 표시되었습니다. fuser는 PID만 출력해 스크립트에 편합니다. 포트를 비워야 한다면 PID를 kill하지 말고 서비스를 멈추세요. 그렇지 않으면 systemd가 다시 시작합니다: sudo systemctl stop nginx.

3. 인터넷에서는 무엇이 보이나

ss는 서버의 관점을 보여 줍니다. 인터넷의 관점은 다를 수 있습니다. 중간에 방화벽이 있거나 서비스가 127.0.0.1에 바인딩되어 있기 때문입니다. 실제 모습을 보려면 서버를 다른 머신에서 스캔하세요. 노트북이나 두 번째 서버면 됩니다. 도구는 nmap입니다(apt install nmap, Windows와 macOS는 nmap.org의 설치 프로그램).

nmap your-server-ip
nmap -p- your-server-ip
nmap -p 22,80,443,3306 your-server-ip

첫 번째 형식은 가장 흔한 1,000개 포트, 두 번째는 65,535개 전체, 세 번째는 지정한 목록을 검사합니다. 방화벽을 켜기 전 새 서버의 결과입니다.

Not shown: 999 closed tcp ports (reset)
PORT   STATE SERVICE
22/tcp open  ssh
Nmap done: 1 IP address (1 host up) scanned in 6.83 seconds

65,535개 포트 전체 스캔은 8.9초 걸렸고 같은 하나의 포트를 찾았습니다. 닫힌 포트는 즉시 리셋으로 응답하므로 스캔이 빠릅니다.

중요한 상태는 세 가지입니다.

  • open: 프로그램이 연결을 받아들였습니다. 이것이 공격 표면입니다.
  • closed: 그곳에서 아무것도 대기하지 않는다고 서버가 응답했습니다. 안전하지만 서버가 존재하며 무엇을 실행하지 않는지 드러납니다.
  • filtered: 응답이 없습니다. 방화벽이 패킷을 버렸습니다. 스캐너는 그 뒤에 서비스가 있는지 알 수 없습니다.

아무것도 설치하지 않고 포트 하나를 확인하려면 nc -zv 서버IP 443이 succeeded 또는 refused를 반환하고, 필터된 포트에서는 타임아웃까지 기다립니다. UDP는 nmap에 -sU를 붙이되 오래 걸린다고 각오하세요. 당사 서버에서는 흔한 UDP 포트 53, 123, 161이 모두 closed였습니다.

NAT 요금제에 대한 참고: 인터넷에서는 당사가 전달해 드린 SSH 포트에만 닿습니다. 공유 주소를 스캔하면 여러분의 서버가 아니라 당사 네트워크의 경계가 보이며, 다른 포트는 무엇이 대기하든 접근할 수 없습니다. 전용 IPv4 주소가 있는 요금제에서는 열린 포트를 모두 직접 관리합니다.

4. 필요 없는 것은 닫기

가장 좋은 방법부터 가장 넓은 방법까지 세 가지입니다.

localhost에 바인딩하기. 서버 안의 프로그램만 쓰는 서비스라면 127.0.0.1에서 대기하게 하세요. --bind 127.0.0.1 8080으로 작은 웹 앱을 실행했더니 ss에는 127.0.0.1:8080으로 나왔고, 외부의 nmap은 8080/tcp closed를 보고했습니다. 데이터베이스에는 이것이 올바른 기본값입니다. Ubuntu의 Redis, MySQL, PostgreSQL은 설치 직후부터 localhost에서 대기하며, 당사의 Redis와 MySQL 가이드도 그대로 둡니다.

서비스 멈추기. sudo systemctl disable --now 이름은 대기 중인 리스너를 완전히 없앱니다. ss로 사라졌는지 확인하세요.

방화벽 켜기. 먼저 SSH를, 그다음 제공하는 서비스를 허용한 뒤 활성화합니다.

sudo ufw allow OpenSSH
sudo ufw allow 80/tcp
sudo ufw enable
sudo ufw status verbose

외부에서 본 스캔 결과가 완전히 달라졌습니다.

Not shown: 997 filtered tcp ports (no-response)
PORT     STATE SERVICE
22/tcp   open  ssh
80/tcp   open  http
8081/tcp open  blackice-icecap
Nmap done: 1 IP address (1 host up) scanned in 10.18 seconds

허용하지 않은 포트는 모두 closed에서 filtered로 바뀌었고, 65,535개 포트 전체 스캔은 8.9초에서 110초로 늘었습니다. 스캐너는 포트마다 더 오래 걸리고, 서버에 대한 정보는 줄어듭니다. 당사의 UFW 가이드에서 속도 제한, 특정 IP만 허용, IPv6를 다룹니다.

하지만 세 번째 줄을 보세요.

5. Docker의 함정

방화벽이 켜져 있고 허용하는 규칙도 없는데 포트 8081은 열려 있었습니다. 이 포트는 docker run -p 8081:80 nginx:alpine으로 실행한 컨테이너의 것입니다. Docker는 커널 방화벽에 자체 규칙을 추가하고, 이 규칙은 ufw 규칙보다 먼저 평가됩니다. 당사 서버에 기록된 규칙입니다.

-A DOCKER ! -i docker0 -p tcp --dport 8081 -j DNAT --to-destination 172.17.0.2:80

포트 8081로 오는 패킷은 ufw가 판단하기도 전에 모두 컨테이너로 전달됩니다. -p나 ports: 항목이 있는 모든 컨테이너가 이렇게 하며, ss는 이를 정직하게 0.0.0.0:8081의 docker-proxy로 보여 줍니다.

해결책은 localhost에만 게시하는 것입니다.

docker rm -f web
docker run -d --name web -p 127.0.0.1:8081:80 nginx:alpine

Compose에서는 ports:에 "127.0.0.1:8081:80"이라고 씁니다. 변경 후 ss는 127.0.0.1:8081을 보여 주었고, Docker 규칙에는 -d 127.0.0.1/32가 붙었으며, 외부 스캔은 8081/tcp filtered를 보고했습니다. 공개 쪽에는 nginx를 리버스 프록시로 HTTPS와 함께 앞에 두어, 서버에서 열린 포트가 22, 80, 443뿐이 되게 하세요.

변경할 때마다 확인할 것

  1. sudo ss -tulpn: 0.0.0.0이나 [::]가 있는 줄은 모두 공개하려던 것인가요?
  2. 노트북에서 nmap 서버IP: 열린 포트가 일치하나요?
  3. 공개 주소에 있는 데이터베이스, 캐시, 관리 패널: 오늘 바로 127.0.0.1이나 방화벽 안쪽으로 옮기세요.
  4. Docker의 각 ports: 항목: 공개 웹 포트가 아니라면 127.0.0.1:로 시작하나요?
  5. SSH가 모두에게 열려 있다면: fail2ban이나 키 전용 로그인을 추가하세요.

Windows Server에서 같은 확인은 Get-NetTCPConnection -State Listen이며, 규칙은 Windows 방화벽에 있습니다. $3 Nano부터 모든 Linux 요금제에는 전용 IPv4 주소가 있는 버전이 있으며, 그곳에서는 ss에 보이는 것이 방화벽이 막는 것을 빼고 그대로 전 세계에 보입니다.

자주 묻는 질문

새 VPS에는 어떤 포트가 열려 있나요?

당사 Ubuntu 24.04 이미지에서는 하나뿐입니다. 포트 22의 SSH입니다. 그 밖에는 127.0.0.53과 127.0.0.54의 로컬 DNS 리졸버만 대기하며, 인터넷에서는 닿지 않습니다. 다른 머신에서 TCP 포트 65,535개를 모두 스캔했더니 열린 포트는 정확히 하나였습니다.

closed와 filtered는 무엇이 다른가요?

closed는 서버가 그곳에서 아무것도 대기하지 않는다고 응답한 상태라 스캐너가 즉시 압니다. filtered는 아무 응답도 없는 상태로, 패킷을 버리는 방화벽이 이렇게 동작합니다. 둘 다 침입자를 막지만, filtered는 어떤 서비스가 있는지도 숨기고 당사 서버에서 전체 스캔을 약 12배 느리게 만들었습니다.

ss에는 보이는데 nmap에서는 closed로 나오는 이유는?

그 서비스는 루프백 주소인 127.0.0.1에서만 대기합니다. 서버 안의 프로그램은 접속할 수 있지만 외부에서는 아무도 못 합니다. ss에서 Local Address 열을 보세요. 127.0.0.1:8080은 비공개이고, 0.0.0.0:8080이나 [::]:8080은 방화벽이 허용하면 인터넷에서 접근할 수 있습니다.

ufw를 켰는데 Docker 포트가 아직 열려 있는 이유는?

Docker는 자체 방화벽 규칙을 작성하고, 이 규칙은 ufw 규칙보다 먼저 적용됩니다. -p 8081:80으로 실행한 컨테이너는 당사 서버에서 ufw가 켜져 있고 8081 규칙이 없는데도 외부에서 접근할 수 있었습니다. 대신 localhost에 게시하고(-p 127.0.0.1:8081:80) 공개 쪽에는 nginx나 Caddy를 앞에 두세요.

내 컴퓨터에서 포트를 확인하려면?

포트 하나는 nc -zv 서버IP 443, 가장 흔한 1000개 포트는 nmap을 설치해 nmap 서버IP를 실행합니다. NAT 요금제에서는 인터넷에서 전달된 SSH 포트만 응답하고 나머지는 설계상 접근할 수 없습니다.

댓글

아직 댓글이 없습니다. 첫 번째가 되세요.

댓글 남기기

댓글은 표시되기 전에 검토됩니다.