localhost와 127.0.0.1: 같은 듯 다른 둘의 정체

localhost와 127.0.0.1: 같은 듯 다른 둘의 정체

개발 서버를 띄우면 터미널에 항상 비슷한 메시지가 뜨죠.

Server running on http://localhost:3000

별생각 없이 클릭해서 들어가지만, 막상 localhost가 정확히 무엇인지, 127.0.0.1과 어떻게 다른지, 그리고 0.0.0.0은 또 뭔지 물으면 순간 헷갈리는 분이 의외로 많습니다.

이번 포스팅에서는 매일 쓰는 localhost를 둘러싼 이름·주소·바인딩의 관계를 정리하고, 자주 만나는 함정 몇 가지도 함께 짚어보겠습니다.

localhost는 이름, 127.0.0.1은 주소

먼저 가장 기본적인 구분부터 짚고 가요. localhost이름이고 127.0.0.1주소입니다. 도메인 이름이 IP 주소로 변환되는 것과 똑같이, localhost라는 이름도 어딘가에서 IP 주소로 바뀌어야 통신이 일어나요.

이 변환은 보통 운영체제의 hosts 파일에서 일어납니다. DNS 서버까지 갈 필요도 없이 로컬 파일 한 줄로 끝나거든요.

/etc/hosts (macOS, Linux)
127.0.0.1   localhost
::1         localhost

Windows에서도 위치만 다를 뿐(C:\Windows\System32\drivers\etc\hosts) 같은 매핑이 들어 있습니다. 즉 localhost라는 이름을 입력하면 운영체제가 이 파일을 보고 “아, 127.0.0.1을 쓰라는 뜻이구나” 하고 해석하는 거예요.

이 파일은 직접 편집할 수도 있어서, 개발할 때 mysite.local처럼 가짜 도메인을 127.0.0.1에 매핑해 두고 쓰는 분도 많습니다. HTTPS 인증서 테스트나 다중 도메인 환경 시뮬레이션할 때 유용한 트릭이에요.

127.0.0.0/8 전체가 루프백이다

127.0.0.1루프백 대역의 한 주소일 뿐입니다. 실제로는 127.0.0.0/8 전체, 그러니까 127.0.0.0부터 127.255.255.255까지 약 1,677만 개의 주소가 모두 루프백으로 예약되어 있어요.

모두 자기 자신을 가리킴
ping 127.0.0.1   # OK
ping 127.0.0.2   # OK (역시 자기 자신)
ping 127.42.0.7  # OK

그래서 한 컴퓨터에서 여러 로컬 서비스를 서로 다른 루프백 주소에 띄우는 트릭이 가능합니다.

포트 충돌 없이 여러 서비스 띄우기
nginx     -p 127.0.0.1:80
nginx-alt -p 127.0.0.2:80

같은 80 포트지만 IP가 달라서 충돌하지 않거든요. 컨테이너나 가상 머신 환경에서도 이 트릭을 응용한 설정을 자주 만나게 됩니다.

::1, IPv6의 localhost

IPv6에서는 루프백 주소가 단 하나, ::1입니다. 0:0:0:0:0:0:0:1을 압축한 형태인데, IPv4 측 127.0.0.0/8처럼 대역으로 예약되어 있지 않고 정확히 그 한 주소만 루프백으로 정의되어 있어요.

문제는 운영체제의 hosts 파일에 보통 두 줄이 함께 들어 있다는 점입니다.

127.0.0.1   localhost
::1         localhost

이러면 localhost라는 이름이 IPv4 주소(127.0.0.1)로도, IPv6 주소(::1)로도 해석될 수 있습니다. 어느 쪽이 먼저 쓰이느냐는 운영체제와 애플리케이션의 정책에 따라 갈리는데요. Node.js 18 이상이 기본적으로 IPv6를 먼저 시도하도록 바뀌면서 “어제까지 잘 되던 fetch('http://localhost:3000')이 갑자기 ECONNREFUSED를 뱉는다”는 이슈가 흔하게 생깁니다.

