SSD Nodes Learn Hosting plans →
가이드 Matt Connor작성자 Matt Connor · 업데이트됨 2026-08-27

UFW IPv6 설정 확인 및 VPS 보안 공백 해결 방법

UFW를 사용해도 IPv6를 통한 서비스 노출 위험이 존재합니다. Ubuntu VPS 환경에서 ss -tulpn 명령어로 IPv6 리스닝 상태를 확인하고, 방화벽이 IPv4와 IPv6 양쪽 스택을 모두 보호하도록 설정하는 구체적인 방법을 안내합니다.

IPv6 방화벽의 함정

방화벽은 IPv4만 보호할 가능성이 높습니다. VPS는 거의 예외 없이 공인 IPv6 주소를 할당받으며, 많은 서비스가 기본적으로 이 주소에서 수신 대기합니다. 방화벽이 IPv4만 차단하거나 IPv4 전용 클라우드 방화벽에 의존한다면, IPv4 쪽은 안전해 보여도 IPv6를 통해 모든 서비스가 인터넷에 노출됩니다. curl으로 포트를 테스트하여 연결 거부 응답을 확인하고 안심할 수 있지만, 공격자는 IPv6로 동일한 포트에 접근하여 침입할 수 있습니다.

이 가이드는 일반적인 Ubuntu 24.04 VPS에서 이러한 보안 공백이 발생하는 원인과 노출된 항목을 확인하고 차단하는 방법을 설명합니다. UFW 자체가 문제는 아닙니다. 최신 Ubuntu 설치 환경에서 UFW는 이미 IPv6를 처리합니다. 보안 공백은 그 주변 계층과 사용자가 인지하지 못한 채 수신 대기 중인 서비스에서 발생합니다.

VPS가 기본적으로 IPv6를 사용하는 이유

오늘날 거의 모든 VPS는 IPv4 주소와 함께 공인 IPv6 주소를 제공하며, 종종 /64 전체를 할당하기도 합니다. 다음 명령어로 확인해 보십시오.

ip -6 addr show scope global
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> ...
    inet6 2001:db8:2a::1/64 scope global

해당 2001:db8:2a::1는 IPv4 주소와 마찬가지로 인터넷 어디에서나 라우팅이 가능합니다. 이제 어떤 서비스가 리스닝 중인지 확인해 보십시오.

sudo ss -tlnp
State   Recv-Q  Local Address:Port   Process
LISTEN  0       0.0.0.0:22           sshd
LISTEN  0       [::]:22              sshd
LISTEN  0       127.0.0.1:5432       postgres
LISTEN  0       [::]:8080            docker-proxy

Local Address 열을 주의 깊게 살펴보십시오. 0.0.0.0:22은 "모든 IPv4 주소에서 리스닝"을 의미합니다. [::]:22은 "모든 IPv6 주소에서 리스닝"을 의미합니다. 127.0.0.1:5432는 루프백에 바인딩되어 있어 외부로 공개되지 않으므로 Postgres 라인은 안전합니다. 두 개의 [::] 라인은 IPv6를 통해 인터넷 전체에 응답하고 있으며, docker-proxy은 실행 중인 사실조차 잊기 쉬운 서비스입니다.

대부분의 데몬은 기본적으로 ::에 바인딩됩니다. Linux에서 :: 소켓은 일반적으로 IPv4 연결도 함께 수락하기 때문입니다. 따라서 새로 설치된 서버의 기본 상태는 "모든 곳에서 양쪽 스택 모두 응답"하는 것입니다. 방화벽만이 이 앞에 서 있는 유일한 방어선이므로, 한쪽 스택만 감시하는 방화벽은 심각한 문제를 야기할 수 있습니다.

IPv6 격차가 발생하는 실제 원인

흔히 발생하는 원인은 네 가지입니다. 특정 서버에서 이 중 하나가 나타날 수도 있고, 여러 개가 동시에 나타날 수도 있습니다.

