尝试搭建家庭实验室内网
Shinwoo PARK
引言
我一直对家庭实验室和自托管抱有一种浪漫的憧憬。
虽然因为成本和实用性等问题,我没能长期维护服务器和自托管服务,但一直以来,我都在持续自托管各种服务,并乐在其中。拥有属于自己的服务让我感到很开心。
随着 AI 的出现,我逐渐习惯了和 AI 一起编写代码,也开始频繁地自己动手制作所需的东西。然后,我会把这些东西部署到自己的 Linux 服务器上运行。
其中一件让我感到不便的事是,我做的东西大多只供自己使用,所以即使让 AI 帮忙,在每项服务中加入注册和登录系统也很麻烦。尤其是每个服务都各自配置一套注册和登录系统后,管理账号也变得相当繁琐。
为了解决这些问题,我一直在使用 Cloudflare Tunnel 和 Access。
Cloudflare Tunnel 减轻了我对 nginx 和端口配置的顾虑,Access 则作为可靠的访问入口和统一的认证系统,解决了我的问题。
不过,在部队服役期间,我接触到了内网和统一登录系统,于是开始尝试在自己的服务器上搭建内网,取代 Tunnel 和 Access。
设计

Tailscale
所谓内网,是指只能在组织内部访问的网络。
它可以通过私人 VPN、特定骨干网络或专用网络来搭建。
在我的内网中,Tailscale 是核心。Tailscale 是一种私人 VPN,它会为通过认证的用户的多台设备分配 Tailscale IP,从而构建一个虚拟内网。

自托管 DNS
除此之外,我还想自托管 DNS。
当然,Tailscale 也提供 Magic DNS。不过它只能使用 *.ts.net 域名,而且域名还是随机分配的,用起来不太方便。
另外,自托管 AdGuard Home 还能在提供 DNS 服务的同时屏蔽广告,这是额外的好处。
好在,连接 Tailscale 后,可以通过 Tailscale 的 Custom DNS 设置,让接入内网的设备使用指定的 DNS,因此这个问题很容易解决。
自签名证书
搭建内网时,我想创建自己的 TLD。(例如 memo.psw、auth.psw……)
通常,证书是由受信任的 CA(Certificate Authority,证书颁发机构)签名后颁发的。
创建自己的 TLD 时会遇到以下问题:
- CA 不会为不存在的 TLD 颁发证书
- 由于这是内网环境,公共 CA 无法访问我的 TLD 域名或对其进行验证
因此,我需要自签名证书。
为此,我使用了 step-ca。
NGINX
最后,我需要在服务托管服务器上运行一个反向代理,将请求路由到各项服务。
当然还有许多其他选择,但我选择 NGINX 纯粹是因为用得顺手。
安装 Tailscale
在开始所有安装和设置之前,我先在所有设备和服务器上安装 Tailscale,为后续操作准备好最基础的私人 VPN。
Android、iOS、macOS、Windows 等客户端设备,只需从官网或应用商店下载 Tailscale 并登录即可。
对于 Linux 无头服务器,我使用了以下命令。
# 安装
curl -fsSL https://tailscale.com/install.sh | sh
# DNS 服务器 — 因为将使用自建 DNS
sudo tailscale up --accept-dns=false
# 服务服务器 — 普通连接
sudo tailscale up
# 查看各服务器的 Tailscale IP
tailscale ip -4
tailscale ip -4输出的 IP,就是 Tailscale 内网使用的 IP。
安装和配置 DNS 服务器
1. 安装 Adguard Home
我用 Docker Compose 简单地完成了安装。
# /opt/adguardhome/docker-compose.yml
services:
adguard-home:
image: adguard/adguardhome:latest
container_name: adguard-home
volumes:
- ./work:/opt/adguardhome/work
- ./conf:/opt/adguardhome/conf
restart: unless-stopped
network_mode: host
之后访问 <tailscale-ip>:3000,就会看到 Adguard Home 的初始设置页面。
完成初始设置后,:3000 端口会关闭,服务会通过 :80 提供。
初始设置时,需要设置管理员账号,并确认 DNS 端口为 53。
DNS 设置
完成初始设置(:3000)后,进入管理页面(:80),还需要进行几项配置。
1. 设置 DNS Rewrite(核心)
Web UI → Filters → DNS Rewrites → Add DNS Rewrite
添加以下 DNS 记录。
- *.psw → <服务服务器的 tailscale ip>
- ca.internal → <DNS 服务器的 tailscale ip>
psw 是我打算在内网中使用的 TLD。如果想使用其他 TLD 或域名,随意替换即可。

