ホストにCaddyを入れてTailscale HTTPS経由でサービス公開する手順

TSDProxyを利用する方法は、Dockerで提供しているサービスを手軽にhttps化出来るので便利ですが、サービスの数だけTailscaleのノードを使用してしまいます。個人用途なら制限には引っかからないでしょうが、今度はTailscale側の管理が大変になります。
その場合は、以前にも紹介したCaddyを利用するほうが、Tailscaleへの登録は1つで済むのですっきりしますね。

前提

  • TailscaleでHTTPS証明書が使える状態(管理コンソールでHTTPS Certificates有効化済み)
  • Tailscaleを止めるとSSH接続が切れる可能性があるため、Tailscaleは常に動かしたまま作業する

1. Caddyインストール

sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl

curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg

curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list

sudo apt update && sudo apt install caddy -y

2. tailscale serveが動いていたら止める

先にCaddyを起動する前に確認・停止する。
tailscale serveが443を占有しているとCaddyが起動できない)

# 確認
tailscale serve status

# 動いていたら停止(Tailscale自体は止めない)
sudo tailscale serve reset

3. Tailscaleのドメイン名を確認

# operator設定(以後sudoなしで使える)
sudo tailscale set --operator=$USER

# ドメイン確認
tailscale status --json | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('CertDomains','') or d.get('MagicDNSSuffix',''))"
# 例: ['dellnt.tailf682b.ts.net']

4. TLS証明書の取得

# カレントディレクトリに証明書が生成される
sudo tailscale cert ホスト名.tailXXXXX.ts.net

# Caddy用ディレクトリに移動
sudo mkdir -p /etc/caddy/certs
sudo mv ホスト名.tailXXXXX.ts.net.crt ホスト名.tailXXXXX.ts.net.key /etc/caddy/certs/
sudo chown -R caddy:caddy /etc/caddy/certs
sudo chmod 600 /etc/caddy/certs/ホスト名.tailXXXXX.ts.net.key
sudo chmod 644 /etc/caddy/certs/ホスト名.tailXXXXX.ts.net.crt

5. 使用中ポートの確認

# 追加したいポート帯が空いているか確認
sudo ss -tlnp | grep -E '443|4443|4444|4445'

LXDコンテナのIPも確認:

lxc list

6. Caddyfileの設定

sudo nano /etc/caddy/Caddyfile

caddyfile

# サービス1つめ(例:Linkwarden)
ホスト名.tailXXXXX.ts.net:4443 {
    tls /etc/caddy/certs/ホスト名.tailXXXXX.ts.net.crt /etc/caddy/certs/ホスト名.tailXXXXX.ts.net.key
    reverse_proxy コンテナIP:サービスのポート
}

# サービス2つめ以降は同じパターンでポートを増やす
ホスト名.tailXXXXX.ts.net:4444 {
    tls /etc/caddy/certs/ホスト名.tailXXXXX.ts.net.crt /etc/caddy/certs/ホスト名.tailXXXXX.ts.net.key
    reverse_proxy コンテナIP:サービスのポート
}

ポイント:

  • ポートは 4443, 4444, 4445... と連番で増やす
  • LXDコンテナ内のサービスは lxc list で確認したIPを使う
  • ホスト自身のサービス(Cockpitなど)はTailscale IP(100.x.x.x:ポート)を使う
  • CockpitのようにHTTPS動作しているサービスは tls_insecure_skip_verify が必要:

caddyfile

ホスト名.tailXXXXX.ts.net:4443 {
    tls /etc/caddy/certs/ホスト名.tailXXXXX.ts.net.crt /etc/caddy/certs/ホスト名.tailXXXXX.ts.net.key
    reverse_proxy https://100.x.x.x:9090 {
        transport http {
            tls_insecure_skip_verify
        }
    }
}

7. 反映・起動

# 構文チェック
sudo caddy validate --config /etc/caddy/Caddyfile

# 初回起動
sudo systemctl start caddy
sudo systemctl enable caddy

# 2回目以降の設定変更時はreload(サービスを止めずに反映)
sudo systemctl reload caddy

# 状態確認
sudo systemctl status caddy

8. サービス追加時のルーティン

# 1. 空きポート確認
sudo ss -tlnp | grep -E '4443|4444|4445|4446'

# 2. コンテナIP確認
lxc list

# 3. Caddyfileに追記
sudo nano /etc/caddy/Caddyfile

# 4. チェックして反映
sudo caddy validate --config /etc/caddy/Caddyfile && sudo systemctl reload caddy

トラブルシュート

