結論

localhost は「このコンピューター自身」を指す名前で、ふつうは 127.0.0.1 というループバックアドレスに変換されます。localhost:3000 の :3000 は、その中で待ち受けているプログラムを選ぶポート番号です。

この記事でわかること

  • localhost・127.0.0.1・::1・0.0.0.0 の違いと、名前がアドレスに変わる仕組み(getent・strace の実物)
  • :3000・:8080・:5173・:8000 がどの開発ツールの既定かと、127.0.0.1 と 0.0.0.0 で「外から見えるか」が変わる実測
  • スマホから見えない理由、Docker・WSL2 の中の localhost が別物になる理由、HTTPS と localhost の関係

2026年10月29日更新|Ubuntu 24.04・Python 3.13・Node.js 22・Vite 7・Chromium 153 で実行確認

ブラウザに http://localhost:3000 と打ったとき、裏側では「localhost という名前をアドレスに変える → 自分自身に向けて通信する → 3000 番で待っているプログラムに渡す」という3段階が起きています。この記事は、その3段階を手元で1つずつ動かして確かめた記事です。

flowchart TD
    B[ブラウザに<br>http://localhost:3000] --> N[名前解決<br>localhost → 127.0.0.1]
    N --> L[ループバック lo<br>自分自身へ届く]
    L --> P{3000番で<br>待つ相手は?}
    P -->|いる| S[開発サーバーが応答]
    P -->|いない| E[接続が拒否されました]

よく調べられることの答えを先にまとめます。

知りたいこと 答え 詳しい節
localhostとは 自分自身を指すホスト名。127.0.0.1(IPv6 では ::1)に変わる localhostとは
localhost と 127.0.0.1 の違い 名前とアドレスの違い。名前は ::1 に向くこともある localhostと127.0.0.1・::1の違い
0.0.0.0 との違い 0.0.0.0 は「全部のアドレスで待つ」。外から見える 0.0.0.0との違い
localhost:3000 の意味 自分の PC の 3000 番ポートで待つプログラムに接続 localhost:3000・:8080の意味
3000・8080・5173・8000 は何 Next.js・Tomcat・Vite・Django などの既定のポート ポート3000・8080・5173・8000
localhost が開けない 待つ相手がいない・ポート違い・別の中で動いている localhostが開けないとき
スマホから見えない スマホの localhost はスマホ自身。LAN の IP で開く 同じPCのスマホから見えない理由
Docker・WSL2 の中の localhost コンテナや VM 自身を指す。ポート公開や特別な名前を使う Docker・WSL2の中のlocalhost
http://localhost は安全扱い? ブラウザはセキュアコンテキストとして扱う(実測 true) HTTPSとlocalhost

確認環境:Ubuntu 24.04.5 LTS(iproute2 6.1.0・curl 8.5.0・glibc の getent)で、名前解決は /etc/hosts・getent・Python 3.13.18 の socket.getaddrinfo・Node.js 22.23.4 の dns.lookup、通信の有無は strace で確かめました。127.0.0.1 と 0.0.0.0 の違いは、ip netns で「PC 役」と「スマホ役」の2つのネットワーク名前空間(t49a・t49b、192.168.49.0/24)を作り、Python の http.server の待ち受けアドレスを変えながら両方から curl で開いて実測しました。開発ツールの既定は Vite 7.3.7・Express 5.3.0・Python http.server を手元で起動して ss -ltnp で確かめ、Next.js・Django・uvicorn・Spring Boot・Rails・Angular・Flask・Jupyter は各公式ドキュメントで確かめています。ブラウザの挙動は Chromium 153.0.8010.12(Playwright 1.63.0)を名前空間の中で動かし、window.isSecureContext とエラーの文言を取りました。この環境は IPv6 が無効(::1 が無い)ため、::1 の挙動は RFC 6761・Node.js と Django の公式ドキュメントの範囲で書いています。Docker と WSL2 は手元で動かさず、Docker・Microsoft Learn の公式ドキュメントで確かめました。図は mermaid で描画を確かめています(確認日 2026年10月29日)。

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「localhostとは|127.0.0.1・0.0.0.0との違いと:3000の意味」

localhostとは(意味を一言で)

この節では、localhost が何を指す言葉で、なぜ開発でよく出てくるのかが分かります。

localhost は、「今このプログラムが動いているコンピューター自身」を指す特別なホスト名です。 www.example.com のようなホスト名が遠くのサーバーを指すのに対して、localhost は常に自分を指します。RFC 6761 は localhost を特別な扱いをする名前として予約し、名前解決の仕組みは localhost の問い合わせに対して「常に IP ループバックアドレスを返すべき(SHOULD always return the IP loopback address for address queries)」と定めています。IPv4 のループバックアドレスが 127.0.0.1、IPv6 では ::1 です。

1自分宛ての手紙

localhost 宛ての通信は、ネットワークケーブルや Wi-Fi に出ていきません。自分の中で折り返して、自分の中で待っているプログラムに届きます。

2開発で使う理由

作りかけの Web アプリを、インターネットに公開せずに自分のブラウザで確かめられます。本番のサーバーも、他の人の PC も要りません。

3設定ファイルでも使う

データベースの接続先に localhost と書けば「同じマシンの中の DB」の意味になります。WordPress の DB_HOST などで見かけます。

検索で上位の解説は「自分のコンピューターを指す」「127.0.0.1 になる」で止まっていることが多いので、この記事では、名前がアドレスに変わる瞬間と、127.0.0.1 と 0.0.0.0 で何が変わるかを、実際に動かして見ていきます。

localhostが127.0.0.1になる仕組み(hostsファイルとgetentの実物)

この節では、localhost という名前がどこで 127.0.0.1 に変わるのか、DNS に問い合わせているのかが分かります。

Linux では、まず /etc/hosts ファイルを見て、そこに書いてある対応で localhost を 127.0.0.1 に変えます。 順番は /etc/nsswitch.conf の hosts: 行で決まっていて、手元では「files(hosts ファイル)→ dns」の順でした。

$ cat /etc/hosts
127.0.0.1 localhost
...

$ grep '^hosts' /etc/nsswitch.conf
hosts:          files dns

$ getent hosts localhost
127.0.0.1       localhost

$ python3 -c "import socket; print(socket.gethostbyname('localhost'))"
127.0.0.1

$ node -e "require('dns').lookup('localhost',{all:true},(e,a)=>console.log(a))"
[ { address: '127.0.0.1', family: 4 } ]

本当に DNS へ問い合わせていないかは、strace で送信の有無を見ると分かります。Python の getaddrinfo('localhost', 80) は、ネットワークへの送信が 0 回でした。

$ strace -f -e trace=sendto,sendmsg -o st.txt python3 -c "import socket; socket.getaddrinfo('localhost', 80)"
$ grep -c -E 'sendto|sendmsg' st.txt
0

一方で、getent hosts localhost は IPv6 のアドレスから先に探す作りのため、hosts ファイルに IPv6(::1)の行が無い手元の環境では、AAAA レコード(IPv6 アドレス)の問い合わせを DNS サーバーに送っていました。

$ strace -f -e trace=network -o st.txt getent hosts localhost
$ grep -E 'connect|sendto' st.txt
connect(3, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("8.8.8.8")}, 16) = 0
sendto(3, "...\tlocalhost\0\0\34\0\1", 27, MSG_NOSIGNAL, NULL, 0) = 27   # \34 = 28 = AAAA
  • hosts ファイルに「::1 localhost」の行もあると、この問い合わせは起きません。 多くのディストリビューションの既定の hosts には IPv4 と IPv6 の両方の行があります。
  • RFC 6761 は、名前解決のライブラリや DNS サーバーが localhost の問い合わせを上位の DNS に送らないことを推奨しています。アプリから見ると「localhost は DNS に行かない」が原則ですが、hosts の書き方によっては上のように漏れることがある、と覚えておくと切り分けに役立ちます。