ca.internal 是用于 CA 请求的域名。如果想改用其他域名,也可以自行替换。不过,之后的配置中也必须始终使用修改后的域名。
2. 设置上游 DNS
Settings → DNS settings → Upstream DNS servers
https://1.1.1.1/dns-query
https://8.8.8.8/dns-query
设置上游 DNS 后,不属于内网域名的外部域名会被转发到公共 DNS。
也就是说,即使连接到内网,仍然可以访问外部网站。
2. 配置 Tailscale DNS
在 Tailscale Admin Console → DNS 标签页中进行设置。
Nameservers → Add nameserver → 点击 Custom
在 Nameserver IP 一栏中输入 DNS 服务器的 tailscale ip,然后保存。

Override DNS Servers 选项会忽略连接到 Tailscale 的设备所使用的默认 DNS。
如果不启用此选项,系统会优先使用本地 DNS,而不是 Tailscale DNS,这样安装 Adguard Home 的意义就不大了。
Adguard Home 也设置了上游 DNS,因此它和其他 DNS 一样可以正常使用,而且是安全的。
3. 搭建内部 CA
安装 step-ca 和 step CLI
#(查看最新版本:github.com/smallstep/certificates)
# step-ca
wget https://dl.smallstep.com/gh-release/certificates/gh-release-header/v0.27.4/step-ca_linux_0.27.4_amd64.tar.gz
tar xzf step-ca_linux_0.27.4_amd64.tar.gz
sudo mv step-ca /usr/local/bin/
# step CLI
wget https://dl.smallstep.com/gh-release/cli/gh-release-header/v0.27.4/step_linux_0.27.4_amd64.tar.gz
tar xzf step_linux_0.27.4_amd64.tar.gz
sudo mv step_0.27.4/bin/step /usr/local/bin/
初始化 CA
step ca init \
--name "Home Internal CA" \
--dns "ca.internal" \
--dns "<DNS 服务器的 tailscale ip>" \
--address "<DNS 服务器的 tailscale ip>:8443" \
--provisioner "admin@internal" \
--acme
运行后会生成以下文件。
~/.step/certs/root_ca.crt— 根 CA 证书~/.step/certs/intermediate_ca.crt— 中间 CA 证书~/.step/secrets/— 私钥
注册并启用 systemd 服务
sudo cat > /etc/systemd/system/step-ca.service << 'EOF'
[Unit]
Description=step-ca Internal CA
After=network.target
[Service]
User=step
ExecStartPre=/bin/sh -c 'until ip -4 addr show tailscale0 | grep -q <DNS 服务器的 tailscale ip>; do sleep 1; done'
ExecStart=/usr/local/bin/step-ca /home/step/.step/config/ca.json
Restart=on-failure
RestartSec=10
[Install]
WantedBy=multi-user.target
EOF
sudo useradd --system --home /home/step --create-home step
sudo cp -r ~/.step /home/step/
sudo chown -R step:step /home/step/.step
sudo systemctl enable --now step-ca
ACME Provisioner
使用以下命令确认是否存在 provisioner。
step ca provisioner list
# 应当存在 "acme" provisioner
如果没有,则使用以下命令添加。
step ca provisioner add acme --type acme
4. Nginx + 签发证书
这一步在服务服务器上进行。
安装 Nginx 和 certbot
sudo apt update && sudo apt install -y nginx certbot
创建 Nginx 配置
我的服务器上原本就安装并运行着 nginx 服务,因此为了区分对外开放的服务和内网服务,我创建了 /etc/nginx/intra.d 目录。
按如下方式创建 nginx 配置文件。
server {
listen <服务服务器的 tailscale ip>:80;
server_name <域名>;
location ^~ /.well-known/acme-challenge/ {
root /var/www/certbot;
default_type text/plain;
try_files $uri =404;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
// 首次签发证书后启用
// listen <服务服务器的 tailscale ip>:443 ssl;
listen <服务服务器的 tailscale ip>:443;
server_name <域名>;
// 首次签发证书后启用
// ssl_certificate /etc/letsencrypt/live/env.psw/fullchain.pem;
// ssl_certificate_key /etc/letsencrypt/live/env.psw/privkey.pem;
location / {
proxy_pass http://127.0.0.1:<服务端口>;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
签发证书时会使用 HTTP-01 和 webroot 方式,因此需要在 nginx 中配置 root 和 try_files,以提供验证文件。
首次签发证书之前,证书文件尚不存在,所以先不启用 SSL,只让服务监听 443 端口。证书签发完成后,再启用 SSL。
签发证书
假设已将 CA 初始化过程中生成的根证书复制到服务服务器上。
sudo certbot certonly \
--webroot -w /var/www/certbot \
--server https://ca.internal:8443/acme/acme/directory \
-d <域名>
运行后即可签发证书,certbot 也会自动续签。
也就是说,该域名首次签发证书后,就不必再操心后续续签等事宜。
不过,即使 certbot 更新了证书,nginx 也不会自动重启,因此可能会继续使用旧证书。
所以需要为 certbot 添加 hook,让它在证书续签后重新加载 nginx。
sudo mkdir -p /etc/letsencrypt/renewal-hooks/deploy
printf '#!/bin/sh\nset -e\nnginx -t\nsystemctl reload nginx\n' \
| sudo tee /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh >/dev/null
sudo chmod 755 /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
5. 安装根证书
现在到了最后一步。
即使证书已经签发,我们自己创建的 CA 默认仍不受信任。
如果不安装根证书就连接 Tailscale 并访问配置了 nginx 的域名,浏览器会提示证书不受信任。
因此,我们需要告知所使用的设备:“签发此证书的机构是可信的。”
这就是安装根证书这一步的目的。
iPhone(iOS)
- 将根证书传到手机上。
- 在文件中找到并打开根证书,会出现“已下载描述文件”的提示。
- 进入设置并安装描述文件。
- 设置 → 通用 → 关于本机 → 证书信任设置
- 在“针对根证书启用完全信任”部分,将 Home Internal CA 的开关打开。
Android
- 将根证书传到手机上。
- 在文件中找到并打开根证书,Android 会检测到证书安装并显示安装提示。
注意:如果要开发需要访问内网的应用,应用不会自动采用上述证书设置。因此,在应用开发过程中,需要将证书添加到网络安全配置中。
<network-security-config>
<base-config cleartextTrafficPermitted="false">
<trust-anchors>
<certificates src="system" />
<certificates src="user" />
</trust-anchors>
</base-config>
</network-security-config>
Windows
- Win + R → 输入 certmgr.msc
- 受信任的根证书颁发机构 → 右键单击“证书” → 所有任务 → 导入
- 选择根证书文件 → 将其存储在“受信任的根证书颁发机构”存储区
也可以使用 PowerShell 一次性完成。
Import-Certificate -FilePath "root_ca.crt" `
-CertStoreLocation Cert:\LocalMachine\Root
搭建内网的感想
我最初知道 Tailscale,是因为想找一种在远程开发时更方便地访问本地开发服务器的方法。
不过,Tailscale 的隧道和 MagicDNS 对于我最初的用途来说显得太复杂、太麻烦了。在我还没来得及深入了解 Tailscale 是什么服务之前,就把它忘了。
然而,一想到要搭建内网,我第一个想到的就是 Tailscale。最终,我认为它确实是最好的选择。
如果 Tailscale 价格昂贵,我大概就会放弃了。
不过,得益于它非常慷慨的政策——只限制可以加入群组的用户数量,而设备注册数量不受限制——我得以实现搭建内网的梦想。
此外,我也开始想进一步学习网络知识。
搭建内网时,我只理解了网络的整体流向,却没有弄清楚 Tailscale 具体如何工作,也不明白 Linux 中的 DNS 是怎样变化的,就这样完成了配置。
我相信,深入了解 Tailscale 一定会对积累计算机知识大有帮助。