1. IPv4만 필터링하는 클라우드 방화벽. 많은 제공업체의 방화벽과 보안 그룹 제품은 IPv4를 중심으로 설계되었습니다. 따라서 IPv6를 무시하거나, 사용자가 직접 별도의 IPv6 규칙을 추가해야 합니다. 제공업체 대시보드의 방화벽이 유일한 방화벽인데 IPv6를 지원하지 않는다면, IPv4의 포트 22에 대해 어떤 설정이 되어 있든 관계없이 귀하의 [::] 서비스는 외부에 노출됩니다. 제공업체의 방화벽 문서를 읽고 IPv6 관련 항목이 있는지 확인하십시오.

2. ip6tables가 없는 수동 iptables 설정. iptables 명령은 IPv4 테이블에만 영향을 미칩니다. IPv6는 별도의 명령인 ip6tables을 사용하며, 고유한 규칙을 가집니다. iptables -A INPUT ... 줄로 가득 찬 방화벽 스크립트를 작성하면서 그에 대응하는 ip6tables 규칙을 작성하지 않았다면, IPv6 방화벽은 비어 있는 상태입니다. 기본 정책이 ACCEPT인 빈 INPUT 체인은 모든 트래픽을 허용합니다.

sudo ip6tables -L INPUT -n
Chain INPUT (policy ACCEPT)
target     prot opt source               destination

이 출력 결과가 함정의 핵심을 보여줍니다. IPv4는 필터링되지만, IPv6는 모든 외부 접근을 허용하고 있습니다.

3. 방화벽을 우회하여 포트를 게시하는 Docker. docker run -p 8080:80를 실행하면 Docker는 UFW보다 앞선 위치에 자체 규칙을 삽입합니다. 따라서 ufw status에서 해당 포트를 거부하도록 설정했더라도 외부에서 접근할 수 있으며, 최신 Docker 버전에서는 IPv6도 동일하게 적용됩니다. Docker가 UFW를 우회하는 이유와 컨테이너 포트를 올바르게 필터링하는 방법에서 메커니즘과 해결책을 설명합니다. 이러한 게시 포트가 어떻게 선언되는지는 VPS에서 Docker Compose 사용하기 기초를 참조하십시오.

4. IPv6가 비활성화된 UFW. UFW는 IPv6를 처리할 수 있지만, 설정이 되어 있어야만 작동합니다. 다음 스위치를 확인하십시오.

grep IPV6 /etc/default/ufw

최신 Ubuntu는 IPV6=yes을 기본으로 제공하므로 UFW는 모든 규칙을 두 스택(IPv4, IPv6)에 모두 적용합니다. 만약 오래된 이미지나 가이드를 따라 IPV6=no로 설정되어 있다면, 작성한 모든 UFW 규칙은 IPv4에만 적용되며 IPv6는 관리되지 않는 상태로 남습니다.

노출 중인 항목을 정확히 확인하기

추측하지 마십시오. 외부에서 직접 측정해야 합니다. 먼저 리스너 목록을 나열하고 ::에 바인딩된 모든 항목을 기록하십시오:

sudo ss -tlnp | grep '::'

그런 다음, 다른 머신에서 서버의 공인 IPv6 주소로 연결하여 닫혀 있다고 생각되는 포트를 시도해 보십시오:

curl -6 -v http://[2001:db8:2a::1]:8080/

만약 페이지나 배너가 반환된다면, 해당 포트는 IPv6에서 열려 있는 것입니다. 닫힌 포트는 Connection refused 또는 타임아웃을 발생시킵니다. 이 두 가지 실패는 서로 다른 신호이며, 거부됨과 타임아웃의 차이를 통해 호스트가 응답하고 거절했는지, 아니면 방화벽이 패킷을 조용히 폐기했는지 알 수 있습니다. 전체적인 상황을 파악하려면 서버 외부에서 nmap을 사용하여 IPv6 주소를 스캔하십시오:

nmap -6 2001:db8:2a::1

nmap이 IPv6에서 열려 있다고 보고하는 모든 포트는 IPv4 스캔 결과와 관계없이 전 세계 인터넷에서 접근 가능한 포트입니다. IPv4와 IPv6 스캔 결과를 나란히 비교하는 것이 격차를 찾는 가장 빠른 방법입니다. -6에서는 열려 있지만 IPv4에서는 닫혀 있는 모든 항목은 방화벽에서 누락된 서비스입니다.

격차 해소

