主机名与hosts配置:一次配对讲清楚

主机名(hostname)是服务器上最不起眼、又最容易埋雷的配置之一。它平时不显眼,一旦出问题,表现却千奇百怪:sudo 要卡好几秒、日志里的来源全是 unknown、邮件被判垃圾、MySQL 主从认证失败、容器里 hostname -f 返回空。这篇文章把这些现象串起来,讲清主机名、/etc/hosts、resolv 之间的真实关系,以及怎么一次性配对。

一、三个完全不同的东西,被叫成了同一个名字

日常说的「主机名」其实是三个独立概念,混为一谈是所有问题的起点:

  • 静态主机名:存在 /etc/hostname,由 hostnamectl set-hostname 写入,开机时被内核读取。它是「这台机器叫什么」的权威答案。
  • 内核主机名:hostname 命令返回的那个值,由 sethostname() 设置。它可以和静态文件不一致,重启后会被 /etc/hostname 覆盖。
  • FQDN(完全限定域名):hostname -f 返回的值。它不是「配置项」,是**查询结果**——系统拿静态主机名去 /etc/hosts 和 DNS 里查,能查到带点的名字才算 FQDN。

所以「hostname -f 返回空」几乎从来不是主机名没配,而是反查不到。这一步想通了,后面所有的坑都好理解。

二、hosts 文件到底怎么匹配

/etc/hosts 的格式是「IP 空格 名字1 名字2 ...」,一行可以写多个别名。关键规则:

127.0.0.1   localhost.localdomain localhost
127.0.1.1   web01.example.com web01
10.0.0.51   db01.internal db01
  • Debian/Ubuntu 用 127.0.1.1 而不是 127.0.0.1来映射自己的主机名,这是历史惯例,目的是让 localhost 保持纯粹的「本机回环」语义。CentOS/RHEL 习惯写 127.0.0.1。两种都能工作,但迁移系统时要留意。
  • 一行里的第一个名字会被当作「规范名」,后面的都是别名。写 127.0.1.1 web01 web01.example.com 时,hostname -f 可能返回 web01 而不是带点的那个,取决于实现与 /etc/nsswitch.conf 的配置。稳妥写法是把 FQDN 放第一位。
  • 同一个 IP 不要在多行重复出现(除非你明确知道后果)。多行会让反查产生歧义,getaddrinfo 返回顺序不确定,日志里同一台机器出现两个不同的名字,看日志时完全分不清。

三、nsswitch:谁先被问

/etc/nsswitch.conf 里的 hosts: 一行决定了查询顺序:

hosts:          files dns myhostname

这是 systemd 系统的默认值。三条的意义:

  1. files:查 /etc/hosts。
  2. dns:查 DNS。
  3. myhostname:systemd 提供的一个虚拟模块,保证「自己的主机名」永远能解析成功,即使 hosts 里没写、DNS 里也没有。这就是为什么有些机器 hosts 配得乱七八糟,本机名还能解析——是 myhostname 兜住了。

如果 myhostname 不在列表里(一些精简镜像或手动改过的系统),而 hosts 又没有自己的主机名,那么任何需要解析自身名字的操作都要走 DNS,走 DNS 就要等超时。这就是第一节说的「sudo 卡几秒」的根因:sudo 会去解析主机名做日志记录,解析失败要等 timeout 秒。/etc/resolv.conf 里 timeout:5 × attempts:2 × 两个不可达的 nameserver,加起来就是十几秒的卡顿。

诊断命令:

getent hosts "$(hostname)"
getent hosts "$(hostname -s)"
hostname -f
nsswitch_result=$(grep '^hosts:' /etc/nsswitch.conf); echo "$nsswitch_result"

用 getent 而不是 ping 或 host:getent 走的正是应用层用的那条 NSS 链路,能用它解析出来,应用就一定能。这个排查习惯值得养成。

四、反查:为什么日志里全是 IP

