27017
mongod 和 mongos 預設監聽的地方——也是驅動程式會安靜地花上三十秒選不到伺服器的那個連接埠。
照你的系統挑一行貼上去執行。它會直接印出占著這個連接埠的行程,以及它綁在哪個位址上——通常看到這兩件事答案就出來了。
sudo lsof -nP -iTCP:27017 -sTCP:LISTEN`-n` 跳過反向 DNS,`-P` 跳過連接埠名稱查詢,`-iTCP:27017` 選連接埠,`-sTCP:LISTEN` 選監聽狀態——這是 lsof 手冊拿來當範例的那個組合。從第二欄取 PID,從最後一欄取綁定位址,印出來是 `127.0.0.1:27017 (LISTEN)` 或 `*:27017 (LISTEN)`。綁在回送位址是預設值,也是多數「應用程式看不到資料庫」問題的答案。COMMAND 是 `com.docker.backend` 代表這是容器發布出來的連接埠,要停的是容器而不是那個行程。請用 `sudo` 跑,否則別的使用者擁有的 `mongod` 是隱形的;沒有人在聽的時候,lsof 什麼都不印並以 1 結束。
sudo ss -tlnp 'sport = :27017'ss(8) 手冊定義了這裡用到的旗標:`-t` 顯示 TCP socket,`-l` 只顯示監聽中的 socket,`-n` 不解析服務名稱,`-p` 顯示使用該 socket 的行程;`sport = :27017` 是它文件裡的連接埠過濾寫法。Local Address:Port 那一欄就是整個診斷——`127.0.0.1:27017` 是 localhost 預設值,別台主機碰不到;`0.0.0.0:27017` 是每一個 IPv4 位址;`[::]:27017` 是 IPv6 萬用位址。持有者讀作 `users:(("mongod",pid=980,fd=12))`。沒有 `ss` 的舊主機可以用 `netstat -tlnp`;兩者都需要超級使用者權限才會點出不屬於你的行程。
netstat -ano | findstr :27017照微軟的參考文件:`-a` 顯示所有作用中的 TCP 連線和電腦正在監聽的連接埠,`-n` 以數字表示位址和連接埠,`-o` 為每條連線包含行程 ID。欄位依序是 Proto、Local Address、Foreign Address、State、PID。用 `tasklist /fi "PID eq 980"` 把 PID 換成名字——`PID` 是文件裡的過濾條件,`eq` 是文件裡的運算子。因為 `findstr` 是在整行任何位置比對子字串,這台機器上的驅動程式往外連到別台資料庫 27017 的連線也會一起出現;哪一行是監聽者,看 State 那一欄。
Get-NetTCPConnection -LocalPort 27017 | Select-Object LocalAddress,LocalPort,State,OwningProcessPowerShell 這個版本把連接埠當數字比對,所以永遠不會撈到湊巧的子字串,而且 `-LocalPort` 會排除 27017 在對端的連線。對 `OwningProcess` 跑 `Get-Process -Id` 就能點出執行檔,在 Windows 上那通常是 MongoDB 服務,而不是你自己開的 shell。結果是空的、綁定卻一直失敗,矛頭指向保留而不是占用:`netsh interface ipv4 show excludedportrange protocol=tcp` 會列出 Hyper-V、WSL2 和 Docker Desktop 開機時搶下的區段。
mongosh --host 127.0.0.1 --port 27017 --eval 'db.runCommand({ ping: 1 })'應用層的檢查,也是把「一個開著的 socket」和「一台真的會服務你的資料庫」分開的那一個。`{ ok: 1 }` 代表健康。認證錯誤同樣代表健康——連接埠、綁定位址和網路全都沒問題,只是少了帳密。任何錯誤之前先停三十秒,代表伺服器選取逾時,那是封包被丟掉而不是被拒絕。請把位址明確寫出來,不要靠預設值,這樣答案才是關於你真正想問的那台主機。
`nmap -Pn -p 27017 db.example.com` 回報這個連接埠從機器外面看是什麼樣子。`-Pn` 把主機當成上線的、跳過探索,這一點很重要,因為資料庫主機常常會丟掉 ping;`-p` 把掃描限制在這一個連接埠。`open` 代表 TCP 交握完成,有東西在聽。`closed` 代表主機回了 reset,所以它活著、路由得到,但沒有人占著這個連接埠——那是 `mongod` 停掉時的典型特徵。`filtered` 代表完全沒有回應,那是防火牆或安全群組把封包丟了;你的驅動程式那三十秒等的也是它。`--reason` 會印出實際發生的是三者中的哪一個,open 對應 `syn-ack`、closed 對應 `conn-refused`,而 `-sV` 會探測連接埠以判定服務和版本——那和全網掃描器在找沒有認證的資料庫時做的判讀是同一件事。只掃你自己負責的主機。
| 你看到的訊息 | 代表什麼 |
|---|---|
MongoNetworkError: connect ECONNREFUSED 127.0.0.1:27017 | socket 層直接拒絕:主機回了一個 reset,因為沒有東西占著這個連接埠。要嘛 `mongod` 沒在跑,要嘛你人在容器裡,而那裡的 `127.0.0.1` 指的是那個容器、不是資料庫。用服務名稱或主機的實際位址去指資料庫,並且在真正跑它的那台機器上用對應的探測指令確認一次。 |
MongoServerSelectionError: Server selection timed out after 30000 ms | `serverSelectionTimeoutMS` 用完之後驅動程式自己的說法,而它的預設值剛好是 30000 毫秒,所以卡大約三十秒就是它的標記。這是一層包裝,不是原因:真正的 socket 錯誤在 `reason` 裡,或是在跟著印出來的拓撲描述裡。去讀那個,並且把逾時當成封包被丟掉、把拒絕當成伺服器停掉。 |
MongoServerError: Authentication failed. | 錯誤碼 18,`AuthenticationFailed`。網路、連接埠和綁定位址全都正確——連線已經走到交握,是帳密被拒絕。檢查使用者、密碼,還有最常見的 `authSource`:在 `admin` 建立的使用者,除非你明講,否則不會拿去對應用資料庫做認證。 |
MongoServerError: not authorized on appdb to execute command | 錯誤碼 13,`Unauthorized`,同樣跟 27017 無關。你認證成功了,只是掛在那個使用者身上的角色沒有帶你剛才那道指令需要的權限。去在對的資料庫上授予角色,不要伸手去拿 root 使用者。 |
listen EADDRINUSE: address already in use 0.0.0.0:27017 | 已經有東西占著這個連接埠了——通常是開機就啟動的 MongoDB 服務,跟你剛拉起來的容器擺在一起,再不然就是上一個沒有真的結束的 `mongod`。先找出持有者;把容器發布到另一個連接埠,常常比去停掉那個服務還快。 |
mongod logs 'Waiting for connections' with a port you did not expect | 那行啟動訊息帶著它實際拿到的連接埠,而它是抓到設定檔覆蓋你的最快方法。帶 `--shardsvr` 的 `mongod` 預設是 27018,帶 `--configsvr` 的是 27019,所以分片叢集的成員經常根本不在 27017 上,不管連線字串怎麼寫。 |
IANA 把 27017 登記給 TCP 上的 `mongodb`(Mongo database system);UDP 那一列是 Reserved,沒有服務名稱。MongoDB 自己的參考文件列出了兄弟號碼,知道它們可以省下很多瞎猜:27017 是 `mongod` 和 `mongos` 共同的預設值,27018 是帶 `--shardsvr` 或設定 `clusterRole: shardsvr` 啟動的 `mongod` 的預設值,27019 是 `--configsvr` 的預設值,而 27020 是 `mongocryptd` 監聽的地方。這些兄弟號碼都沒有出現在 IANA 登記表裡,只有 27017 有。現在的 MongoDB 執行檔預設綁在 localhost,手冊直接這樣寫,也點名了放寬的方法:`0.0.0.0` 是每一個 IPv4 位址,`::,0.0.0.0` 是兩個位址家族,再不然就是 `net.bindIpAll` 這個設定。那個預設值就是「在伺服器上用 shell 問什麼都對、從應用主機卻連不到」最常見的單一原因。另一件值得知道的事是,這裡的失敗通常是透過伺服器選取而不是 socket 回報的:驅動程式會一直重試它拓撲裡的每一個節點,直到 `serverSelectionTimeoutMS` 用完,而那個預設值是 30000 毫秒,所以連接埠寫錯或被擋住,呈現出來的是卡三十秒,不是立刻報錯。
27017 絕不能從網際網路碰得到。授權是一個你要自己打開的東西,而一台沒開授權就啟動的資料庫,會接受任何打得開那個 socket 的人送來的每一道指令——這正是掃描器開始掃這個連接埠之後,大量暴露的部署被清空並勒索的原因。localhost 這個預設值是實實在在的改善,但它只保護沒人重新設定過的伺服器;`bindIp` 一旦為了讓應用主機連進來而放寬,資料庫的可及範圍就等同網路允許的範圍。MongoDB 的建議是讓執行個體「只在受信任的網路上碰得到」,而在有多張網路介面的機器上,要綁在私有或內部那一張,不要綁全部。先做這件事。接著建立一個管理者使用者並啟用授權,而且給每個應用程式自己的角色範圍使用者,不要給 root,這樣一組外洩的帳密就是一次有邊界的損失。要從網路外面連進資料庫,走 VPN、對等連接的私有子網路或 SSH 通道,而不是在防火牆上把連接埠打開;真的非得可路由不可,就依來源位址限制並要求 TLS,因為沒有 TLS 的話,帳密、查詢和回傳的文件全都是明文穿過網路。
這些全都在你的瀏覽器裡跑,不會上傳任何東西。
查 27017 的時候,通常也會順手看一下這幾個。
幾乎都是權限問題。lsof 和 ss 會列出 socket,但別人的行程它不會顯示所有者,所以由系統帳號、容器執行環境或 launchd 啟動的服務,在你補上 sudo 之前就只是一個沒有主人的監聽項。Windows 則是反過來:netstat 什麼都沒有,綁定卻還是失敗,那代表這個號碼落在 Hyper-V、WSL2 或 Docker Desktop 開機時預留的範圍裡。
先看清楚它是什麼。沒收乾淨的開發伺服器砍掉沒事;正在寫入的資料庫不行,其他東西依賴的系統服務也不行。如果持有者是容器,請停容器,不要砍主機看到的那個行程——砍了執行環境只會把它拉起來。遇到真的不能動的,把自己的服務換一個連接埠反而更快。
因為它綁在 127.0.0.1 而不是 0.0.0.0。每一頁的指令之所以會把綁定位址跟行程名一起印出來,就是為了這件事:綁在回送位址上的服務,不管防火牆怎麼設,都只有這台機器自己連得到。現在不少框架是刻意這樣預設的,所以通常是一個參數的問題,不是 bug。如果已經是 0.0.0.0 還是不通,下一個要懷疑的就是主機防火牆或雲端的安全群組。
幾乎沒有幫助。掃全網的機器持續在掃整段範圍,換號碼買到的是幾個小時,不是安全,代價卻是每個同事都得記住它。它確實能讓針對預設埠的無差別掃描少污染一點日誌,這算是實際但有限的好處。真正會改變結果的是認證、限制來源位址的防火牆,以及一開始就不要讓它對外。
連接埠 27017 還是卡住嗎?看完整的連接埠對照表,或是回到上面,從你實際看到的那行錯誤往回追。