-
-
Save segfo/2e88a3e77e3d2fb5cefc79d527330224 to your computer and use it in GitHub Desktop.
| # 同じディレクトリに .env を作ってください。 | |
| # .envのサンプルはgistのコメントに書きます。 | |
| services: | |
| # パスワードマネージャ 不要なら消してね | |
| vaultwarden: | |
| image: vaultwarden_arm64:1.34.3 | |
| container_name: vaultwarden | |
| user: '1000:1000' | |
| ports: | |
| - 8080:80 | |
| environment: | |
| - DOMAIN=${VAULTWARDEN_DOMAIN} | |
| - SIGNUPS_ALLOWED=${VAULTWARDEN_SIGNUPS_ALLOWED} | |
| - ROCKET_PORT=80 | |
| - EXPERIMENTAL_CLIENT_FEATURE_FLAGS=ssh-key-vault-item,ssh-agent | |
| volumes: | |
| - ./vw-data:/data | |
| restart: unless-stopped | |
| # DNS型広告ブロッカー | |
| pihole: | |
| image: pihole/pihole:latest | |
| container_name: pihole | |
| cap_add: | |
| - NET_ADMIN | |
| environment: | |
| - TZ=${TZ} | |
| - PIHOLE_UID=${PUID} | |
| - PIHOLE_GID=${PGID} | |
| volumes: | |
| - ./etc/pihole:/etc/pihole | |
| - ./etc/dnsmasq.d:/etc/dnsmasq.d | |
| ports: | |
| - "53:53/tcp" | |
| - "53:53/udp" | |
| restart: unless-stopped | |
| # pi-holeのUI用のリバプロ。TLS通信の終端の役割をしている。不要なら消してね | |
| nginx: | |
| image: nginx:latest | |
| container_name: nginx | |
| depends_on: | |
| - pihole | |
| ports: | |
| - 10443:443 | |
| volumes: | |
| - ./nginx/conf.d:/etc/nginx/conf.d | |
| - ./nginx/certs:/etc/nginx/certs | |
| restart: unless-stopped | |
| # ローカルCA、生成したTLS証明書をNginxに食わせる | |
| stepca: | |
| image: smallstep/step-ca | |
| container_name: stepca | |
| volumes: | |
| - ./step-ca:/home/step | |
| ports: | |
| - 8443:443 # ACME endpoint | |
| restart: unless-stopped | |
| networks: | |
| internal_net: | |
| driver: bridge |
はじめに
Vaultwarden や他のサービスにも応用できる、汎用的な Tailscale Funnel 自動起動構成のまとめ
🧩 全体構成概要
| 要素 | 内容 |
|---|---|
| サービス名 | tailscale-funnel.service |
| バックエンド | http://127.0.0.1:8080/(Vaultwardenなど) |
| 公開方法 | Tailscale Funnel 経由で HTTPS 公開 |
| 起動方式 | systemd による自動起動(OS起動時) |
| 安定化処理 | tailscaled とバックエンドの起動待機、リトライ処理あり |
🛠️ スクリプト /usr/local/bin/enable-funnel.sh
#!/bin/bash
# 最大5回まで tailscaled とバックエンドの起動を待つ
for i in {1..5}; do
if systemctl is-active --quiet tailscaled; then
# バックエンドが応答可能か確認
if curl -s http://127.0.0.1:8080/ > /dev/null; then
# Funnel 設定を適用
tailscale funnel --https=443 http://127.0.0.1:8080/
exit 0
else
echo "Backend not responding yet, retrying in 3s..."
sleep 3
fi
else
echo "tailscaled not active yet, retrying in 3s..."
sleep 3
fi
done
echo "Failed to enable funnel after retries"
exit 1⚙️ systemd ユニット /etc/systemd/system/tailscale-funnel.service
[Unit]
Description=Enable Tailscale Funnel on boot
After=network-online.target tailscaled.service
Wants=network-online.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/enable-funnel.sh
RemainAfterExit=true
[Install]
WantedBy=multi-user.target🔁 有効化と再読み込みコマンド
sudo systemctl daemon-reexec
sudo systemctl daemon-reload
sudo systemctl enable tailscale-funnel.service
sudo systemctl start tailscale-funnel.service✅ 動作確認コマンド
tailscale funnel status期待される出力例:
Funnel is enabled
Serving HTTPS on port 443
Proxying / → http://127.0.0.1:8080/
✨ 応用ポイント
-
/vaultwardenのみ公開したい場合:tailscale serve --https=443 /vaultwarden http://127.0.0.1:8080/vaultwarden
-
複数サービスを公開する場合は
/service1,/service2のようにserveを複数回呼び出すことで対応可能。
Tailscaleのログインセッションについて
10日くらい継続して接続されているとき、サーバが再起動すると再認証が必要になる。
再認証は以下のコマンドで行う
sudo tailscale login
デーモンのステータス確認
sudo systemctl status tailscale-funnel.service
デーモンの再起動
sudo systemctl restart tailscale-funnel.service
.envのサンプル
compose.ymlとCA初期化・運用スクリプトと共用です。
.env
# CA 設定
CA_DNSNAME=local-ca.local
CA_NAME=LocalCA
# ディレクトリ(スクリプトの場所を基準に補完)
STEP_CA_DIR=step-ca
CERT_DIR=nginx/certs
# サーバ情報
SERVERS="server.local"
O=Local
OU=Local
# Step CLI / CA
STEP_CA_URL=https://$CA_DNSNAME:8443
STEP_CLI_IMAGE=smallstep/step-cli:latest
STEP_CA_IMAGE=smallstep/step-ca:latestCAの初期化とサイト毎に証明書を作成するスクリプト
CAの初期化
環境依存の内容は compose.yml と同じディレクトリの .env に書いてください。
ca-setup.sh
#!/bin/bash
set -eu
# スクリプトの存在するディレクトリ
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && cd ../ && pwd)"
# .env の読み込み
source "$SCRIPT_DIR/.env"
# 相対パスを補完
STEP_CA_DIR="$SCRIPT_DIR/$STEP_CA_DIR"
CERT_DIR="$SCRIPT_DIR/$CERT_DIR"
PASSWORD_FILE="$STEP_CA_DIR/secrets/password"
mkdir -p "$STEP_CA_DIR/secrets"
docker compose down stepca || true
# 自動パスワード生成
openssl rand -base64 32 > "$PASSWORD_FILE"
chmod 600 "$PASSWORD_FILE"
# Step CA を非対話で初期化
docker run --rm \
-v "$STEP_CA_DIR":/home/step \
"$STEP_CA_IMAGE" \
step ca init \
--deployment-type="standalone" \
--name "$CA_NAME" \
--dns "$CA_DNSNAME" \
--address ":443" \
--provisioner "admin@$CA_DNSNAME" \
--password-file /home/step/secrets/passwordサイト毎の証明書作成
create-cert.sh
#!/bin/bash
set -eu
# スクリプトの存在するディレクトリ
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && cd ../ && pwd)"
# .env の読み込み
source "$SCRIPT_DIR/.env"
# 相対パスを補完
STEP_CA_DIR="$SCRIPT_DIR/$STEP_CA_DIR"
CERT_DIR="$SCRIPT_DIR/$CERT_DIR"
PASSWORD_FILE="$STEP_CA_DIR/secrets/password"
mkdir -p "$CERT_DIR"
docker compose up stepca -d
for SERVER in $SERVERS; do
echo "=== request cert for: $SERVER ==="
# CSR / キー作成
docker run --rm -v "$CERT_DIR":/certs alpine:3.18 \
sh -c "apk add --no-cache openssl >/dev/null 2>&1 && \
openssl req -new -newkey ec:<(openssl ecparam -name secp521r1) -nodes \
-keyout /certs/$SERVER.key \
-out /certs/$SERVER.csr \
-subj '/C=JP/O=$O/OU=$OU/CN=$SERVER' \
&& chmod 600 /certs/$SERVER.key && chmod 644 /certs/$SERVER.csr"
# CSR を CA に送信して署名
docker run --rm \
-v "$STEP_CA_DIR":/home/step:ro \
-v "$CERT_DIR":/certs \
--network host \
"$STEP_CLI_IMAGE" \
step ca sign /certs/$SERVER.csr /certs/$SERVER.crt \
--ca-url "$STEP_CA_URL" \
--root /home/step/certs/root_ca.crt \
--password-file /home/step/secrets/password \
--not-after 24h \
--force
done
docker compose down nginx;docker compose up nginx -d最後、定期的にTLS証明書を自動更新するスクリプト
crontab -e でこれを実行するようにしておくと、12時間ごとに証明書が更新されます。
* */12 * * * /path/to/server/scripts/cert-renew-and-reload.sh > /dev/null 2>&1
cert-renew-and-reload.sh
#!/bin/bash
set -eu
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && cd ../ && pwd)"
source "$SCRIPT_DIR/.env"
# 相対パスを絶対パスに補完
STEP_CA_DIR="$SCRIPT_DIR/$STEP_CA_DIR"
CERT_DIR="$SCRIPT_DIR/$CERT_DIR"
PASSWORD_FILE="$STEP_CA_DIR/secrets/password"
mkdir -p "$CERT_DIR"
changed=false
for SERVER in $SERVERS; do
echo "=== request cert for: $SERVER ==="
CRT_FILE="$CERT_DIR/$SERVER.crt"
KEY_FILE="$CERT_DIR/$SERVER.key"
TMP_CRT="$CERT_DIR/.${SERVER}.crt.tmp"
TMP_KEY="$CERT_DIR/.${SERVER}.key.tmp"
docker run --rm \
-v "$STEP_CA_DIR":/home/step:ro \
-v "$CERT_DIR":/certs \
--network host \
"$STEP_CLI_IMAGE" \
step ca certificate "$SERVER" "/certs/.${SERVER}.crt.tmp" "/certs/.${SERVER}.key.tmp" \
--ca-url "$STEP_CA_URL" \
--root /home/step/certs/root_ca.crt \
--not-after 24h \
--password-file /home/step/secrets/password || {
echo "warning: certificate request failed for $SERVER"
continue
}
chmod 644 "$TMP_CRT" || true
chmod 600 "$TMP_KEY" || true
mv -f "$TMP_CRT" "$CRT_FILE"
mv -f "$TMP_KEY" "$KEY_FILE"
echo "issued/updated: $CRT_FILE $KEY_FILE"
changed=true
done
if [ "$changed" = true ]; then
echo "reloading nginx container..."
docker exec nginx nginx -s reload || {
echo "warning: nginx reload failed"
}
else
echo "no certificate changes; skip reload"
fiスマホのモバイル回線からPi-HoleにDNS解決させる方法
コンセプト
Tailscale VPNにスマホを参加させて、スプリットトンネルを構成します。
DNSクエリのみをVPNに通過させて、解決させてそれ以外のトラフィックは直接インターネットに出すことでVPN側に不要なトラフィックを発生させません。もう一つのメリットとして通信パケットのカプセル化が必要以上にされないと思うので(諸説)通信(量|料)の節約にもつながるかも。
広告ブロックのみを主眼に置いた構成です。
Tailscaleの設定
- Tailscaleの管理画面のDNS設定内の「MagicDNS」を有効にする
sudo tailscale ipを確認する- Tailscaleの管理画面のDNS設定内の「Global nameservers」に2で確認したIPアドレスを書く
Pi-Holeの設定
Pi-Hole > SYSTEM > Settings > DNS(DNS Settings画面) で作業をします
DNS Settings
設定モードをExpertモードにしてください(Basicモードだと設定できません)
Interface settings > Potentially dangerous optionsブロックの
Permit all origins を選択します。(まぁファイアウォールで制御していれば問題なしです)
これで、TailscaleからのDNSリクエストをPi-holeで処理できます。
Nignxの設定ファイル
# nginx が $connection_upgrade を理解するように map を追加
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 443 ssl;
server_name server.local;
ssl_certificate /etc/nginx/certs/server.local.crt;
ssl_certificate_key /etc/nginx/certs/server.local.key;
# Docker の内部DNS を使う(コンテナ内では 127.0.0.11 が Docker DNS)
resolver 127.0.0.11 valid=30s;
# upstream を変数で指定すると起動時に解決しない(リクエスト時に解決される)
set $upstream_host "pihole:80";
location / {
proxy_pass http://$upstream_host;
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;
# WebSocket 等が必要なら以下も
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}
}
ディレクトリ構成
server
├── .env <環境変数(compose.ymlとLocalCA構築・運用スクリプト共用)>
├── compose.yml
├── etc <piholeのフォルダ>
│ ├── dnsmasq.d
│ └── pihole
├── logs <TLS証明書の再発行スクリプトのログなど>
├── nginx <TLSの終端・リバースプロキシ>
│ ├── certs <各サーバの証明書>
│ │ ├── server.crt
│ │ ├── server.csr
│ │ └── server.key
│ └── conf.d <リバプロの構成ファイル>
│ └── pihole.conf <pihole用>
├── scripts
│ ├── ca-setup.sh
│ ├── cert-renew-and-reload.sh
│ └── create-cert.sh
├── step-ca <scripts/ca-setup.shを実行すると作成される>
└── vw-data <vaultwardenのDBデータ>
🌐
tailscale funnelで HTTPS 公開する方法✅ 前提条件
→ 有効化は Funnel設定ページ から行います
🛠️ コマンド例
--bg:バックグラウンドで実行--https=443:Funnel経由で HTTPS 公開(Let's Encrypt証明書が使われます)http://127.0.0.1:8080/:ローカルで動いているサービス(Vaultwardenなど)このコマンドを実行すると、Tailscale が自動で HTTPS URL を発行してくれます。
例:
https://your-device.tailnet-name.ts.net/🔒 セキュリティ注意点
📌 まとめ
tailscale serve --bg --https 443 http://127.0.0.1:8080/tailscale funnel --bg --https=443 http://127.0.0.1:8080/もし
tailscale funnel実行時に「Funnel is not enabled」などのメッセージが出たら、管理画面で有効化してみてください。