🔧 Mac Miniをサーバーとして使う時に知っておくべきこと — スリープ&Tailscaleトラブルシューティング
Mac Mini M4をリモート開発サーバーとして使っていたところ、突然SSH、VNC、Tailscaleがすべて繋がらなくなりました。外部からいくら接続を試みてもConnection timed out… 🥲
原因は意外と単純でしたが、診断の過程で学んだことがかなり多かったですよ。Macをサーバーとして運用している方のお役に立てれば嬉しいです!
📋 環境情報
| 項目 | 値 |
|---|---|
| 機器 | Mac Mini M4, 16GB, macOS Sequoia |
| SSHポート | 22(デフォルト)/ カスタムポート(外部用) |
| VNC | socatによるポートリダイレクト |
| Tailscale | インストール・設定済み |
| ルーター | ipTIME、ポートフォワーディング設定済み |
🔍 症状:すべてのリモート接続が一斉にダウン
MacBookから外部ネットワーク経由でSSH接続を試みました。
ssh -p {port} {user}@{ddns-host}
# → Connection timed out
ルーターのポートフォワーディングは正常。では内部からは?同じネットワーク上のWindows PCからMac Miniのポートを確認しました。
# TCPポートチェック(PowerShell)
$tcp = New-Object System.Net.Sockets.TcpClient
$tcp.ConnectAsync("192.168.0.xxx", {port}).Wait(5000)
# → True(ポートは開いている!)
ポートは開いているのに… SSHバナーが返ってこない!
# SSHバナー確認試行
$stream = $tcp.GetStream()
$stream.ReadTimeout = 5000
$buffer = New-Object byte[] 256
$bytesRead = $stream.Read($buffer, 0, 256)
# → Timeout! バナーなし
22番、カスタムポート、VNCポート、すべて同じ症状です。TCP接続は成功しますが、裏側の実際のサービスが応答していない状態でした。
Tailscaleのステータスも確認:
tailscale status
# → offline, last seen 1d ago
Tailscaleもオフライン。Mac Mini自体に何か問題がありそうですよ。
🎯 原因:macOSスリープモード
犯人はmacOSのスリープモードでした!
Mac Miniが一定時間アイドル状態になると、自動的にスリープに入り、すべてのネットワークサービスが停止してしまいます。
macOSの
launchdがソケットをlisten状態で維持していますが、スリープモードでは実際のサービスプロセス(sshdなど)を生成しません。そのためTCP 3ウェイハンドシェイクは成功しますが、その後のデータ交換ができないのです。
macOSの「ネットワークアクセスによるスリープ解除」オプションは、通常のSSH/TCPパケットでは動作しません。WOLマジックパケット(FF FF FF FF FF FF + MACアドレス×16)を送信する必要があります。それでもOSレベルのサービス復旧が不完全な場合がありますよ。
🛠️ WOL(Wake on LAN)の使い方
Mac Miniがスリープしてしまった場合に備えて、WOLマジックパケットの送信方法も整理しておきますよ。
MACアドレス確認(同じネットワーク上のWindows PCから):
Get-NetNeighbor -IPAddress 192.168.0.xxx | Select-Object LinkLayerAddress
WOLマジックパケット送信(PowerShell):
$mac = '{MACアドレス-ハイフン除去}'
$macBytes = [byte[]]@(0..5 | ForEach-Object {
[Convert]::ToByte($mac.Substring($_ * 2, 2), 16)
})
$magicPacket = [byte[]](@(0xFF) * 6) + ($macBytes * 16)
for ($i = 0; $i -lt 5; $i++) {
$udpClient = New-Object System.Net.Sockets.UdpClient
$udpClient.Connect("192.168.0.255", 9)
$udpClient.Send($magicPacket, $magicPacket.Length) | Out-Null
$udpClient.Close()
Start-Sleep -Milliseconds 300
}
ipTIMEルーターの管理ページからもWOL送信が可能ですよ。
✅ 解決方法
ステップ1:スリープを完全に無効化
サーバー用途なら、スリープを完全にオフにする必要がありますよ。
sudo pmset -a sleep 0 # システムスリープ無効化
sudo pmset -a disablesleep 1 # スリープ完全ブロック
sudo pmset -a displaysleep 10 # ディスプレイのみ10分後にオフ
設定確認:
pmset -g | grep -i sleep
# SleepDisabled 1
# sleep 0
# displaysleep 10
ステップ2:Tailscaleの自動起動を登録
Tailscaleはデフォルトでは再起動後に自動起動しません!ログイン項目に登録する必要がありますよ。
osascript -e 'tell application "System Events" to make login item at end with properties {path:"/Applications/Tailscale.app", hidden:false}'
確認:
osascript -e 'tell application "System Events" to get the name of every login item'
# → Tailscale
ステップ3:SSH LaunchDaemonの確認
カスタムポートSSHはLaunchDaemonで管理されています。再起動後に手動ロードが必要な場合:
# ⚠️ launchctl loadは非推奨!
# 最新macOSではbootstrapを使用
sudo launchctl bootstrap system /Library/LaunchDaemons/com.custom.sshd-{port}.plist
🏗️ 最終アーキテクチャ:SSH二重ポート戦略
| ポート | 用途 | アクセス範囲 | 備考 |
|---|---|---|---|
| 22 | 内部トラブルシューティング用 | 内部ネットワークのみ | macOSデフォルトリモートログイン |
| カスタム | 外部リモートアクセス用 | 外部(ポートフォワーディング) | カスタムLaunchDaemon |
22番を残す理由は、外部からカスタムポートが使えない時に、内部のWindows PCから22番でMac Miniに接続してトラブルシューティングできるからです。ルーターのポートフォワーディングに22番は含まれていないので、外部への露出も心配ありませんよ。
両方のポートともlaunchdがソケットをlistenし、接続時のみsshdプロセスを生成するため、リソースの無駄もありません。
📡 接続ガイドまとめ
外部から(Tailscale推奨):
# SSH
ssh -p {port} {user}@{tailscale-ip}
# VNC (Finder ⌘+K)
vnc://{tailscale-ip}:5900
外部から(ルーターポートフォワーディング):
# SSH
ssh -p {port} {user}@{ddns-host}
# VNC
vnc://{ddns-host}:{vnc-port}
内部ネットワークから:
# SSH(デフォルトポート)
ssh {user}@192.168.0.xxx
# VNC
vnc://192.168.0.xxx:5900
💡 重要な教訓まとめ
1. pmset -a sleep 0 && pmset -a disablesleep 1は必須!スリープモードに入るとリモート接続がすべて死にますよ。
2. WOL(Wake on LAN)は万能ではありません。ハードウェアは起こせても、OSレベルのサービスが正しく復旧しない場合がありますよ。
3. SSH二重ポート戦略が有効です。外部用+内部トラブルシューティング用を分離すれば、緊急時に内部から復旧可能!
4. Tailscaleはログイン項目に登録しないと、再起動後に自動起動しませんよ。
5. launchctl loadは非推奨!最新macOSではlaunchctl bootstrapを使いましょう。
6. ルーターポートフォワーディング+Tailscaleの二重化で接続の安定性を確保しましょう。
Mac Miniをサーバーとして運用して経験した、最もビックリしたトラブルシューティングでした。「なぜ全部死んだの?」と思ったら、答えはスリープモード… 😅 同じ状況の方がいらっしゃったら、まずpmsetの設定を確認してみてくださいね!