症状原因対処
Caddyが起動しないポートが使用中sudo ss -tlnpで確認して別ポートに変更
「安全でない」と表示tailscale serveが残っているsudo tailscale serve reset
パスで飛ばされるtailscale serveが443を占有同上
Cockpitに繋がらないCockpitがHTTPSで動いているtls_insecure_skip_verifyを追加
証明書エラー証明書の期限切れsudo tailscale certで再取得してreload

おまけ:1コピペで

nano caddy-tailscale-setup.sh
# 下記を貼り付け
bash caddy-setup.sh
cat << 'SCRIPT' > caddy-setup.sh
#!/bin/bash
set -e

echo "=== Caddy + Tailscale HTTPS セットアップ ==="

# --- 1. tailscale serve 停止 ---
echo "[1/6] tailscale serve を確認・停止..."
if tailscale serve status 2>/dev/null | grep -q "proxy"; then
    echo "  -> tailscale serve が動いています。停止します。"
    sudo tailscale serve reset
else
    echo "  -> tailscale serve は動いていません。スキップ。"
fi

# --- 2. Caddy インストール ---
echo "[2/6] Caddy をインストール..."
if command -v caddy &>/dev/null; then
    echo "  -> Caddy は既にインストール済みです。スキップ。"
else
    # 壊れたリポジトリを一時退避
    sudo find /etc/apt/sources.list.d/ -name "*.list" -exec grep -l "Release ファイルがありません\|InRelease\|amdgpu\|rocm" {} \; 2>/dev/null | while read f; do
        sudo mv "$f" "/tmp/$(basename $f).bak" && echo "  -> 退避: $f"
    done

    sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https curl
    curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
    curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list
    sudo apt update && sudo apt install caddy -y
fi

# --- 3. フルドメイン取得 ---
echo "[3/6] Tailscale ドメインを取得..."
sudo tailscale set --operator=$USER 2>/dev/null || true
DOMAIN=$(tailscale status --json | python3 -c "
import sys, json
d = json.load(sys.stdin)
certs = d.get('CertDomains') or d.get('MagicDNSSuffix')
if isinstance(certs, list) and certs:
    print(certs[0].strip(\"[]\").strip(\"'\").strip())
elif isinstance(certs, str):
    print(certs.strip())
" 2>/dev/null)

if [ -z "$DOMAIN" ]; then
    echo "  -> ドメイン取得に失敗しました。Tailscaleが起動しているか確認してください。"
    exit 1
fi
echo "  -> ドメイン: $DOMAIN"

# --- 4. TLS証明書の取得・配置 ---
echo "[4/6] TLS証明書を取得..."
sudo mkdir -p /etc/caddy/certs
cd /tmp
sudo tailscale cert "$DOMAIN"
sudo mv "/tmp/${DOMAIN}.crt" "/tmp/${DOMAIN}.key" /etc/caddy/certs/
sudo chown -R caddy:caddy /etc/caddy/certs
sudo chmod 600 "/etc/caddy/certs/${DOMAIN}.key"
sudo chmod 644 "/etc/caddy/certs/${DOMAIN}.crt"
echo "  -> 証明書を /etc/caddy/certs/ に配置しました。"

# --- 5. 空きポートとDockerサービスを自動検出 ---
echo "[5/6] Dockerサービスと空きポートを検出..."

# 使用中ポートを取得
USED_PORTS=$(sudo ss -tlnp | awk '{print $4}' | grep -oE '[0-9]+$' | sort -n | uniq)

# 4443から空きポートを順番に割り当てる関数
NEXT_PORT=4443
get_next_port() {
    while echo "$USED_PORTS" | grep -q "^${NEXT_PORT}$"; do
        NEXT_PORT=$((NEXT_PORT + 1))
    done
    echo $NEXT_PORT
    USED_PORTS="$USED_PORTS
$NEXT_PORT"
    NEXT_PORT=$((NEXT_PORT + 1))
}

# Dockerが使えるか確認
DOCKER_BLOCKS=""
if command -v docker &>/dev/null && docker ps &>/dev/null 2>&1; then
    echo "  -> Dockerコンテナを検出中..."
    while IFS= read -r line; do
        CONTAINER=$(echo "$line" | awk '{print $NF}')
        PORTS=$(echo "$line" | grep -oE '0\.0\.0\.0:[0-9]+->[0-9]+' | head -1)
        if [ -n "$PORTS" ]; then
            HOST_PORT=$(echo "$PORTS" | cut -d: -f2 | cut -d- -f1)
            CADDY_PORT=$(get_next_port)
            DOCKER_BLOCKS="${DOCKER_BLOCKS}
# ${CONTAINER}
# ${DOMAIN}:${CADDY_PORT} {
#     tls /etc/caddy/certs/${DOMAIN}.crt /etc/caddy/certs/${DOMAIN}.key
#     reverse_proxy localhost:${HOST_PORT}
# }
"
            echo "  -> ${CONTAINER}: localhost:${HOST_PORT} -> :${CADDY_PORT}"
        fi
    done < <(docker ps --format "table {{.Ports}}\t{{.Names}}" | tail -n +2 | grep "->")
else
    DOCKER_BLOCKS="# (Dockerが見つかりませんでした。手動で追加してください。)"
fi

# --- 6. Caddyfile テンプレート生成 ---
echo "[6/6] Caddyfile を生成..."

sudo tee /etc/caddy/Caddyfile > /dev/null <<EOF
# ============================================================
# Caddy リバースプロキシ設定
# ドメイン : ${DOMAIN}
# 証明書   : /etc/caddy/certs/${DOMAIN}.crt
#
# サービス追加手順:
#   1. 空きポート確認 : sudo ss -tlnp | grep 444
#   2. コンテナIP確認 : lxc list  または  docker ps
#   3. 下のブロックのコメントを外して編集
#   4. sudo caddy validate --config /etc/caddy/Caddyfile && sudo systemctl reload caddy
#
# テンプレート(コピーして使う):
# ------------------------------------------------------------
# # サービス名
# ${DOMAIN}:XXXX {
#     tls /etc/caddy/certs/${DOMAIN}.crt /etc/caddy/certs/${DOMAIN}.key
#     reverse_proxy localhost:ホストポート
# }
#
# # Cockpitなどホスト上のHTTPSサービスの場合:
# ${DOMAIN}:XXXX {
#     tls /etc/caddy/certs/${DOMAIN}.crt /etc/caddy/certs/${DOMAIN}.key
#     reverse_proxy https://TailscaleIP:9090 {
#         transport http {
#             tls_insecure_skip_verify
#         }
#     }
# }
# ============================================================

# ---- 自動検出されたDockerサービス(コメントを外して有効化) ----
${DOCKER_BLOCKS}
EOF

sudo caddy validate --config /etc/caddy/Caddyfile

sudo systemctl enable caddy
sudo systemctl restart caddy
sudo systemctl --no-pager status caddy | head -20

echo ""
echo "======================================"
echo "✅ セットアップ完了!"
echo "  ドメイン : ${DOMAIN}"
echo "  証明書   : /etc/caddy/certs/${DOMAIN}.crt"
echo "  Caddyfile: /etc/caddy/Caddyfile"
echo ""
echo "次のステップ:"
echo "  sudo nano /etc/caddy/Caddyfile  でコメントを外して有効化"
echo "  sudo caddy validate --config /etc/caddy/Caddyfile && sudo systemctl reload caddy"
echo "======================================"
SCRIPT

chmod +x caddy-setup.sh
echo "作成完了: ./caddy-setup.sh"

完了後は sudo nano /etc/caddy/Caddyfile を開くとコメント付きテンプレートが入っています。Dockerコンテナを自動検出してポートマッピングを読み取り、全部コメントアウト済みでCaddyfileに書き出します。使いたいサービスの#を外して reload するだけです。

Outline


なお、OutlineはDexで認証しているため、内部の設定を変更しないとうまく通信が出来ません。
Outlineの例で言えば、下記あたりで対処出来ると思います。
・docker-compose.yml で 127.0.0.1:内部ポート:コンテナポート にする
・Caddyで外部ポート → 内部ポートに中継する
・反映は reload がダメなら restart

Cockpit

Cockpitを他のマシンにも追加する場合は同じ /etc/cockpit/cockpit.conf の設定が必要です。
ログイン画面は出るけどログイン後に落ちるパターンになる可能性があります。
CockpitはWebSocketを使うので、Caddyにヘッダー設定が必要です。
また、/etc/cockpit/cockpit.conf でOriginsを許可しないとCockpit側がプロキシ経由のアクセスを拒否します。

Caddyfileのcockpitブロック

sudo nano /etc/caddy/Caddyfile
sudo mkdir -p /etc/cockpit
sudo tee /etc/cockpit/cockpit.conf > /dev/null <<EOF
[WebService]
Origins = https://ser5.tailf682b.ts.net:5557
ProtocolHeader = X-Forwarded-Proto
AllowUnencrypted = true
EOF

sudo systemctl reload cockpit
タイトルとURLをコピーしました