Skip to content

Instantly share code, notes, and snippets.

@segfo
Last active August 28, 2025 00:22
Show Gist options
  • Select an option

  • Save segfo/2e88a3e77e3d2fb5cefc79d527330224 to your computer and use it in GitHub Desktop.

Select an option

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
@segfo
Copy link
Author

segfo commented Aug 26, 2025

🌐 tailscale funnel で HTTPS 公開する方法

✅ 前提条件

  1. Tailscale Funnel が Tailnet で有効になっている
    → 有効化は Funnel設定ページ から行います
  2. MagicDNS と HTTPS Certificates を有効にしている
    • 管理画面の「DNS」設定で確認できます

🛠️ コマンド例

tailscale funnel --bg --https=443 http://127.0.0.1:8080/
  • --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/


🔒 セキュリティ注意点

  • Funnel は 誰でもアクセス可能な公開手段です
  • 認証が必要なサービス(Vaultwardenなど)は、必ずパスワードや2FAを設定しましょう
  • 公開を停止したいときは:
tailscale funnel --https=443 off

📌 まとめ

やりたいこと コマンド例
Tailnet内限定公開(Serve) tailscale serve --bg --https 443 http://127.0.0.1:8080/
インターネット公開(Funnel) tailscale funnel --bg --https=443 http://127.0.0.1:8080/

もし tailscale funnel 実行時に「Funnel is not enabled」などのメッセージが出たら、管理画面で有効化してみてください。

@segfo
Copy link
Author

segfo commented Aug 26, 2025

はじめに

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 を複数回呼び出すことで対応可能。

@segfo
Copy link
Author

segfo commented Aug 26, 2025

Tailscaleのログインセッションについて

10日くらい継続して接続されているとき、サーバが再起動すると再認証が必要になる。
再認証は以下のコマンドで行う
sudo tailscale login

デーモンのステータス確認

sudo systemctl status tailscale-funnel.service

デーモンの再起動

sudo systemctl restart tailscale-funnel.service

@segfo
Copy link
Author

segfo commented Aug 26, 2025

.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:latest

CAの初期化とサイト毎に証明書を作成するスクリプト

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

@segfo
Copy link
Author

segfo commented Aug 26, 2025

スマホのモバイル回線からPi-HoleにDNS解決させる方法

コンセプト

Tailscale VPNにスマホを参加させて、スプリットトンネルを構成します。
DNSクエリのみをVPNに通過させて、解決させてそれ以外のトラフィックは直接インターネットに出すことでVPN側に不要なトラフィックを発生させません。もう一つのメリットとして通信パケットのカプセル化が必要以上にされないと思うので(諸説)通信(量|料)の節約にもつながるかも。
広告ブロックのみを主眼に置いた構成です。

Tailscaleの設定

  1. Tailscaleの管理画面のDNS設定内の「MagicDNS」を有効にする
  2. sudo tailscale ipを確認する
  3. 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で処理できます。

@segfo
Copy link
Author

segfo commented Aug 27, 2025

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;
    }
}

@segfo
Copy link
Author

segfo commented Aug 27, 2025

ディレクトリ構成

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データ>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment