我把 Minecraft Java 版伺服器放在家裡的電腦上,朋友從外網用一個固定的公網位址就能連進來。伺服器跑在虛擬機裡的 Docker 中,跟日常使用的系統隔開。外網那一端只放一台很小的 AWS 機器,負責把連接埠轉回家。遊戲行程和世界存檔都留在家裡。
這篇只涵蓋 Java 版。Java 版走 TCP 25565,反向 SSH 隧道轉得動。基岩版(Bedrock)走的是 UDP,同一條隧道轉不了。下面的遊戲版本和 AWS 免費額度,以 2026 年 10 月的情況來寫。
最後長這樣
1 | 朋友的 Minecraft 客戶端 |
家裡的虛擬機開機後自己連去 AWS。連線是從家裡打出去的,所以家用路由器不用做任何連接埠轉發,電信把用戶放在 CGNAT 後面也沒關係。朋友連的是 AWS 的位址。世界存檔留在家裡的硬碟上。
比較過的幾種做法
| 做法 | 它解決什麼 | 卡在哪 |
|---|---|---|
| Port mapping(連接埠轉發) | 路由器把外網的 25565 轉到家裡某台電腦 | 很多家用線路沒有真正的公網 IP,外面連不進來。連得進來時,家裡的連接埠也直接暴露在外網,IP 一變朋友存的位址就失效 |
| Virtual Server(虛擬伺服器) | 同上。這是不少家用路由器韌體裡的名字,背後還是連接埠轉發 | 跟 Port mapping 是同一件事 |
| DDNS | 給會變的家用 IP 一個固定網域名稱 | 只解決「位址會變」。連不進家裡、以及家裡連接埠對外開放,兩件事都還在 |
| Reverse SSH tunnel(反向 SSH 隧道) | 家裡主動連上一台有公網 IP 的小機器,由那台機器代收 25565 | 多一台常開的小機器,以及它的公網 IPv4 費用 |
我用反向 SSH 隧道。家裡沒有穩定的公網 IP,也不想為了一台遊戲伺服器去改路由器、把家裡網路露出去。
為什麼外面只放一台小機器
Minecraft 吃 CPU 和記憶體的地方在家裡。AWS 上那台機器只轉 TCP,t2.micro(1 vCPU、1 GiB 記憶體)就夠。它不跑 Java,也不存世界。
免費額度要看帳號是什麼時候開的,以 AWS 文件 為準:
| 帳號開立時間 | 額度 |
|---|---|
| 2025-07-15 之前 | 開通後 12 個月。每個月 750 小時的 t2.micro 或 t3.micro,以及搭配 EC2、使用中的公網 IPv4 750 小時 |
| 2025-07-15 當日或之後 | 改成點數制,最高約 200 美元,用完或滿 6 個月為止。主機要選當時標成 free tier eligible 的機型,新帳號通常是 t3.micro 這一代,不一定還有 t2.micro |
公網 IPv4 從 2024-02-01 起每小時 0.005 美元,一個位址全月大約 3.65 美元。舊額度的 750 小時通常蓋得住「掛在一台運行中的 EC2 上的那一個位址」。下面兩種情況會計入帳單:
- 執行個體停了,Elastic IP 還掛在上面
- 帳號裡另外躺著沒在用的 Elastic IP
這台跳板要一直開著。朋友存的是它的位址,機關掉,伺服器就從外網消失。
任何有公網 IPv4 的小 VPS 都能擔任同一個角色。後面的 SSH 設定不依賴 AWS。
家裡:用虛擬機把伺服器隔開
Minecraft、Docker、以及連去 AWS 的金鑰都放在虛擬機裡。宿主機不開 25565,也不持有那把隧道金鑰。
| VirtualBox | VMware Workstation | |
|---|---|---|
| 取得方式 | 直接安裝 | 個人使用曾改為免費,授權條件時有變動,安裝前看一次目前條款 |
| 沒有桌面時怎麼跑 | VBoxManage startvm "minecraft" --type headless |
有自己的 headless 模式 |
| 這次的需求 | 客體要能主動連出外網 | 同左 |
我用 VirtualBox。客體用 NAT 就夠了:隧道是虛擬機自己連出去,VirtualBox 不用做連接埠轉發。
客體我裝 Ubuntu Server LTS,給它:
- 2 顆 vCPU
- 4 GB 記憶體(下面的 Java 堆疊用 2 GB,剩下留給系統和容器)
- 30 GB 虛擬磁碟(世界和備份都放這裡,地圖大了再加)
宿主機開機後要把這台虛擬機以 headless 拉起來,裡面的 systemd 才會接著跑。Linux 宿主上就是開機時執行:
1 | VBoxManage startvm "minecraft" --type headless |
虛擬機裡用 Docker 跑伺服器
Docker 負責三件事:Java 版本不用自己管、世界資料放在一個目錄裡、升級時拉新映像再重建容器。
映像用 itzg/minecraft-server。先在客體裝 Docker Engine 和 Compose 外掛,步驟以 Docker 的 Ubuntu 安裝說明 為準,重點是最後要有 docker compose 這條指令,以及把使用者加入 docker 群組:
1 | sudo usermod -aG docker "$USER" |
登出再登入,群組才會生效。
在家目錄建一個目錄,寫入 compose.yaml:
1 | mkdir -p ~/minecraft |
1 | # ~/minecraft/compose.yaml |
EULA: "TRUE" 是在接受 Minecraft EULA。不設的話容器會直接退出。這份 EULA 不允許靠伺服器本身向玩家收費;這裡是朋友一起玩的私人伺服器。
TYPE: PAPER 用的是 Paper。同樣的視距,它比官方伺服器省 CPU。想改回官方伺服器,拿掉 TYPE 這行即可,預設是 VANILLA。
RCON 只活在容器裡,compose 沒有把 25575 公布出去。密碼換掉,這個檔案不要再貼到別的地方。
啟動並看日誌,等到出現 Done:
1 | cd ~/minecraft |
第一次會下載伺服器,多等幾分鐘。確認只聽在本機:
1 | ss -lnt | grep 25565 |
世界在 ~/minecraft/data。docker compose down 不會刪掉這個目錄。升級映像:
1 | docker compose pull |
版本號要升級時,先備份 data,再改 VERSION,然後執行上面兩行。
跳板:讓外網連得到 25565
在離玩家近的區域開一台 Amazon Linux 2023。舊免費額度用 t2.micro,新帳號改選當下標成 free tier eligible 的最小機型。安全群組先開兩個規則:
| 連接埠 | 來源 | 用途 |
|---|---|---|
| TCP 22 | 家裡目前的 IP(主控台的 My IP) | 自己登入 |
| TCP 25565 | 0.0.0.0/0 |
朋友連 Minecraft |
22 不要對全世界開。家裡 IP 變了導致登不進去時,從 AWS 主控台改這條規則即可,主控台不走這台機器的 SSH。
接著申請一個 Elastic IP 並掛到這台執行個體。沒掛之前,停止再開啟會換一個公網 IP,朋友存的位址就失效。掛上之後,原本自動配的那個公網 IP 會被放掉,帳號裡仍然只有一個位址。
SSH 預設把反向隧道綁在 127.0.0.1,那樣只有跳板自己連得到。要讓安全群組放進來的流量被接到隧道上,跳板的 sshd 得允許客戶端指定監聽位址。新增:
1 | # /etc/ssh/sshd_config.d/99-minecraft-tunnel.conf |
GatewayPorts clientspecified 的意思是:客戶端有寫監聽位址才對外聽。沒寫的話仍然只聽 loopback。ClientAlive* 讓斷掉的隧道被清掉,避免 25565 被一條已經死掉的連線占住。
檢查語法、確認設定有被讀進去,再重載:
1 | sudo sshd -t |
輸出要是 gatewayports clientspecified。若仍是 gatewayports no,就是主設定檔裡已有 GatewayPorts no,而且比這個 drop-in 先被讀到。把那一行改成 GatewayPorts clientspecified 再查一次。sshd 同一項設定以先讀到的為準。
另外建一個只能轉連接埠的使用者,不拿來登入做別的事:
1 | sudo useradd --create-home --shell /bin/bash tunnel |
/home/tunnel/.ssh/authorized_keys 先留空,下一節把虛擬機的公鑰貼進來。那一行會限制這把金鑰只能轉 25565。
把隧道做成斷線會自己回來
在虛擬機裡產生一把沒有密碼的金鑰。沒有密碼是為了讓開機能自己連;權限靠跳板上的 authorized_keys 收緊,這把金鑰做不了別的事。
1 | ssh-keygen -t ed25519 -f ~/.ssh/mc-tunnel -N "" |
把印出來的公鑰放到跳板的 /home/tunnel/.ssh/authorized_keys,整行是:
1 | restrict,port-forwarding,permitlisten="*:25565" ssh-ed25519 AAAA... mc-tunnel |
restrict 關掉終端、指令和轉發,後面的 port-forwarding 再把轉發打開,permitlisten 只放行 25565。檔案權限:
1 | sudo chown -R tunnel:tunnel /home/tunnel/.ssh |
permitlisten 需要 OpenSSH 7.9 之後的版本。Amazon Linux 2023 和 Ubuntu 22.04 之後都有。
回到虛擬機,先手動試一次。203.0.113.10 換成自己的 Elastic IP:
1 | ssh -N \ |
-R 0.0.0.0:25565:127.0.0.1:25565 是整篇的關鍵:跳板在所有介面上聽 25565,流量送回虛擬機的 Minecraft。少寫前面的 0.0.0.0:,跳板就只聽它自己,朋友會一直逾時。ExitOnForwardFailure=yes 讓「連接埠沒綁上」變成直接失敗,而不是一條看起來還活著、其實沒在轉的連線。
在跳板上另開一個工作階段確認:
1 | sudo ss -lntp | grep 25565 |
再用手機網路,或任何不在家裡的機器:
1 | nc -vz 203.0.113.10 25565 |
TCP 通了只代表隧道通了。再用 Minecraft 客戶端加這組位址,連接埠維持預設的 25565。
手動這條確認沒問題後,用 Ctrl-C 停掉,改由 systemd 接手。虛擬機裡新增 /etc/systemd/system/mc-tunnel.service。User= 和 /home/mc 換成客體裡實際的登入名稱(金鑰就是上一步 ssh-keygen 寫在那個家目錄的):
1 | [Unit] |
1 | sudo systemctl daemon-reload |
之後虛擬機重開,Docker 會把 Minecraft 拉起來(restart: unless-stopped),這個服務會把隧道重新連上。
朋友怎麼連
客戶端的伺服器位址填 Elastic IP。左下角的遊戲版本要和 VERSION 一致,否則會被拒絕進入。
白名單打開之前,不要把 IP 傳出去。虛擬機裡:
1 | cd ~/minecraft |
保持預設的正版驗證(online mode)。沒有登入 Mojang 帳號的客戶端連不上。
備份就是把 data 打包。打包前先停伺服器,避免抄到寫到一半的世界:
1 | cd ~/minecraft |
隧道通了但人進不去時
依序看這四個地方:
- 跳板上
ss顯示的是127.0.0.1:25565。-R少了0.0.0.0:,或是 sshd 的GatewayPorts仍是預設的no。 - 客戶端立刻失敗,日誌寫
remote port forwarding failed。25565 還被舊的 sshd 占著,或是permitlisten那一行和實際監聽位址不符。在跳板上把占住 25565 的那條 sshd 結束,虛擬機的mc-tunnel會自己重連。 - 客戶端說版本不符。把
VERSION改成客戶端顯示的版本,備份data之後重建容器。 - 容器反覆重啟。
docker compose logs裡如果是 EULA,就是EULA沒設成TRUE;如果是記憶體,把客體記憶體加大,或把MEMORY從2G降下來。
小結
- 伺服器放在家裡的 VirtualBox 客體裡,用 Docker 跑 Paper,連接埠只綁
127.0.0.1。 - 外網入口是一台很小的 EC2,只轉 TCP,不跑遊戲。
- 反向隧道由家裡主動連出,路由器不用開連接埠,也避開 CGNAT。
- 跳板的 sshd 要設
GatewayPorts clientspecified,客戶端的-R要寫0.0.0.0:25565。 - 隧道金鑰在
authorized_keys裡收成只能轉 25565,SSH 的 22 只對家裡的 IP 開放。 - 白名單先開,再把 Elastic IP 給朋友。世界資料在
~/minecraft/data,停機後打包即可備份。