원인은 대부분 서버는 IPv4(127.0.0.1)에만 바인딩되어 있는데 클라이언트는 IPv6(::1)로 먼저 연결을 시도하는 비대칭이에요. 해결책은 두 가지입니다. 서버를 양쪽에 바인딩하도록 0.0.0.0이나 ::로 띄우거나, 클라이언트에서 localhost 대신 명시적으로 127.0.0.1을 적는 거죠.

127.0.0.1과 0.0.0.0은 다르다

서버를 어디에 바인딩하느냐는 보안 경계와 직결되는 결정입니다. 127.0.0.1에 바인딩하면 내 컴퓨터 안에서만 접근할 수 있어서, 같은 와이파이의 다른 기기나 도커 컨테이너에서도 닿을 수 없습니다. “내 컴퓨터 밖으로는 절대 새지 않는다”는 경계가 자연스럽게 생기는 셈이죠.

반면 0.0.0.0에 바인딩하면 의미가 정반대가 됩니다. “이 머신의 모든 네트워크 인터페이스에서 접근을 허용”한다는 뜻이라, 같은 와이파이의 다른 기기, VPN을 통해 들어오는 외부 사용자, 도커 컨테이너 모두가 그 서버에 접근할 수 있게 돼요. 공유 와이파이에서 개발 서버를 0.0.0.0으로 띄우는 건 옆자리 사람에게 내 코드와 데이터를 공개하는 셈이라, 인증과 방화벽을 한 번 점검할 필요가 있습니다.

0.0.0.0이라는 와일드카드 주소가 바인딩 외에도 라우팅, DHCP 같은 곳에서 어떻게 쓰이는지는 별도의 글에 따로 정리해 두었습니다.

루프백이 가지는 보안 경계의 의미

루프백 트래픽은 물리 네트워크 어댑터를 전혀 거치지 않습니다. 패킷이 운영체제 커널 안에서 곧장 돌아오는 구조라, 외부 네트워크 상태와 무관하게 항상 동작하고 외부에서는 절대 접근할 수 없어요.

이 특성 덕분에 127.0.0.1 바인딩은 “내 컴퓨터 안에서만 도는 서비스”라는 약속이 됩니다. 랜선을 뽑아도, 와이파이를 꺼도 동작하고, 외부 침입자가 IP를 알아내도 닿을 수 없죠.

다만 이 안전감 때문에 인증을 느슨하게 걸어둔 로컬 서비스가 DNS 리바인딩 같은 우회 공격의 표적이 되곤 합니다. “어차피 127.0.0.1이니까 외부에서 못 들어오겠지”라는 가정이 깨지는 시나리오인데요. 로컬 서비스라도 인증을 챙기는 게 좋다는 점은 따로 다룬 글에 자세히 적어 두었습니다.

마치며

localhost는 이름, 127.0.0.1은 주소, 0.0.0.0은 “어디든 받겠다”는 와일드카드입니다. 이 셋을 구분해서 머릿속에 정리해 두면, 개발 서버가 갑자기 안 잡힐 때나 다른 기기에서 접근이 필요할 때 실수가 크게 줄어듭니다.

특히 IPv4와 IPv6가 혼재하는 환경에서는 localhost가 어느 쪽으로 해석되는지를 한 번쯤 의심해 보는 습관이 큰 도움이 됩니다. 연결이 안 될 때 가장 먼저 127.0.0.1로 직접 쳐서 시도해 보는 것만으로도 절반의 문제는 풀려요.

운영체제별 hosts 파일과 이름 해석 우선순위에 대해서는 Wikipedia의 hosts 항목이 잘 정리되어 있으니 참고해 보세요.

This work is licensed under CC BY 4.0 CC BY

개발자를 위한 뉴스레터

달레가 정리한 AI 개발 트렌드와 직접 만든 콘텐츠를 전해드립니다.

Discord