UFW가 두 스택을 모두 보호하도록 설정하고 기본 정책을 거부(deny)로 지정하십시오. 변경 사항을 확인한 다음, 인바운드 기본 정책을 거부로 설정하고 필요한 트래픽만 허용하십시오:

sudo sed -i 's/^IPV6=no/IPV6=yes/' /etc/default/ufw
sudo ufw default deny incoming
sudo ufw allow 22/tcp
sudo ufw enable
sudo ufw status verbose

IPV6=yes 설정을 변경할 때 이미 UFW가 활성화된 상태였다면, sudo ufw reload 명령을 실행하기 전까지는 변경 사항이 적용되지 않습니다.

ufw status 명령을 실행하면 각 규칙이 두 번씩 나열됩니다. 한 번은 일반 규칙으로, 다른 한 번은 (v6) 접미사가 붙은 형태로 나타납니다. (v6) 줄이 보인다면 UFW가 IPv6를 필터링하고 있는 것입니다:

22/tcp                     ALLOW IN    Anywhere
22/tcp (v6)                ALLOW IN    Anywhere (v6)

iptables를 직접 관리한다면 모든 규칙을 ip6tables에도 동일하게 반영하십시오. 또는 nftables로 전환하십시오. nftables의 inet 테이블은 IPv4와 IPv6를 한곳에서 처리하므로 이러한 유형의 실수를 원천적으로 방지합니다. 직접 규칙을 작성할 때는 단일 nftables inet 필터 테이블을 사용하는 것이 가장 깔끔한 해결책입니다. Ubuntu가 아닌 Rocky나 AlmaLinux를 사용하는 VPS라면 UFW가 없으므로 대신 firewalld를 프런트엔드로 관리해야 하며, 이 도구는 영역 규칙을 두 스택에 동시에 적용합니다.

공개할 필요가 없는 서비스는 루프백 인터페이스에 바인딩하십시오. 데이터베이스, 관리자 패널, 메트릭 엔드포인트는 공인 주소가 필요한 경우가 거의 없습니다. 해당 서비스를 127.0.0.1::1에 바인딩하여 애초에 라우팅 가능한 주소에서 수신 대기하지 않도록 설정하십시오. Postgres의 경우 listen_addresses = 'localhost'를 설정하십시오. 애플리케이션 서버라면 127.0.0.1에 바인딩하고 앞에 리버스 프록시를 두십시오. 리스너를 닫는 것이 방화벽으로 막는 것보다 더 확실한 방법입니다. 접근할 대상 자체가 사라지기 때문입니다.

Docker가 게시한 포트를 보호하는 데 UFW를 신뢰하지 마십시오. 컨테이너 포트를 모든 인터페이스가 아닌 특정 주소(예: -p 127.0.0.1:8080:80)에 게시하여, 호스트나 의도적으로 프록시를 거친 경로에서만 포트에 접근할 수 있도록 하십시오. 컨테이너를 반드시 공개해야 한다면 Traefik 리버스 프록시 뒤에 배치하고, 각 애플리케이션이 아닌 프록시만 포트를 게시하십시오.

제공업체의 방화벽에 IPv6 규칙을 추가하십시오. 해당 방화벽이 IPv6를 지원하지 않는다면, 호스트의 UFW나 nftables가 해당 역할을 대신 수행하도록 설정하십시오.

포트가 실제로 닫혔는지 확인

변경 사항을 적용한 후 외부에서 동일한 테스트를 다시 수행합니다.

curl -6 -v http://[2001:db8:2a::1]:8080/
nmap -6 2001:db8:2a::1

이전에 응답했던 포트는 이제 연결을 거부하거나 시간 초과가 발생해야 하며, nmap은 해당 포트를 filtered 또는 closed 상태로 보고해야 합니다. 포트가 여전히 열려 있다면 위에서 언급한 네 가지 원인을 다시 검토하십시오. 서비스가 여전히 ::에 바인딩되어 있고 앞단에 규칙이 없는 경우, UFW보다 우선하는 Docker 규칙이 있는 경우, 또는 IPv6 트래픽을 전혀 제어하지 않는 클라우드 제공업체의 방화벽이 있는 경우입니다.