Windows・Mac の hosts ファイル

Windows は C:\Windows\System32\drivers\etc\hosts、Mac は /etc/hosts に同じ役割のファイルがあります。Linux の同じ仕組みで、ここに書いた名前は DNS より先に解決されます。編集には管理者権限が要ります。

127.0.0.1とは・ループバックアドレスの仕組み(127.0.0.0/8全部が自分)

この節では、127.0.0.1 がどんなアドレスで、なぜ外に出ていかないのかが分かります。

127.0.0.1 は IPv4 のループバックアドレスで、自分自身に向けた通信にだけ使う、予約されたアドレスです。 RFC 1122 は 127 で始まるアドレスについて「ホストの外に現れてはならない(MUST NOT appear outside a host)」と定めています。実際に Linux では、lo(loopback)という仮想のインターフェースに 127.0.0.1/8 が付いています。

$ ip addr show lo
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo

$ ip route show table local
local 127.0.0.0/8 dev lo proto kernel scope host src 127.0.0.1
local 127.0.0.1 dev lo proto kernel scope host src 127.0.0.1

/8 は「先頭の 8 ビット(127)が同じなら全部」という意味なので、127.0.0.1 だけでなく 127.0.0.0/8 の約1,677万個のアドレスがすべて自分自身です。 127.5.5.5 で待ち受けても、ちゃんと動きます。