很多服务(Apache/Nginx 的访问日志、Postfix、SSH、/var/log/auth.log)会做反向解析,把 IP 变成主机名。反查走的是 in-addr.arpa 域,需要 PTR 记录。

  • 内网 IP 几乎永远没有 PTR 记录,所以内网服务的日志里出现 IP 是正常的,不是故障。
  • 关掉反查能显著提速:Nginx 的 access_log 默认不做反查,但 Apache 默认 HostnameLookups Off,若被打开会每个请求都查一次,QPS 上来直接拖垮。SSH 的 UseDNS yes 也是登录慢的经典原因,改 no。
  • Postfix 的 smtpd_helo_required 与反查:自己搭的发信服务如果 PTR 是 vps-provider-1234.cloud.net 这种机器名,很容易被判垃圾。PTR 应该设成发信域名的 A 记录对应的名字,且正反必须一致。

五、容器里的主机名是另一套规则

Docker 容器的 hostname 默认是容器 ID 的前 12 位。容器内 hostname -f 通常返回空或返回那个 ID,/etc/hosts 由 Docker 自动生成:

127.0.0.1   localhost
::1         localhost ip6-localhost ip6-loopback
172.17.0.2  3f8a9c2b1d04

几个实际影响:

  • 容器内改 hostname 不会影响其它容器。要用可预测的名字做服务发现,得用自定义网络 + --name,Docker 的内置 DNS(127.0.0.11)会按容器名解析。
  • 不要把宿主机的 /etc/hosts 挂进容器来「解决」解析问题。宿主机 hosts 里可能有 127.0.1.1 web01,进了容器 127.0.1.1 指的是容器自己,解析结果全错。
  • /etc/resolv.conf 在容器里是 Docker 生成的,写的是宿主机的 nameserver。宿主机用 systemd-resolved 时容器会拿到 127.0.0.53,而容器里的 127.0.0.53 没有 resolved 在监听——表现为「容器内 DNS 完全不通」。解法是在 /etc/docker/daemon.json 里指定 dns 为真实的 DNS 服务器 IP。

六、容器之外:这些服务对主机名敏感

部署前值得检查一遍,避免上线后才发现:

  • MySQL 主从:CHANGE MASTER TO MASTER_HOST='db01' 里的名字会在从库的 mysql.slave_master_info 里存成字符串。改了主机名或 hosts 映射,复制会断,报错却只有 Last_IO_Error: error connecting。建议复制链路一律用 IP 或稳定的内网域名,不用主机名。
  • PostgreSQL 的 pg_hba.conf:可以写主机名,但每次连接都会解析一次,解析变慢时连接池会占满。用 IP + CIDR 更稳。
  • systemd 的 After= 与 Restart:不依赖主机名,但 Environment 里如果写了 HOST=$(hostname) 这种,改主机名后服务行为会变。
  • 证书:如果证书是用主机名签的(内网 mTLS),改主机名会让证书不匹配,且报错在客户端,服务端日志里看不出来。

七、一次配对的完整清单

新机器到手的标准动作,六步:

# 1) 设置静态主机名(短名,不带域名)
hostnamectl set-hostname web01

# 2) /etc/hosts 写入 FQDN + 短名,FQDN 在前
printf '127.0.1.1\tweb01.example.com web01\n' >> /etc/hosts

# 3) 确认 nsswitch 有 myhostname
grep -q '^hosts:.*myhostname' /etc/nsswitch.conf || \
  sed -i 's/^hosts:.*/hosts: files dns myhostname/' /etc/nsswitch.conf

# 4) 关掉 SSH 反查
sed -i 's/^#\?UseDNS.*/UseDNS no/' /etc/ssh/sshd_config

# 5) 验证三条链路
hostname -f
getent hosts web01
hostname --all-ip-addresses

# 6) 时间同步(主机名无关,但新机器几乎都漏)
timedatectl set-ntp true

第 2 步用 >> 追加是危险的——重复执行会写两行,造成前面说的「同一 IP 多行」。生产脚本里要先判断再写:

grep -q "web01.example.com" /etc/hosts || printf '127.0.1.1\tweb01.example.com web01\n' >> /etc/hosts

hostnamectl set-hostname 在 Docker 容器里会失败(无 systemd),这时用 hostname web01 临时设置,并在启动脚本里固定,或者干脆在 docker run 时加 --hostname。

八、改主机名之后要连带做的事

主机名不是改完就结束,通常有四处要跟着动:

第一,日志目录。有些服务的日志路径包含主机名(如 Graylog/Filebeat 的 host 字段),历史日志和新日志会分裂成两个来源,报表里看是两个主机。

第二,监控与告警。Prometheus 的 instance 标签、Zabbix 的主机对象都是按名字注册的。改名前先在监控里加一个别名,或者干脆保留旧名字为 alias,否则改名那一刻告警会「消失」——不是恢复了,是变成了一个没人认识的新对象。

第三,备份脚本。备份文件名里带主机名的,恢复时会发现对不上;更糟的是保留策略按名字做去重,改名后策略失效,磁盘被撑爆。

第四,SSH known_hosts。不用主机名连的话没影响,但如果 ~/.ssh/config 里有 Host web01 的别名,改名后所有自动化脚本的连接目标都要同步更新。

一个稳妥的做法是:服务器的静态主机名一旦上线就不要改。需要对外用别的名字,就加 HostAlias 或在 DNS 里加别名记录。主机名是身份,身份变了牵连的系统比想象中多。

九、从主机名能看出的两类隐患

主机名配错的机器,通常还伴随另一个问题:时间不同步。原因是同一个——机器在初始化的最后一步被跳过了。检查方法很简单,两条命令:

timedatectl status | grep -E "System clock|NTP service|Time zone"
chronyc tracking 2>/dev/null || ntpq -p 2>/dev/null

如果 NTP service 显示 inactive,那么这台机器上的所有日志时间戳都会逐渐漂移,跨机器的日志按时间排序会彻底错乱;证书校验、JWT 过期、数据库主从的时间字段也会出现莫名其妙的问题。这些故障的表现和主机名毫无关系,根因却往往是同一批初始化步骤被漏掉。

第二类隐患是 hosts 文件被应用偷偷改写。有些面板、某些商业软件的安装脚本会往 /etc/hosts 里追加内容,把某个域名指向 127.0.0.1 或者一个已经下线的旧 IP。这类记录的破坏力极大:它优先级高于 DNS,且不会有任何报错提示。表现是「明明 DNS 改了,机器上就是访问到老服务器」。定期核对是值得的:

grep -vE '^\s*#|^\s*$' /etc/hosts | grep -vE '^(127\.0\.0\.1|127\.0\.1\.1|::1)\s'

这条命令过滤掉注释、空行和三个标准回环行,剩下的每一条都应该是你自己写进去的。多出来的,就是某个软件留下的。上线前的标准动作里加这一条,比事后排查「为什么改了 DNS 不生效」便宜得多。

另外提醒一点:/etc/hosts 的修改不需要重启任何服务就会立即生效,因为每次解析都重新读文件。这既是好事(改完马上能验证),也是个陷阱——如果你在调试时临时加了一条,很容易忘了删,几个月后它以「莫名其妙访问到测试环境」的形式回来找你。临时改动一律加 # TEMP 2026-10-29 这样的注释,并配套一个到期检查。

十、小结

主机名问题的本质是「一次查询链路的多个环节被当成了一个配置项」。排查顺序永远是:先 hostname 看内核值,再 getent hosts 看解析链路,再 hostname -f 看 FQDN 是否成立,最后才是看 hosts 文件内容。反过来从 hosts 文件开始看,往往会陷在「明明写了为什么还不行」的困惑里,因为真正的答案在 nsswitch.conf 的顺序、或者 resolv.conf 的超时设置里。

三句话记住:静态名进 /etc/hostname、解析进 /etc/hosts 的 127.0.1.1 行、FQDN 只是查询结果不是配置。把这三件事分清,绝大多数「主机名怪问题」都能在五分钟内定位。

Last modification:October 29th, 2026 at 08:26 pm

Leave a Comment