6379
Redis 伺服器聽 RESP 的連接埠——你的用戶端說連不到的那一個,也是暴露在外的快取幾個小時內就會被找到的那一個。
照你的系統挑一行貼上去執行。它會直接印出占著這個連接埠的行程,以及它綁在哪個位址上——通常看到這兩件事答案就出來了。
sudo lsof -nP -iTCP:6379 -sTCP:LISTEN`-n` 跳過反向 DNS 查詢,`-P` 跳過連接埠名稱查詢,所以位址維持數字;`-iTCP:6379` 收斂到那個連接埠,`-sTCP:LISTEN` 收斂到監聽狀態——這正是 lsof 自己的手冊拿來當範例的那個組合。欄位依序是 COMMAND、PID、USER、FD、TYPE、DEVICE、SIZE/OFF、NODE、NAME。第二欄是你會去停掉的那個行程,最後一欄是綁定位址,印成 `127.0.0.1:6379 (LISTEN)` 或 `*:6379 (LISTEN)`。回送位址代表只有這台機器連得進來,這也是為什麼容器或另一台主機永遠連不到;`*` 則是每一張介面。完全沒有任何一行、lsof 以 1 結束,代表連接埠是真的空的。不加 `sudo` 只看得到自己的行程,所以由 launch daemon 或 Docker 啟動的 `redis-server` 看起來會像什麼都沒有。
sudo ss -tlnp 'sport = :6379'ss(8) 手冊對每個旗標都有定義:`-t` 顯示 TCP socket,`-l` 只顯示監聽中的 socket,`-n` 不解析服務名稱,`-p` 顯示使用該 socket 的行程,而 `sport = :6379` 是它文件裡寫的 `{dport|sport} [OP] [FAMILY:]:PORT` 過濾格式。讀 Local Address:Port 那一欄:`0.0.0.0:6379` 是每一個 IPv4 位址,`127.0.0.1:6379` 只有回送位址,`[::]:6379` 是 IPv6 萬用位址,在預設的 Linux 主機上也會收 IPv4 連線。持有者印成 `users:(("redis-server",pid=1234,fd=6))`。沒有 `ss` 的機器上,比較舊的 `netstat -tlnp` 給你同樣的三件事;netstat 的手冊提醒你要有超級使用者權限才看得到不屬於你的 socket 的程式名稱,`ss` 也一樣。
redis-cli -h 127.0.0.1 -p 6379 ping同一個問題的應用層版本,也是把「有個 socket 開著」和「Redis 真的會回答你」分開的那一個。`PONG` 代表伺服器健康。`NOAUTH Authentication required.` 同樣代表健康——連接埠和網路都沒問題,只是少了帳密。`Could not connect to Redis at 127.0.0.1:6379: Connection refused` 代表那裡沒有人在聽。請把位址寫死,不要寫 `-h localhost`:在雙堆疊主機上那個名稱可能解析成 `::1`,而伺服器只綁了 IPv4,這時候的失敗看起來會和伺服器停掉一模一樣。
netstat -ano | findstr :6379微軟對這些旗標的說明是:`-a`「顯示所有作用中的 TCP 連線,以及電腦正在監聽的 TCP 和 UDP 連接埠」,`-n` 以數字表示位址和連接埠號碼,`-o`「為每一條連線包含行程 ID(PID)」。欄位是 Proto、Local Address、Foreign Address、State、PID,所以第二欄是綁定位址、最後一欄是行程。用 `tasklist /fi "PID eq 1234"` 把 PID 換成名字——`PID` 是文件裡有的過濾名稱,`eq` 是它文件裡有的運算子。不過比對要小心:`findstr` 是在整行任何位置找子字串,所以一行 Foreign Address 結尾是 `:6379` 的紀錄——也就是一條對外的用戶端連線——同樣會命中。停掉任何東西之前,先確認 State 那一欄寫的是 LISTENING。
Get-NetTCPConnection -LocalPort 6379 | Select-Object LocalAddress,LocalPort,State,OwningProcessPowerShell 這個寫法把連接埠當數字比對而不是文字,所以沒有任何子字串騙得了它,而且 `-LocalPort` 永遠不會撈到 6379 在遠端那一側的連線。把 `OwningProcess` 的值丟給 `Get-Process -Id` 就能拿到映像檔名稱。結果是空的、綁定卻還是失敗,那就是保留範圍的情況:`netsh interface ipv4 show excludedportrange protocol=tcp` 會列出 Hyper-V、WSL2 和 Docker Desktop 開機時搶下的動態連接埠區段,落在裡面的綁定會失敗,而且沒有行程可以怪。
`nmap -Pn -p 6379 cache.example.com` 回答的是離開這台主機之後唯一重要的那個問題。`-Pn` 把主機當成上線的、跳過探索,所以不理 ping 的機器照樣掃得到,而 `-p` 把掃描限制在你在意的那個連接埠。`open` 代表 TCP 交握完成:有東西在聽而且碰得到。`closed` 代表主機回了 reset,所以它活著、路由得到,但沒有人占著這個連接埠。`filtered` 代表什麼都沒回來——防火牆或雲端安全群組把封包丟掉而不是拒絕,而這個差別就是整個診斷。加 `--reason` 看實際發生的是三者中的哪一個;nmap 會在 open 的連接埠旁印 `syn-ack`,在 closed 的旁邊印 `conn-refused`。`-sV` 更進一步,它會探測連接埠以辨識服務和版本,對 Redis 通常會回傳版本字串——而這正是全網掃描器找到沒保護的執行個體的方法。只掃你自己負責的主機。
| 你看到的訊息 | 代表什麼 |
|---|---|
Could not connect to Redis at 127.0.0.1:6379: Connection refused | redis-cli 對 `ECONNREFUSED` 的說法:封包送到主機了,而核心回了一個 reset,因為沒有行程占著這個連接埠。Redis 停了、啟動途中死了,或是綁在一個不包含你撥過去那個位址的地方。在斷定它有起來過之前先讀它的紀錄檔——一台拿不到連接埠的伺服器會記下 `Failed listening on port 6379 (tcp), aborting.` 然後結束。 |
Warning: Could not create server TCP listening socket 127.0.0.1:6379: bind: Address already in use | 連接埠被占用時 Redis 自己的啟動訊息,後面會接 `Failed listening on port 6379 (tcp), aborting.` 然後結束。十次有九次是先前那個 `redis-server` 被丟到背景而不是真的停掉,再不然就是某個容器還在發布這個連接埠。用你系統對應的探測指令跑一次,把 PID 從裡面讀出來,不要用猜的。 |
(error) DENIED Redis is running in protected mode because protected mode is enabled and no password is set for the default user. | 網路沒問題,Redis 也在跑。它拒絕你,是因為你從非回送位址進來,而這台伺服器沒有密碼、也沒有明確的 bind。錯誤訊息本身列了四條出路,挑那條設密碼或設 ACL 使用者的。在一台碰得到的伺服器上關掉 protected mode,正是那些執行個體被清空的方式。 |
(error) NOAUTH Authentication required. | 完全不是連接埠問題——連線成功了,而 `requirepass` 或某條 ACL 正在生效。先送 `AUTH <password>`,ACL 使用者則送 `AUTH <user> <password>`,或者把帳密放進連線 URL。在明文連接埠上,那個密碼是以明文穿過網路的。 |
The client blocks for tens of seconds and then reports a connect timeout | `ETIMEDOUT`:什麼都沒回應。有個封包過濾器安靜地把 SYN 丟掉而不是拒絕——安全群組、主機上的防火牆,或你和快取之間的某條網路 ACL。被拒絕是瞬間的、被丟掉是慢的,所以光是這段延遲就告訴你現在看的是哪一種。 |
(error) MISCONF Redis is configured to save RDB snapshots, but it's currently unable to persist to disk. | 這也跟 6379 無關。連線是通的,而 Redis 拒絕寫入,是因為背景存檔失敗了,通常是磁碟滿了或某個目錄它寫不進去。整段期間讀取都還是正常的。去修磁碟,或者如果你接受丟掉那份快照,就改 `stop-writes-on-bgsave-error`。 |
IANA 把 6379 登記給 TCP 上的 `redis`;同號碼的 UDP 那一列沒有服務名稱、標記為 Reserved,而且 Redis 沒有任何東西講 UDP,所以一篇關於 6379 的文章就是一篇只關於 TCP 的文章。這個號碼來自伺服器內附 `redis.conf` 裡的 `port 6379` 那一行,而 `redis-cli` 也用同一個值當自己的預設,這就是為什麼在本機安裝上 `redis-cli ping` 不用帶任何參數。混淆多半來自三個鄰居號碼。Redis Sentinel 有一份獨立的設定檔,它的 `port` 是 26379,所以 Sentinel 絕不會是 6379 上的那個東西。跑在 cluster 模式的節點會另外開一個監聽 socket 給 cluster bus,而 `redis.conf` 寫明在 `cluster-port` 維持預設值 0 時,bus 會綁到指令連接埠加 10000——預設節點就是 16379——所以一個 cluster 成員持有的是兩個連接埠,不是一個。至於 Redis 6 加進來的 TLS,是透過另一個 `tls-port` 設定的:在你刻意把 `port` 設成 0 之前,原本那個連接埠會繼續在旁邊講沒有加密的 RESP。最後這件事值得講兩次,因為它就是大家會弄錯的那一個:除非有人把明文連接埠關掉,否則連到 6379 的連線就是明文。
永遠不要讓 6379 面向網際網路。Redis 在自己的安全性頁面上就把設計前提講清楚了——它「是設計來讓受信任環境裡的受信任用戶端存取的」——而且把兩個後果都寫出來了。任何碰得到這個連接埠的人,一個 `FLUSHALL` 就能毀掉整份資料集。更糟的是 `CONFIG` 指令讓用戶端可以改工作目錄和 dump 檔名,同一頁把這稱為一個資安問題,「可能導致系統被入侵,或以 Redis 執行身分的同一個使用者執行不受信任的程式碼」。這就是每一起大規模 Redis 攻擊背後的手法:把一個 dump 檔寫進作業系統稍後會讀回去的路徑。protected mode 有幫助,但它比大家以為的窄。從 3.2.0 起,Redis 會對非回送位址的用戶端回 `-DENIED` 錯誤,而文件對時機講得很精確:只有在伺服器跑著預設設定、綁在所有介面上、而且沒有設密碼的時候。為了讓伺服器連得到而加上一行明確的 `bind 0.0.0.0`,同時也拆掉了那道護欄,而那正是維運者在事故發生前不久做的那個編輯。內附的 `redis.conf` 綁的是 `127.0.0.1 -::1`;保持那樣,在它前面擺一個密碼或一個 ACL 使用者,遠端執行個體走 VPN 或 SSH 通道進去,並且記得在明文連接埠上,`AUTH` 本身也是明文送出去的。
這些全都在你的瀏覽器裡跑,不會上傳任何東西。
查 6379 的時候,通常也會順手看一下這幾個。
幾乎都是權限問題。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 還是不通,下一個要懷疑的就是主機防火牆或雲端的安全群組。
幾乎沒有幫助。掃全網的機器持續在掃整段範圍,換號碼買到的是幾個小時,不是安全,代價卻是每個同事都得記住它。它確實能讓針對預設埠的無差別掃描少污染一點日誌,這算是實際但有限的好處。真正會改變結果的是認證、限制來源位址的防火牆,以及一開始就不要讓它對外。
連接埠 6379 還是卡住嗎?看完整的連接埠對照表,或是回到上面,從你實際看到的那行錯誤往回追。