Tailscaleで自前のDNSと独自ドメインでのHTTPSでの接続を実現する

## はじめに
最近、外出先から自宅サーバーへとSSHを繋ぎたいことがあってTailscale VPNを導入しましたのですが、せっかくプライベートなネットワークなので自宅のサーバーに常駐するセルフホストのサービスを立ててみました。

しかし、残念なことにポート番号が覚えづらくてならない。

べつにVPNの中なのでわざわざドメインにIPを紐付けなくとも同じIPですし、なんならTailscaleの場合はマシンごとに勝手にドメインを割り当ててくれる(MagicDNS)のでDNSなんてわざわざ建てる必要ないな、なんて思っていたのですが、私の脳ではたった３つ程度のセルフホストのサービスのポート番号すら間違えてしまいます。

そんなわけで、先人たちに倣って私も自前のDNSを建てることにしました。

## 前提条件
- Ubuntu 24.04を利用しています
- 自前の使い道のないドメインを持っている前提です
- この方法ではドメインのネームサーバーはCloudflareである必要があります

(Cloudflareでドメインを管理していない場合は移管するなりLet's encryptのチャレンジを解決する用のサーバーを建てるなりしてもらう必要があります…)

## 自前のDNSを立てて、TailscaleのDNSに参照させる

### `Split DNS`の設定
まず、TailscaleのDNSには、`Split DNS`という便利な機能があります。これは、特定のドメインにアクセスする場合にのみ、特定のDNSを使用して名前解決を図る仕組みです。

ここでは、利用するドメインを`example.com`と仮定します。

Tailscaleにアクセスした後、`DNS`のタブの`Nameservers`にある`Add nameserver`をクリックします。その後、選択肢の中で`Custom`を選択すると設定できます。この際、`Restrict to domain
`のトグルをONにしてSplit DNSの機能を利用するようにしてください。

まず、ここにDNSを運用するサーバーのVPN上でのIPアドレスと、利用するドメインを登録しておきましょう。

### sytemd-resolvedと利用するポートが被る問題を解消
まず、Ubuntuの場合、DNSに利用したい53番ポートは`systemd-resolved`によって使用されています。これは、名前解決のキャッシュや`.local`でアクセスできるようなローカルネットワークのデバイスIPの名前解決を行ってくれる便利な機能なのですが、今回に限ってはこれの一部機能をOFFにする必要があります。本来であればNameserverのポート番号を指定できれば良い話ですが、残念ながらTailscaleではできないようです。

53番ポートを利用するために、systemd-resolvedのコンフィグを編集しましょう。

```sh
sudo bash -c "echo 'DNSStubListener=no' >> /etc/systemd/resolved.conf"
sudo systemctl restart systemd-resolved
```

### DNSサーバーを建てる

つぎに適当なDNSサーバーを立てます。設定が簡単なのは`dnsmasq`とかだと思います。

```sh
sudo apt update
sudo apt install dnsmasq
sudo bash -c "echo 'address=/sample.example.com/xxx.xxx.xxx.xxx' >> /etc/dnsmasq.conf"
sudo systemctl restart dnsmasq
```

おそらくこれで、dnsmasqが53番で動作していると思います。

### 動作確認
TailscaleでSplit DNSが機能し、dnsmasqを動作させコンフィグが適切に設定されていれば、これで名前の解決は可能になったはずです。試しに、digコマンドで確認してみましょう。

```sh
dig @127.0.0.1 sample.example.com #自分のサーバーで動いているDNS
dig @100.100.100.100 sample.example.com #tailscaleのDNS
dig sample.example.com #現在利用しているDNS
```

適切に設定されていれば、それぞれのコマンドで同じIPに辿り着くはずです。

### nginxとかで適当に振り分ける

例えば、セルフホストのサービスが`localhost:8080`で動作している場合は、以下のようになります。

```/etc/nginx/sites-available/sample.example.com
server {
    listen 80;
    server_name sample.example.com;

    location / {
        proxy_pass http://localhost:8080;
    }
}
```

設定後は以下のようにしてコンフィグを有効にします。

```sh
ln -s /etc/nginx/sites-available/sample.example.com /etc/nginx/sites-enabled
sudo nginx -t #コンフィグが正しいか確認
sudo systemctl restart nginx
```

## HTTPSで接続できるようにする

### そもそもいる？
セルフホストの、さらにVPNの中で動いているサービスを、わざわざHTTPSにする必要があるのかと疑問に思うかもしれません。
まあ、ぶっちゃけ必要ないのですが、普段使っているブラウザで危険なサイトとして扱われたりしてアクセスするのが面倒だったりします。それを回避するための手段と思ってもらってたら良いかもしれません。

### Let's encryptで署名付き証明書を手にいれる
HTTPSで通信するためには、適切に署名された証明書を手に入れる必要があります。しかしながら、これはあくまでVPNのサーバー。外界からは遮断され、よくある方法でDNSの所有権を証明することはできません。

そこで、ドメインのテキストレコードをプラグインで自動的に編集する方法をとります。一度証明書を取得するだけであれば手動で問題ないのですが、let's encryptの証明書の有効期限は三ヶ月です。三ヶ月ごとに毎回作業をするのはダルいので、今回は出回っているプラグインを利用する方法をとります。

```sh
sudo apt update
sudo apt install certbot python3-certbot-dns-cloudflare
mkdir -p ~/.secrets/certbot
touch ~/.secrets/certbot/cloudflare.ini
chmod 600 ~/.secrets/certbot/cloudflare.ini
```

最後に作成した`~/.secrets/certbot/cloudflare.ini`にはCloudflareのサイトで取得したAPIトークンを登録します。このAPIのトークンはDNSレコードの編集権限を持つだけのものが良いかと思います。
```ini:cloudflare.ini
dns_cloudflare_api_token = YOUR_CLOUDFLARE_API_TOKEN
```

さて、これでコマンドを実行できるようになるはずです。

```sh
sudo certbot certonly \
  --dns-cloudflare \
  --dns-cloudflare-credentials ~/.secrets/certbot/cloudflare.ini \
  -d example.com \
  -d *.example.com
```

ワイルドカードを利用して取得することで証明書取得の手間が省けますが、気になる場合は個別に取得していただいて構いません。

### 自動更新の設定をする

```ini:/etc/systemd/system/certbot-renew.service
[Unit]
Description=Certbot Renewal
Documentation=https://certbot.eff.org/
Wants=network-online.target
After=network-online.target

[Service]
Type=oneshot
ExecStart=/usr/bin/certbot renew --quiet --deploy-hook "/bin/systemctl reload nginx"

[Install]
WantedBy=multi-user.target
```

```ini:/etc/systemd/system/certbot-renew.timer
[Unit]
Description=Run Certbot renewal every day

[Timer]
OnCalendar=*-*-1 00:00:00
Persistent=true

[Install]
WantedBy=timers.target
```

以下のように登録します。

```sh
sudo systemctl daemon-reload
sudo systemctl enable certbot-renew.timer
sudo systemctl start certbot-renew.timer
```

これで、毎月1日に証明書の自動更新と、それに付随するnginxの再起動が行われます。

### nginxの設定を変更する
```/etc/nginx/sites-available/sample.example.com
server {
    listen 80;
    server_name sample.example.com;

    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    server_name sample.example.com;

    ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;

    location / {
        proxy_pass http://localhost:8080;
    }
}
```

書き換え後にnginxを再起動します。

```
sudo nginx -t
sudo systemct restart nginx
```

これで https://sample.example.com にアクセスすると、セルフホスト運用しているサービスへとアクセスできるはずです。

## 最後に
セルフホストのサービスの中で、特におすすめなのが[miniflux](https://miniflux.app/)です。マジでおすすめです。CSS改造すると、マジでいいですよ。皆さんもぜひ、自宅サーバーライフのご満喫を。

## 参考
https://chatgpt.com