민감한 서비스를 공용 인터넷에서 완전히 격리하는 것이 훨씬 더 강력한 보안 방법입니다. SSH와 관리자 패널을 WireGuard VPN 뒤로 배치하고 해당 포트에 방화벽을 설정하여 터널을 통해서만 응답하도록 하면, IPv6 노출 문제는 더 이상 적용되지 않습니다. 공개된 상태로 남아 있는 서비스에 대한 무차별 대입 공격을 늦추려면, 기본 거부 방화벽 위에 SSH 앞단에 Fail2ban을 구성하십시오.

포트 개념이 생소하다면, 포트의 정의와 서비스가 리스닝하는 방식에 대한 입문서를 먼저 읽어보시기 바랍니다.

FAQ

UFW는 기본적으로 IPv6를 차단합니까?

최신 Ubuntu 24.04 설치 환경에서는 그렇습니다. UFW는 IPV6=yes에서 /etc/default/ufw을 읽어 각 규칙을 IPv4와 IPv6 모두에 적용하며, ufw status(v6) 접미사가 붙은 IPv6 규칙을 보여줍니다. 문제는 IPV6=no을 사용할 때(오래된 이미지나 가이드에서 비롯된 경우), IPv4만 필터링하는 제공업체의 방화벽에 의존할 때, 또는 Docker가 UFW를 우회하여 포트를 노출할 때 발생합니다. grep IPV6 /etc/default/ufw를 사용하여 설정을 확인하십시오.

VPS가 IPv6로 무엇을 노출하고 있는지 어떻게 확인합니까?

sudo ss -tlnp를 실행하고 로컬 주소가 [::]으로 시작하는 모든 리스너를 확인하십시오. 이는 해당 서비스가 모든 IPv6 인터페이스에서 응답하고 있음을 의미합니다. 그 후 다른 장비에서 서버의 공인 IPv6 주소를 curl -6 -v http://[YOUR:IPV6::ADDR]:PORT/로 직접 테스트하거나 nmap -6 YOUR:IPV6::ADDR로 스캔하십시오. IPv6 스캔에서는 열려 있으나 IPv4에서는 닫혀 있는 포트가 보안 취약점입니다.

UFW는 차단되었다고 나오는데 왜 Docker 컨테이너의 포트에 접근할 수 있습니까?

Docker는 -p로 포트를 게시할 때 UFW보다 앞서 자체 방화벽 규칙을 삽입합니다. 따라서 ufw status에는 거부된 것으로 표시되더라도 게시된 포트는 접근 가능합니다. 이는 IPv4에서 발생하며, Docker의 IPv6 지원이 켜져 있을 경우 IPv6에서도 동일하게 발생합니다. -p 127.0.0.1:8080:80과 같이 특정 주소로 게시하거나, 컨테이너를 리버스 프록시 뒤에 배치하고 프록시 포트만 게시하십시오.

IPv4 방화벽이 견고해도 IPv6 방화벽이 여전히 필요합니까?

네, 필요합니다. IPv4와 IPv6는 별도의 네트워크 스택이며 방화벽 규칙도 각각 적용됩니다. 완벽한 IPv4 규칙을 설정해도 IPv6 트래픽에는 아무런 영향을 주지 못합니다. VPS에 공인 IPv6 주소가 할당되어 있다면(대부분의 VPS가 그렇습니다), ::에서 수신 대기 중인 모든 서비스는 IPv6 방화벽 규칙이나 루프백 바인딩으로 차단하기 전까지 IPv6를 통해 접근할 수 있습니다.

서비스를 IPv4 전용이나 로컬호스트 전용으로 수신 대기하게 하려면 어떻게 합니까?

서비스의 설정 파일에서 바인딩 주소를 지정하십시오. IPv4 루프백 전용으로 하려면 127.0.0.1에, IPv6 없이 모든 IPv4 주소에서 수신 대기하려면 0.0.0.0에 바인딩하십시오. Postgres는 listen_addresses를 사용하고, SSH는 ListenAddress을 사용하며, 대부분의 애플리케이션 서버는 호스트나 바인딩 플래그를 제공합니다. sudo ss -tlnp로 결과를 확인하고 Local Address에 더 이상 [::]가 나타나지 않는지 확인하십시오.