$ python3 -m http.server 8000 --bind 127.5.5.5
Serving HTTP on 127.5.5.5 port 8000 (http://127.5.5.5:8000/) ...

$ curl http://127.5.5.5:8000/
<h1>hello from t49a</h1>
$ curl http://127.0.0.1:8000/
curl: (7) Failed to connect to 127.0.0.1 port 8000 after 0 ms: Couldn't connect to server

127.5.5.5 で待っているサーバーは 127.0.0.1 からは見えません。「同じ自分」でも、待ち受けに使ったアドレスにぴったり一致しないと届かない、という点は次の 0.0.0.0 の話につながります。

127.0.0.0/8IPv4 のループバックRFC 6890。外へ転送しない
::1/128IPv6 のループバック1個だけ。RFC 4291
0回getaddrinfo の DNS 送信strace で実測(Python)

localhostと127.0.0.1・::1の違い(IPv6が絡むと別物になる)

この節では、「localhost と 127.0.0.1 は同じですか」という問いの正確な答えと、::1 が絡んで起きる食い違いが分かります。

localhost は「名前」、127.0.0.1 は「アドレス」です。 ふだんは同じ場所を指しますが、名前は解決のしかたで向き先が変わります。特に IPv6 が有効な環境では、localhost が ::1(IPv6 のループバック)に解決されることがあり、そのとき 127.0.0.1 だけで待っているサーバーには届きません。

名前とアドレスのたとえ

localhost「自宅」という呼び名。住所録(hosts・DNS)を引いて住所に変える。引き方で 127.0.0.1 にも ::1 にもなる
↔
127.0.0.1住所そのもの。解釈の余地がなく、IPv4 の自分に必ず届く

困ったときは、名前ではなくアドレスで指定すると原因を切り分けやすい

書き方 何か 届く先
localhost ホスト名(RFC 6761 で予約) 解決結果しだい。127.0.0.1 か ::1
127.0.0.1 IPv4 のループバックアドレス IPv4 の自分自身
::1 IPv6 のループバックアドレス IPv6 の自分自身。URL では http://[::1]:3000
0.0.0.0 「全部のアドレス」(待ち受け側の指定) 接続先として打つものではない
app.localhost localhost のサブドメイン ブラウザ・curl は 127.0.0.1 扱い。OS の解決は失敗(後述)

手元の環境は IPv6 が無い(正直に書きます)

この記事の環境には IPv6 のアドレスがまったく無く、::1 への接続はその場で失敗しました。そのため localhost は常に 127.0.0.1 に解決され、「localhost が ::1 に向く」現象は手元では再現できません。

$ ip -6 addr
(何も表示されない)
$ curl http://[::1]:8000/
curl: (7) Failed to connect to ::1 port 8000 after 0 ms: Couldn't connect to server

公式ドキュメントで確かめられる範囲は次のとおりです。

  • Node.js:dns.lookup() の結果の並び順の既定は v17.0.0 で verbatim(OS が返した順のまま)に変わりました。OS が ::1 を先に返す環境では、localhost への接続が ::1 に向きます。dns.setDefaultResultOrder('ipv4first') か --dns-result-order=ipv4first で IPv4 を先にできます(Node.js 公式)。
  • Django:runserver の既定は 127.0.0.1:8000 で、-6 を付けると既定のアドレスが ::1 に変わります(Django 公式)。
  • Vite:server.host に localhost を使うと、Node.js の並び順の設定によってブラウザが Vite の待ち受けと違うアドレスを使うことがある、と注記されています。解決したアドレスが違うときは Vite が表示します(Vite 公式)。

「127.0.0.1 なら開けるのに localhost だと開けない」ときは、この IPv4 と IPv6 の食い違いを疑います。 サーバーを ::1 と 127.0.0.1 の両方で待たせるか、ブラウザ側で 127.0.0.1 を直接打つのが早い対処です。

0.0.0.0との違い(127.0.0.1で待つと外から見えないことを実測)

この節では、サーバーの待ち受け先を 127.0.0.1 にしたときと 0.0.0.0 にしたときで、同じ PC・別の端末からどう見えるかが、実測で分かります。

0.0.0.0 は「この PC が持っている全部のアドレスで待つ」という待ち受け側の指定です。 127.0.0.1 で待つと自分の中からしか届かず、0.0.0.0 で待つと LAN の IP でも届きます。RFC 6890 でも 0.0.0.0/8 は「このネットワーク上のこのホスト」を表し、宛先には使えない(Destination: False)とされています。

手元では、ip netns で「PC 役」(t49a、192.168.49.10)と「スマホ役」(t49b、192.168.49.20)を作って同じ LAN につなぎ、PC 役で Python の http.server を待ち受け先を変えて起動し、両方から curl で開きました。

待ち受け(--bind) PC 役から スマホ役から
127.0.0.1 localhost:200/127.0.0.1:200/192.168.49.10:拒否 192.168.49.10:拒否
0.0.0.0 localhost:200/127.0.0.1:200/192.168.49.10:200 192.168.49.10:200
192.168.49.10 localhost:拒否/127.0.0.1:拒否/192.168.49.10:200 192.168.49.10:200
どれでも ― 127.0.0.1・localhost:拒否(スマホ自身を指すため)

ss -ltnp で見ると、待ち受けの違いは「Local Address:Port」の列にそのまま出ます。

$ ss -ltnp                       # --bind 127.0.0.1 のとき
LISTEN 0  5    127.0.0.1:8000   0.0.0.0:*   users:(("python3",pid=27107,fd=3))

$ ss -ltnp                       # --bind 0.0.0.0 のとき
LISTEN 0  5      0.0.0.0:8000   0.0.0.0:*   users:(("python3",pid=27133,fd=3))

$ curl http://192.168.49.10:8000/      # 127.0.0.1 で待つサーバーに LAN の IP で(PC 役自身から)
curl: (7) Failed to connect to 192.168.49.10 port 8000 after 0 ms: Couldn't connect to server

サーバーのログには、どこから来たかが残ります。0.0.0.0 で待ったときは、localhost で開くと 127.0.0.1、自分の LAN の IP で開くとその IP、スマホ役から開くとスマホ役の IP でした。

127.0.0.1 - - [29/Oct/2026 22:39:55] "GET / HTTP/1.1" 200 -
192.168.49.10 - - [29/Oct/2026 22:39:55] "GET / HTTP/1.1" 200 -
192.168.49.20 - - [29/Oct/2026 22:39:55] "GET / HTTP/1.1" 200 -

127.0.0.1 で待つ(既定にしているツールが多い)

自分の PC の中からだけ開ける

同じ Wi-Fi の人にも見えない。開発中の安全側

スマホ・別 PC で確かめたいときだけ切り替える

0.0.0.0 で待つ(--host などで切り替え)

LAN の IP でもループバックでも開ける

同じネットワークの誰からも見える。公共 Wi-Fi では避ける

ファイアウォールの許可が別に要ることがある

ブラウザの URL に http://0.0.0.0:8000 と打って開けることもありますが、それは OS がループバックに読み替えているだけです。0.0.0.0 は待ち受け側の書き方で、開く側は localhost か 127.0.0.1 か LAN の IP を使います。

localhost:3000・localhost:8080の「:3000」の意味(ポート番号)

この節では、localhost:3000 の :3000 が何を選んでいるのか、同じ番号を2つ使えない理由、80 番が使えないことがある理由が分かります。

:3000 はポート番号で、「同じコンピューターの中の、どのプログラムに届けるか」を選ぶ番号です。 アドレスが建物なら、ポートは部屋番号です。1 台の PC で Web の開発サーバー・API・データベースが同時に動けるのは、それぞれ違うポートで待っているからです。

// srv3000.js:Node.js の最小のサーバー(PORT が無ければ 3000)
const http = require('http');
const port = process.env.PORT || 3000;
http.createServer((req, res) => res.end('hello from :' + port + '\n'))
  .listen(port, '127.0.0.1', () => console.log('listening on http://localhost:' + port));
$ node srv3000.js
listening on http://localhost:3000

$ curl http://localhost:3000/
hello from :3000
$ curl http://localhost:3001/          # 3001 で待つ相手はいない
curl: (7) Failed to connect to localhost port 3001 after 0 ms: Couldn't connect to server

$ node srv3000.js                      # 2つ目を同じ 3000 で起動すると
Error: listen EADDRINUSE: address already in use 127.0.0.1:3000

$ PORT=3001 node srv3000.js            # 別のポートにすれば2つ動く
listening on http://localhost:3001

$ ss -ltnp | grep -E ':300[01]'
LISTEN 0  511   127.0.0.1:3001   0.0.0.0:*   users:(("node",pid=27500,fd=21))
LISTEN 0  511   127.0.0.1:3000   0.0.0.0:*   users:(("node",pid=27481,fd=21))
  • 1ポートは 0〜65535 の番号URL で省略すると http は 80、https は 443 を使う。http://localhost は http://localhost:80 と同じ
  • 2同じアドレス・同じポートで待てるのは1つだけ2つ目は EADDRINUSE(Node)や Address already in use(Python)で止まる
  • 31024 未満は特権ポートLinux の既定(ip_unprivileged_port_start = 1024)では一般ユーザーが 80 で待てない。3000・8080 が好まれる理由の1つ
  • 4ポートが違えばオリジンも違うlocalhost:3000 から localhost:8080 の API を呼ぶと CORS の制約がかかる

一般ユーザー(uid 65534)で 80 番と 8080 番を試した結果です。80 番は権限のエラーで止まり、8080 番はそのまま動きました。

$ setpriv --reuid=65534 --regid=65534 --clear-groups python3 -m http.server 80 --bind 127.0.0.1
PermissionError: [Errno 13] Permission denied

$ setpriv --reuid=65534 --regid=65534 --clear-groups python3 -m http.server 8080 --bind 127.0.0.1
Serving HTTP on 127.0.0.1 port 8080 (http://127.0.0.1:8080/) ...

8080 が「80 の代わり」として広まったのは、この特権の壁を避けられる番号で、見た目も 80 に近いからです。IANA のポート番号の一覧でも 8080 は http-alt として登録されています。ポート番号の全体像は、関連記事「ポート番号一覧【2026年版】well-knownポートと確認コマンドの早見表」で扱っています。

ポート3000・8080・5173・8000はどのツールの既定か(公式で確認)

この節では、検索でよく見る localhost:3000・localhost:8080・localhost:5173・localhost:8000 がどのツールの既定なのか、そして既定の待ち受けアドレスがツールごとに違うことが分かります。

ポート番号に決まりはなく、ツールごとの慣習です。 各ツールの公式ドキュメントと、手元で起動したときの表示で確かめた一覧です。

ポート 既定にしているツール 変え方
3000 Next.js(next dev)、Ruby on Rails(rails server)、Express の公式例、Grafana Next.js は -p 4000 か PORT=4000。Rails は -p
8080 Spring Boot(server.port)、Tomcat、Jenkins Spring Boot は server.port=8081 か SERVER_PORT
5173 Vite(vite/npm run dev) server.port。使用中なら自動で次を試す。strictPort で止める
8000 Django(runserver)、uvicorn(FastAPI)、Python http.server runserver 0:8001、--port 8001、http.server 9000
5000 Flask(flask run) flask run --port 5001。macOS では AirPlay が 5000 を使う(Flask 公式)
4200 Angular(ng serve) --port。環境変数 PORT が優先
8888 Jupyter Notebook/JupyterLab --port 9999。使用中なら次の空きを探す

既定の「待ち受けアドレス」はツールで違う(手元で実測)

ポートと同じくらい大事なのに見落とされがちなのが、既定でどのアドレスに待ち受けているかです。Vite・Express・Python の http.server を起動して ss -ltn で見ました。

$ npx vite
  VITE v7.3.7  ready in 304 ms
  ➜  Local:   http://localhost:5173/
  ➜  Network: use --host to expose
$ ss -ltn | grep 5173
LISTEN 0  511   127.0.0.1:5173   0.0.0.0:*          # 自分の中だけ

$ npx vite --host
  ➜  Local:   http://localhost:5174/              # 5173 が使用中だったので 5174 に
  ➜  Network: http://192.0.2.2:5174/
LISTEN 0  511     0.0.0.0:5174   0.0.0.0:*          # LAN からも見える

$ node app.js                                    # Express 5.3.0:app.listen(3000)
Example app listening on port 3000
LISTEN 0  511     0.0.0.0:3000   0.0.0.0:*          # ホストを省くと全部のアドレス

$ python3 -m http.server 8000
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...   # 既定は全部のアドレス
ツール 既定の待ち受け 確かめ方
Vite 7.3.7 localhost(127.0.0.1)。--host で 0.0.0.0 手元で実行・Vite 公式
Express 5.3.0/Node の listen(port) ホスト省略で全部のアドレス(手元は 0.0.0.0) 手元で実行
Python http.server 全部のアドレス。--bind 127.0.0.1 で絞る 手元で実行・Python 公式
Next.js next dev -H の既定は 0.0.0.0 Next.js 公式
Django runserver 127.0.0.1。0:8000 で全部 Django 公式
uvicorn 127.0.0.1。--host 0.0.0.0 で全部 uvicorn 公式
Rails rails server localhost。-b で変更 Rails 公式
Angular ng serve localhost。--host で変更 Angular 公式

「Express のサーバーは同じ Wi-Fi のスマホから開けたのに、Vite は開けない」のは、この既定の違いです。 起動時の表示に Network の行が無いときは、自分の中だけで待っています。

localhostが開けないとき(エラーの文言と見る順番)

この節では、localhost:3000 を開いて失敗したときに、画面の文言から何が起きているかを読み取る方法が分かります。詳しい直し方は既存の記事に任せ、ここでは「何が起きているか」の判定に絞ります。

「接続が拒否されました」は、そのアドレス・ポートで待つ相手がいない、という意味です。 名前解決・到達・待ち受けの3段階のどこで止まったかで、文言が変わります。

文言(原文) 止まった段階 主な原因
Chrome「localhost で接続が拒否されました」
ERR_CONNECTION_REFUSED
待ち受け サーバーが起動していない・落ちた・ポート違い・127.0.0.1 と ::1 の食い違い
curl: (7) Failed to connect to localhost port 3001 after 0 ms: Couldn't connect to server 待ち受け 上と同じ(curl 版)
Error: listen EADDRINUSE: address already in use 127.0.0.1:3000 起動時 同じポートで別のプロセスが待っている
Error: Port 5173 is already in use(Vite の strictPort) 起動時 同上。strictPort 無しなら 5174 へ自動で移る
PermissionError: [Errno 13] Permission denied 起動時 一般ユーザーで 1024 未満のポートを使った
Chrome ERR_CERT_AUTHORITY_INVALID TLS https://localhost を自己署名の証明書で開いた
ページは出るが前の版・違うアプリ 届いている 別のプロジェクトが同じポートで動いている。ss -ltnp で PID を見る
1

起動したターミナルの表示を見る

「Local: http://localhost:5173/」のような行に、本当のポートが書いてある。5173 が使用中で 5174 に移っていることはよくある。

ポート違い

2

誰が待っているかを見る

Linux・Mac は ss -ltnp か lsof -nP -iTCP:3000 -sTCP:LISTEN、Windows は netstat -ano | findstr :3000。Local Address が 127.0.0.1 か 0.0.0.0 か ::1 かも一緒に見る。

待ち受け

3

名前ではなくアドレスで開く

http://127.0.0.1:3000 で開けるなら、localhost の解決(::1 に向いている)が原因。

名前解決

4

どの「自分」で動いているかを見る

Docker・WSL2・VM・別の端末で動かしているなら、その localhost は別物。次の2つの節へ。

到達

原因ごとの直し方(httpsへの自動切り替え・ファイアウォール・ツール別の対処)は、関連記事「localhostで接続が拒否されましたの原因と解決方法」で扱っています。ポートを使っているプロセスの探し方は、関連記事「ポートを使っているプロセスの調べ方|ss・lsof・netstat比較」で扱っています。

同じPCのスマホからlocalhostが見えない理由とLAN IPでの開き方

この節では、PC で動かした開発サーバーをスマホで開こうとして失敗する理由と、正しい開き方が分かります。

スマホで http://localhost:3000 を開くと、それは「スマホ自身の 3000 番」を指します。 PC ではありません。上の実測でも、スマホ役から 127.0.0.1 や localhost を開くと、PC 役のサーバーが 0.0.0.0 で待っていても拒否されました。

  1. 1PC の LAN の IP を調べるLinux・Mac は ip addr/ifconfig、Windows は ipconfig。192.168.x.x や 10.x.x.x のような値
  2. 2サーバーを 0.0.0.0 で待たせるVite は --host、Django は runserver 0:8000、uvicorn は --host 0.0.0.0、Python は既定で全部
  3. 3スマホを同じ Wi-Fi につなぎ、http://LANのIP:ポート で開く例:http://192.168.49.10:8000。実測ではスマホ役から 200 が返った
  4. 4開けなければファイアウォールと「ゲストネットワーク」を疑うOS のファイアウォールでそのポートを許可する。端末どうしの通信を遮る Wi-Fi 設定(プライバシーセパレーター)もある
  5. 5確かめ終わったら 127.0.0.1 に戻す0.0.0.0 のままにすると、同じネットワークの誰からも開ける

自分の IP アドレスの調べ方と、プライベート IP・グローバル IP の違いは、関連記事「グローバルIPの確認方法|コマンド・プライベートIPとの違いとNATを実演」で扱っています。インターネット越しに見せたいときは、ルーターのポート開放ではなく、トンネル(ngrok など)を使うのが一般的です。

Docker・WSL2の中のlocalhostは別物(公式ドキュメントで確認)

この節では、コンテナや WSL2 の中で動かしたサーバーに、なぜ localhost で届いたり届かなかったりするのかが分かります。手元では動かせないため、Docker と Microsoft Learn の公式ドキュメントの範囲で書きます。

コンテナも VM も、それぞれ自分の「lo」を持っています。 つまり、コンテナの中の localhost はコンテナ自身、WSL2 の中の localhost は WSL2 の Linux 自身を指します。手元の実験で使った ip netns は、まさにこの「ネットワークが別の自分」を作る仕組みで、スマホ役の 127.0.0.1 が PC 役に届かなかったのと同じことが起きます。

したいこと 書き方 出典
ホストのブラウザからコンテナの 80 番を開く docker run -p 8080:80 … → http://localhost:8080。-p でホストの 8080 をコンテナの 80 に対応づける Docker 公式
公開したポートを自分の PC だけに限る -p 127.0.0.1:8080:80。アドレスを省くとホストの全アドレスで公開され、外の世界からも届く(公式は「既定では安全ではない」と明記) Docker 公式
コンテナの中からホストのサービスへ host.docker.internal(ホストの内部 IP に解決される特別な名前。Docker Desktop) Docker Desktop 公式
コンテナの中のアプリを -p で見えるようにする アプリをコンテナの中で 0.0.0.0 で待たせる。127.0.0.1 で待つとコンテナの外から届かない この記事の実測と同じ仕組み
Windows から WSL2 の中のサーバーへ localhost でそのまま開ける(NAT モードでも転送される) Microsoft Learn
WSL2 の中から Windows のサーバーへ NAT モードでは Windows 側の IP(ip route show | grep default で分かる)。ミラーモード(Windows 11 22H2 以降、networkingMode=mirrored)なら 127.0.0.1 で届く Microsoft Learn
LAN の別端末から WSL2 のサーバーへ NAT モードでは netsh interface portproxy で転送。ミラーモードなら直接 Microsoft Learn

Docker Desktop の導入とエラーは、関連記事「Docker Desktopとは|インストール・使い方・有料の条件とエラー対処」で、WSL2 の環境づくりは、関連記事「WSL2環境構築|Ubuntu開発環境をコマンドだけで整える手順」で扱っています。

HTTPSとlocalhost(ブラウザはセキュアコンテキストとして扱う)

この節では、http://localhost が HTTPS でないのに「安全な接続」として扱われる理由と、https://localhost で証明書の警告が出る理由が分かります。

ブラウザは、http://localhost・http://127.0.0.1・http://*.localhost を「潜在的に信頼できるオリジン」として扱います。 MDN は、これらはブラウザと同じ端末上にあるため安全に配信されたと考えられる、と説明しています。そのため、本来は HTTPS が要る機能(Service Worker・位置情報・カメラとマイク・クリップボード・Web Crypto・通知など)が、localhost では HTTP のまま動きます。

名前空間の中で Chromium を動かし、window.isSecureContext を取りました。

開いた URL isSecureContext 結果
http://localhost:8000/ true 表示された
http://127.0.0.1:8000/ true 表示された
http://app.localhost:8000/ true 表示された(Chromium が自分で 127.0.0.1 に向けた)
http://192.168.49.10:8000/ false 表示されたが、HTTPS 限定の機能は使えない
https://localhost:8443/(自己署名) ― net::ERR_CERT_AUTHORITY_INVALID
http://localhost:3999/(待つ相手なし) ― net::ERR_CONNECTION_REFUSED

「スマホ(LAN の IP)で開くとカメラが使えない」のは、この isSecureContext が false になるためです。 localhost では動いていた機能が LAN の IP では動かない、という差はここから来ます。

https://localhost を使いたいとき

自己署名の証明書で HTTPS サーバーを立てると、curl もブラウザも警告します。

$ openssl req -x509 -newkey rsa:2048 -nodes -keyout key.pem -out cert.pem -days 30 \
    -subj '/CN=localhost' -addext 'subjectAltName=DNS:localhost,IP:127.0.0.1'
$ curl https://localhost:8443/
curl: (60) SSL certificate problem: self-signed certificate
$ curl -k https://localhost:8443/        # -k で検証を飛ばすと中身は返る
<h1>hello from t49a</h1>
  • ローカルで警告を出さずに HTTPS を使うには、自分の PC だけが信頼するローカル認証局を作る mkcert が広く使われています。Next.js の next dev --experimental-https も mkcert でローカルに信頼される証明書を作ります(Next.js 公式)。
  • HTTPS が本当に要るのは、「secure context では足りない」場面、たとえば Secure 属性つき Cookie の検証、HTTPS 前提の外部サービス(OAuth のコールバックや Webhook)とのやり取りです。それ以外は http://localhost のままで多くの機能が動きます。

.localhostサブドメインとhostsファイルの使い方(curl --resolveで代用)

この節では、app.localhost のような名前がどこで解決されるのか、hosts ファイルに何を書けば自分の PC に別名を付けられるのかが分かります。

*.localhost は、OS の名前解決では失敗しても、ブラウザと curl が自分で 127.0.0.1 に向けます。 手元の getent は app.localhost を解決できませんでしたが、curl 8.5.0 と Chromium 153 はどちらも 127.0.0.1 に接続しました。

$ getent hosts app.localhost
$ echo $?
2                                        # OS の解決は失敗

$ curl -v http://app.localhost:8000/
*   Trying 127.0.0.1:8000...
* Connected to app.localhost (127.0.0.1) port 8000
<h1>hello from t49a</h1>                 # curl は自分で 127.0.0.1 にした

複数のプロジェクトを a.localhost:3000・b.localhost:3000 のように名前で分けたいときに使えます(Cookie やローカルストレージがオリジンごとに分かれます)。ただし、Python や Java のプログラムから接続するときは OS の解決を使うので、同じ名前が通るとは限りません。

hosts ファイルに別名を書く

プログラムからも通る名前にしたいときは、hosts ファイルに1行足します。この記事では hosts を書き換えずに、curl の --resolve(この名前はこのアドレス、と curl にだけ教える)で同じことを確かめました。

# hosts ファイルに書くなら(管理者権限)
127.0.0.1   myapp.test

# 書き換えずに curl だけで試す
$ curl --resolve myapp.test:8000:127.0.0.1 http://myapp.test:8000/
<h1>hello from t49a</h1>
1

.test・.example・.invalid

RFC 6761 でテスト用に予約された名前。本物のドメインと衝突しない。

hosts の別名に向く

2

.localhost

同じく予約。ブラウザは自動で自分に向ける。

hosts に書かなくても動く

3

.local

mDNS(Bonjour)が使う名前。hosts の別名に使うと解決が遅れることがある。

避けたほうが無難

4

.dev

実在する TLD で、ブラウザが HTTPS を強制する。

ローカルの別名に使わない

どのアドレスで待ち受けるか・どの名前で開くかの判断表

この節では、場面ごとに「サーバーは何で待ち、ブラウザは何で開くか」を迷わず決められます。

場面 待ち受け 開く側
自分の PC だけで開発する 127.0.0.1(ツールの既定のまま) http://localhost:ポート
localhost で開けず 127.0.0.1 なら開ける ::1 と 127.0.0.1 の両方で待たせる 当面は http://127.0.0.1:ポート
同じ Wi-Fi のスマホで確かめる 0.0.0.0(--host など) http://LANのIP:ポート。終わったら戻す
Docker コンテナの中で動かす コンテナの中で 0.0.0.0、-p 127.0.0.1:8080:80 http://localhost:8080
WSL2 の中で動かし Windows のブラウザで見る WSL2 の中で既定のまま http://localhost:ポート
カメラ・Service Worker を試す 127.0.0.1 http://localhost(secure context)。LAN の IP では不可
HTTPS 前提の外部サービスと連携する mkcert の証明書で HTTPS、またはトンネル https://localhost:ポート か公開 URL
プロジェクトを名前で分けたい 127.0.0.1 http://a.localhost:ポート(ブラウザ・curl)

よくある質問

Q. localhost とは何ですか? A. 「このコンピューター自身」を指す特別なホスト名です。RFC 6761 で予約されていて、名前解決では常にループバックアドレス(IPv4 は 127.0.0.1、IPv6 は ::1)に変わります。localhost 宛ての通信はネットワークに出ず、自分の中で折り返します。

Q. localhost と 127.0.0.1 は同じですか? A. ふだんは同じ場所を指しますが、localhost は名前、127.0.0.1 はアドレスです。IPv6 が有効な環境では localhost が ::1 に解決されることがあり、127.0.0.1 だけで待つサーバーには届きません。手元の環境は IPv6 が無く、常に 127.0.0.1 でした。

Q. localhost:3000 とはどういう意味ですか? A. 自分の PC の 3000 番ポートで待っているプログラムに接続する、という意味です。3000 は Next.js や Rails、Express の例が既定にしているポートで、決まりではなく慣習です。

Q. localhost:8080 は何ですか? A. 自分の PC の 8080 番です。Spring Boot・Tomcat・Jenkins などの既定で、一般ユーザーでも使える「80 の代わり」として広まりました。IANA では http-alt として登録されています。

Q. 0.0.0.0 と 127.0.0.1 の違いは何ですか? A. どちらもサーバーの待ち受け先の指定で、127.0.0.1 は自分の中からだけ、0.0.0.0 は PC が持つ全部のアドレス(LAN の IP を含む)で受け付けます。手元の実測では、127.0.0.1 で待つと同じ LAN の別端末からは拒否され、0.0.0.0 にすると 200 が返りました。

Q. localhost が開けません。 A. 「接続が拒否されました」なら、そのポートで待つ相手がいません。起動時の表示でポートを確かめ、ss -ltnp などで誰が待っているかを見ます。127.0.0.1 なら開けるなら IPv6 の食い違い、Docker・WSL2 の中なら localhost が別物です。直し方は関連記事「localhostで接続が拒否されましたの原因と解決方法」にまとめています。

Q. スマホから PC の localhost:3000 を開けますか? A. localhost のままでは開けません。スマホの localhost はスマホ自身です。サーバーを 0.0.0.0 で待たせ、同じ Wi-Fi から http://PCのLANのIP:3000 で開きます。

Q. Docker の中から localhost のデータベースに接続できません。 A. コンテナの中の localhost はコンテナ自身です。Docker Desktop では host.docker.internal という名前がホストの内部 IP に解決されます。逆にホストからコンテナへは -p でポートを公開して localhost:公開したポート で開きます。

Q. http://localhost は暗号化されていないのに、カメラや Service Worker が使えるのはなぜですか? A. ブラウザが localhost・127.0.0.1・*.localhost を「潜在的に信頼できるオリジン」として扱うためです(MDN)。手元の Chromium でも window.isSecureContext は localhost で true、LAN の IP では false でした。

Q. https://localhost で「この接続ではプライバシーが保護されません」と出ます。 A. 自己署名の証明書をブラウザが信頼していないためで、手元では ERR_CERT_AUTHORITY_INVALID でした。mkcert でローカルの認証局を作って証明書を発行すると、警告なしで開けます。

Q. hosts ファイルはどこにあり、何を書けますか? A. Linux・Mac は /etc/hosts、Windows は C:\Windows\System32\drivers\etc\hosts です。「127.0.0.1 myapp.test」のように書くと、その名前が DNS より先に 127.0.0.1 に解決されます。編集には管理者権限が要ります。

Q. 127.0.0.2 や 127.1.2.3 も自分ですか? A. はい。127.0.0.0/8 全体がループバックで、手元では 127.5.5.5 で待ち受けたサーバーにそのアドレスで接続できました。ただし待ち受けに使ったアドレスと一致しないと届かず、127.0.0.1 からは拒否されました。

まとめ

  • localhost は「このコンピューター自身」を指す予約された名前で、hosts ファイルなどで 127.0.0.1(IPv6 は ::1)に変わる。手元の getaddrinfo は DNS に問い合わせなかった。
  • 127.0.0.0/8 は全部ループバックで、ホストの外に出ない。127.5.5.5 で待つサーバーも動くが、待ち受けに使ったアドレスと一致しないと届かない。
  • 127.0.0.1 で待つと自分の中だけ、0.0.0.0 で待つと LAN の IP でも届く。別端末(スマホ役)の 127.0.0.1 は別端末自身なので、何をしても PC には届かない。
  • :3000・:8080・:5173・:8000 はポート番号で、ツールごとの慣習。既定の待ち受けアドレスもツールで違い、Vite は 127.0.0.1、Express と Python の http.server は全部のアドレスだった。
  • Docker・WSL2 の中の localhost は別物。ブラウザは http://localhost をセキュアコンテキストとして扱い、LAN の IP では扱わない(実測 true/false)。

参考・出典

確認日はいずれも 2026年10月29日です。

  • IETF「RFC 6761 Special-Use Domain Names」6.3 Domain Name Reservation Considerations for "localhost.":https://www.rfc-editor.org/rfc/rfc6761#section-6.3
  • IETF「RFC 1122 Requirements for Internet Hosts」3.2.1.3 Addressing:https://www.rfc-editor.org/rfc/rfc1122#section-3.2.1.3
  • IETF「RFC 6890 Special-Purpose IP Address Registries」:https://www.rfc-editor.org/rfc/rfc6890
  • MDN「安全なコンテキスト」:https://developer.mozilla.org/ja/docs/Web/Security/Secure_Contexts
  • MDN「安全なコンテキストに限定されている機能」:https://developer.mozilla.org/ja/docs/Web/Security/Defenses/Secure_Contexts/features_restricted_to_secure_contexts
  • Node.js「DNS」dns.setDefaultResultOrder・dns.lookup:https://nodejs.org/api/dns.html
  • Vite「Server Options」server.host・server.port・server.strictPort:https://vite.dev/config/server-options
  • Next.js「next CLI」:https://nextjs.org/docs/app/api-reference/cli/next
  • Django「django-admin と manage.py」runserver:https://docs.djangoproject.com/ja/5.2/ref/django-admin/#runserver
  • Uvicorn「Settings」:https://www.uvicorn.org/settings/
  • Python「http.server」:https://docs.python.org/ja/3/library/http.server.html
  • Flask「Development Server」Address already in use:https://flask.palletsprojects.com/en/stable/server/
  • Spring Boot「Embedded Web Servers」Change the HTTP Port:https://docs.spring.io/spring-boot/how-to/webserver.html
  • Ruby on Rails「The Rails Command Line」:https://guides.rubyonrails.org/command_line.html
  • Angular「ng serve」:https://angular.dev/cli/serve
  • Jupyter「Running the Notebook」:https://docs.jupyter.org/en/latest/running.html
  • Apache Tomcat 11「RUNNING.txt」:https://tomcat.apache.org/tomcat-11.0-doc/RUNNING.txt
  • Jenkins「Installing Jenkins: WAR file」:https://www.jenkins.io/doc/book/installing/war-file/
  • Grafana「Configure Grafana」http_port:https://grafana.com/docs/grafana/latest/setup-grafana/configure-grafana/
  • IANA「Service Name and Transport Protocol Port Number Registry」8080 http-alt:https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml
  • Docker「Port publishing and mapping」:https://docs.docker.com/engine/network/port-publishing/
  • Docker Desktop「Explore networking how-tos」:https://docs.docker.com/desktop/features/networking/networking-how-tos/
  • Microsoft Learn「WSL を使用したネットワーク アプリケーションへのアクセス」:https://learn.microsoft.com/ja-jp/windows/wsl/networking

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「localhostとは|127.0.0.1・0.0.0.0との違いと:3000の意味」

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?