📝
TailscaleでローカルのViteサーバーに繋がらなかった時のメモ
iPhoneで開発中のWebアプリを確認したくて、Tailscale使ったらデータ表示されなかった(CORSエラーで詰まった)のでメモです。
(この記事はClaudeCodeがほぼ書きました。
正直、iPhoneでSSHをしてTailscaleでMacにつないでClaudeCodeで開発していると、とても見通しが悪いため、自分用にmdドキュメントをzennにプッシュして、自分で内容を確認する意味合いが強い記事です💦 AI駆動開発をスマホで行うと、見通しが悪いので苦肉の策として..)
ローカル環境
- Mac (M2)
- iPhone (Tailscale経由)
- Vite dev server (port 5180)
- PHP API (port 8181)
問題
tailsclaeでMacにiPhoneからSSHを通してchromeで開発中のwebアプリにアクセスすると403エラー:
Request failed with status code 403
APIは動いてるのに、なぜかデータが表示されない。
原因
CORSの許可リストにTailscaleのIPが入ってなかった。
$allowedOrigins = [
'http://localhost:5173',
'http://localhost:3000',
// ← ここに http://[TAILSCALE_IP]:5180 がない
];
解決方法
1. Viteの設定
vite.config.ts
:
server: {
host: true, // これ重要!
port: 5180,
proxy: {
'/api': {
target: 'http://localhost:8181',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '')
}
}
}
2. ❌ 危険な一時対応(やってはいけない)
最初はこんな風に書いてしまった(ClaudeCodeがw 「そりゃあかんでしょ」とツッコミ入れました):
// 絶対にこのまま本番に上げてはいけない
if (true) { // TODO: 本番では戻す
header("Access-Control-Allow-Origin: *");
}
TODOで「本番では戻す」なんて、確実に忘れる。危険すぎる。(これは人間のコメント笑)
3. ✅ 環境変数による適切な設定
.env の設定
# 環境設定
APP_ENV="development"
TAILSCALE_IP="1xx.1xx.1xx.3x" # MacのTailscale IP
cors_header.php の改善
// 環境変数による適切な分岐
$isDevelopment = $_ENV['APP_ENV'] === 'development' || $_ENV['APP_ENV'] === 'local';
if ($isDevelopment) {
// 開発環境: Tailscale IPを動的に追加
$tailscaleIp = $_ENV['TAILSCALE_IP'] ?? '';
if ($tailscaleIp) {
$allowedOrigins[] = "http://{$tailscaleIp}:5180";
$allowedOrigins[] = "https://{$tailscaleIp}:5180";
}
// 開発用のlocalhostも許可
if (isset($_SERVER["HTTP_ORIGIN"]) &&
(in_array($_SERVER["HTTP_ORIGIN"], $allowedOrigins) ||
strpos($_SERVER["HTTP_ORIGIN"], 'http://localhost:') === 0)) {
header("Access-Control-Allow-Origin: " . $_SERVER["HTTP_ORIGIN"]);
} else {
header("HTTP/1.1 403 Forbidden");
}
} else {
// 本番環境: 厳格なチェック
if (isset($_SERVER["HTTP_ORIGIN"]) &&
in_array($_SERVER["HTTP_ORIGIN"], $allowedOrigins)) {
header("Access-Control-Allow-Origin: " . $_SERVER["HTTP_ORIGIN"]);
} else {
header("HTTP/1.1 403 Forbidden");
}
}
デバッグに使ったテストページ
シンプルなHTMLで問題の切り分け:
<!DOCTYPE html>
<html>
<body>
<button onclick="test()">Test API</button>
<div id="result"></div>
<script>
async function test() {
const res = await fetch('/api/basics.php?timestamp=958550400');
const data = await res.json();
document.getElementById('result').innerText = JSON.stringify(data);
}
</script>
</body>
</html>
環境ごとの設定例
開発環境
APP_ENV="development"
TAILSCALE_IP="1xx.1xx.1xx.3x"
ステージング環境
APP_ENV="staging"
TAILSCALE_IP=""
本番環境
APP_ENV="production"
TAILSCALE_IP=""
メモ
-
host: true
を忘れると外部からアクセスできない - 開発環境でも完全に無制限にはしない
- TODOコメントに頼らず、環境変数で確実に分岐させる
- Tailscale IPは.envで管理すれば、リポジトリに含めなくて済む
まとめ
以上、次に同じことで詰まらないための備忘録。
Discussion