技术知识文章集合TECHNICAL ARCHIVE · 457 DOCUMENTS

显示模式

登录
ARCHIVE DOCUMENTBE

回环地址与"本机":一个被说简单了的概念

所属馆藏
Backend
文件格式
Markdown
原始路径
Backend/回环地址与本机的概念:一张网卡、一个相对身份、一道物理边界
本文目录6 个章节
  1. 一、回环是一张网卡,不是一个地址
  2. 二、"本机"是相对的:容器里的 127.0.0.1 是容器自己
  3. 三、"本机"有多个地址,绑哪个决定谁能进
  4. 四、为什么绑 127.0.0.1 就"公网免疫"
  5. 五、SSH 隧道:把"机器内部"延伸到你身边
  6. 总结

回环地址与"本机":一个被说简单了的概念

Category(分类): Backend Status: 已验证

"127.0.0.1 就是本机"——这句话人人都说过,但它其实是一层高度压缩的简称。压缩掉的信息里藏着三个容易踩坑的细节:

  1. 回环不是"一个地址",是内核里一张真实存在的虚拟网卡;
  2. "本机"是个相对概念——容器里的 127.0.0.1 指的是容器自己,不是宿主机;
  3. "本机"有多个地址(回环、内网、网桥),服务绑哪一个,决定谁能访问它。

本文在一台真实服务器(Ubuntu,跑着 mihomo 代理和 Docker 化的 Nuxt 应用)上逐条实测,把这三个细节展开讲清楚。

案例环境:

项目
操作系统Ubuntu 系 Linux(腾讯云)
mihomo 代理mixed-port 7890,管理 API 9897
Nuxt 应用Docker 容器,映射 127.0.0.1:3031:3031
Nginx宿主机安装,443 反代到 127.0.0.1:3031

一、回环是一张网卡,不是一个地址

执行 ip addr show lo,能看到机器上编号为 1 的第一张网卡:

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever

关键在 inet 127.0.0.1/8 这个 /8:整个 127.0.0.0/8 网段——从 127.0.0.1127.255.255.254,共 1600 多万个地址——全都挂在这张名叫 lo 的虚拟网卡上。IPv6 的对应物是 ::1

发往这些地址的数据包不经过物理网卡、不离开机器,在内核网络栈里转一圈直接回到接收方——这就是"回环"(loopback)名字的由来。它不是为了"快"而设计的捷径,而是操作系统自我通信的基础设施:ping 127.0.0.1 测的是本机协议栈,sshd、数据库、代理这些本机互访的服务都靠它。

但要注意:服务绑定的是具体地址,不是"本机"这个概念。回环网卡上有 1600 万个地址,一个服务只绑其中 127.0.0.1 时,用同属回环的 127.0.0.2 去访问它是进不去的:

# mihomo 管理 API 只绑定了 127.0.0.1:9897

$ curl -s -m 2 http://127.0.0.1:9897/version
{"message":"Unauthorized"}          # 能敲到门(401 是因为没带密钥,TCP 是通的)

$ curl -s -m 2 http://127.0.0.2:9897/version
                                    # 连接直接失败,静默无响应

第一个请求拿到了 HTTP 响应(哪怕是 401),说明 TCP 通了;第二个请求连 TCP 握手都完不成。这就是"绑了 127.0.0.1"和"绑了整个回环"的区别。

二、"本机"是相对的:容器里的 127.0.0.1 是容器自己

这是容器时代最容易踩的坑。先看实测——在宿主机上启动一个临时容器,从容器内部分别访问宿主机的两个地址:

$ docker run --rm node:24-bookworm-slim node -e "
const t=(name,url)=>fetch(url,{signal:AbortSignal.timeout(2000)})
  .then(r=>r.text()).then(x=>console.log(name,'-> OK:',x))
  .catch(e=>console.log(name,'-> FAIL:',e.cause?.code||e.message));

t('容器内访问 127.0.0.1:9897', 'http://127.0.0.1:9897/version');
t('容器内访问 172.17.0.1:9897', 'http://172.17.0.1:9897/version');
"

输出:

容器内访问 127.0.0.1:9897 -> FAIL: ECONNREFUSED
容器内访问 172.17.0.1:9897 -> FAIL: ECONNREFUSED

两项都失败了,但失败的原因完全不同。

第一项 ECONNREFUSED:容器拥有自己独立的网络栈、自己的 lo 网卡。容器内写 127.0.0.1,内核把它理解为"容器自己的回环",而容器的回环上什么服务都没跑,所以连接被拒绝。不是网络不通,是目的地根本不是你以为的那个

实际后果:代理地址怎么写

这台服务器上,Docker 构建阶段需要走宿主机的 mihomo 代理下载依赖。如果代理地址写成这样:

HTTP_PROXY=http://user:pass@127.0.0.1:7890   # ❌ 错误

构建容器里的 127.0.0.1 指向构建容器自己,代理请求会全部失败。正确写法是借道 docker 网桥网关:

# docker-compose.yml
extra_hosts:
  - "host.docker.internal:host-gateway"
# .proxy.env
HTTP_PROXY=http://user:pass@host.docker.internal:7890   # ✅ 正确

host-gateway 会让 Docker 把 host.docker.internal 解析成网桥地址(本机为 172.17.0.1),这是"容器访问宿主机服务"的标准姿势。验证一下:

$ docker run --rm --add-host=host.docker.internal:host-gateway \
    -e http_proxy=http://user:pass@host.docker.internal:7890 \
    alpine sh -c 'grep host.docker /etc/hosts && wget -qO- --timeout=12 http://ifconfig.me'

172.17.0.1    host.docker.internal
107.173.83.251        # ← 这是代理的海外出口 IP,链路打通

出口 IP 是代理出口而不是本机真实 IP,证明请求确实经过了宿主机的 mihomo。

三、"本机"有多个地址,绑哪个决定谁能进

宿主机同时拥有好几个身份,都算"本机":

地址网卡身份
127.0.0.1lo回环,只有机器内部能到达
10.0.20.14eth0腾讯云 VPC 内网地址
172.17.0.1docker0Docker 网桥网关,容器眼里的"宿主机"
43.136.76.115(公网映射)真实公网 IP

ss -tlnp 一眼看清这台机器上两个服务的绑定差异:

LISTEN 127.0.0.1:9897   users:(("mihomo",...))   # 管理 API:只绑回环
LISTEN *:7890           users:(("mihomo",...))   # 代理入口:绑所有网卡
LISTEN 127.0.0.1:3031   users:(("docker-proxy"...))  # Nuxt:只绑回环

绑定地址与可访问范围的对应关系:

监听写法含义回环内进程本机容器(经 172.17.0.1)公网
127.0.0.1:7890只在回环网卡上开门
172.17.0.1:7890只在 docker 网桥上开门
0.0.0.0:7890(即 *:7890每一张网卡上开门✅(防火墙放行则通)

所以这台机器上:mihomo 的 7890 必须绑 0.0.0.0,因为构建容器要从 172.17.0.1:7890 进来——如果它只绑 127.0.0.1,Docker 构建立刻断网;而管理 API 9897 只绑 127.0.0.1,构建容器和公网都够不到它,这正是想要的效果。

Nuxt 的端口映射也是同一课

# docker-compose.yml
ports:
  - "127.0.0.1:3031:3031"

这行的含义:容器内 3031 → 映射到宿主机回环地址的 3031。于是公网扫描 43.136.76.115:3031 直接连不通(实测返回连接失败),唯一入口是宿主机 Nginx:

# /etc/nginx/sites-available/techarticle
location / {
    proxy_pass http://127.0.0.1:3031;
}

请求链路:浏览器 → Nginx(443, HTTPS) → 本机回环 3031 → 容器。应用躲在 Nginx 身后,TLS、限流、日志都在 Nginx 层统一处理。

四、为什么绑 127.0.0.1 就"公网免疫"

很多人以为这是防火墙规则——不是。它比防火墙更底层:协议规定目的地为 127.0.0.0/8 的包不允许离开主机,任何路由器、交换机收到都会直接丢弃。外部世界的包在物理上就不存在投递到别人家回环网卡的路径。

用第三方探测节点实测(check-host.net 多国节点 TCP 探测):

43.136.76.115:22    ✅ TCP连通 (3/3 节点)     ← SSH,安全组放行
43.136.76.115:9897  ❌ 超时 (3/3 节点)        ← 只绑回环 + 安全组拦截
43.136.76.115:7890  ❌ 超时 (3/3 节点)        ← 云安全组拦截
43.136.76.115:3031  ❌ 连接失败                ← 只绑回环

两种拦截机制的区别值得体会:

防火墙 / 云安全组绑定 127.0.0.1
工作层面网络包过滤规则服务监听地址 + 协议约定
端口在公网"可见"吗可见(能完成 TCP 握手再被丢)不可见(无路可走)
误配风险规则加错就开了几乎不可能被误开

所以生产服务的标准姿势是双保险:服务绑 127.0.0.1,防火墙/安全组默认拒绝。任何一层失误都还有另一层兜底。

五、SSH 隧道:把"机器内部"延伸到你身边

服务只绑回环意味着必须"人在机器里"才能访问。SSH 隧道解决的就是这个问题:

ssh -L 9897:127.0.0.1:9897 root@43.136.76.115

这条命令的含义:SSH 登录服务器后,sshd 在服务器内部发起去 127.0.0.1:9897 的连接——起点就在机器里面,走的是合法的内部路径;同时你本地的 9897 端口通过加密的 SSH 通道与之对接。之后在本地浏览器打开:

http://127.0.0.1:9897/ui

流量路径是:你的浏览器 → 本机回环 → SSH 加密隧道(穿越公网) → 服务器内部的回环 → 目标服务。对 mihomo 来说,访问它的连接来自本机的 sshd,天经地义;对你来说,"必须在机器内部"这个限制被消除了——人在哪,机器内部就延伸到哪

隧道断开(关掉 SSH 窗口),入口立即消失,无需改任何配置。这也是管理类服务(面板、数据库、监控)最推荐的暴露方式:零证书、零续期、零公网暴露。

总结

"127.0.0.1 就是本机"没有错,但展开后是四句更精确的话:

  1. 回环是网卡不是地址lo 网卡挂着整个 127.0.0.0/8,服务绑哪个具体地址,决定回环内的哪些请求能到达;
  2. "本机"是相对的:容器有独立网络栈,容器里的 127.0.0.1 是容器自己;容器访问宿主机服务要走 host.docker.internal(网桥网关);
  3. "本机"有多个地址:回环、内网、网桥都是本机,服务绑哪个决定谁能访问——mihomo 的 7890 绑 0.0.0.0 是为了让容器进来,9897 绑 127.0.0.1 是为了把所有人挡在外面;
  4. 回环绑定是物理免疫:127.x 的包不允许离开主机,这不是防火墙,是协议层面的无路可走;需要远程访问时用 SSH 隧道,把"机器内部"延伸到自己身边。
457 DOCUMENTS · 10 COLLECTIONS
ARCHIVE SEARCH457 篇文章

SEARCH GUIDE

输入关键词开始搜索

支持搜索文章标题、所属分类和原始文档路径。

按分类浏览

10 COLLECTIONS