80
純文字 HTTP 的連接埠。現在它多半只負責轉去 443——但把它關掉,憑證就續不了約;而以一般使用者身分綁它,伺服器根本啟動不了。
照你的系統挑一行貼上去執行。它會直接印出占著這個連接埠的行程,以及它綁在哪個位址上——通常看到這兩件事答案就出來了。
sudo lsof -nP -iTCP:80 -sTCP:LISTEN`-n` 和 `-P` 讓主機和連接埠維持數字,`-iTCP:80` 挑協定和連接埠,`-sTCP:LISTEN` 把已建立的連線藏起來,讓你只看到持有者。NAME 那一欄是綁定位址:`127.0.0.1:80` 代表只服務本機,`*:80` 代表每一張介面,也就是從網路上碰得到。COMMAND 和 PID 點出行程,USER 顯示它用哪個帳號在跑——綁完之後才降權的伺服器,會出現一個掛在 `_www` 或 `nobody` 底下的 worker,即使當初開 socket 的是 root。沒有輸出、結束碼 1,代表連接埠是空的。
sudo ss -tlnp 'sport = :80'`-t` 只看 TCP,`-l` 只看監聽中的 socket,`-n` 讓連接埠維持數字,`-p` 補上所屬行程。先讀 Local Address:Port 那一欄:`0.0.0.0:80` 是每一張 IPv4 介面,`127.0.0.1:80` 只有回送位址,這正好解釋了為什麼伺服器本機回應得好好的、外面卻連不到,而 `[::]:80` 是 IPv6 萬用位址,在多數 Linux 主機上連 IPv4 也一起收。nginx 會有好幾行共用同一個連接埠,因為每個 worker 都繼承了那個 socket,這是正常的。不加 `sudo` 的話,不屬於你的那些行程欄會是空的。
sudo netstat -tlnp | grep ':80 '舊映像檔和精簡容器裡用 net-tools 這個寫法。旗標一樣;最後那欄 PID/Program name 對不屬於你的 socket 需要超級使用者權限,man page 也警告這個值不可信。樣式最後那個空格是有實際作用的——少了它,`:80` 會連 `:8080`、`:8000` 和 `:80443` 一起命中,而這正是最多人誤讀這份輸出的原因。
netstat -ano | findstr :80`-a` 會把監聽中的連接埠和現行連線一起顯示,`-n` 讓它們維持數字,`-o` 補上所屬 PID;`tasklist /FI "PID eq 1234"` 把它換成名字。在 Windows 上,答案常常是 PID 4,也就是 System 行程,因為 HTTP.sys 這個核心驅動程式代表 IIS、Web Deployment Agent,甚至是 Skype 時代留下來的東西持有連接埠 80——砍 PID 4 不是選項,改用 `netsh http show servicestate` 查是哪個應用程式預留了它。跟往常一樣,`findstr` 比的是子字串,所以 `:80` 也會命中 `:8080`。
`nmap -Pn -p 80 example.com` 會回報三種狀態之一,每一種的下一步都不同。`open` 代表有東西完成了交握;接著用 `curl -I http://example.com/` 看它是提供內容還是做轉向。`closed` 代表主機回了 reset,所以它連得到但沒有人在聽——問題出在伺服器行程,不是網路。`filtered` 代表什麼都沒回來,矛頭指向安全群組、雲端防火牆或上游 ACL,而不是主機本身。加 `--reason` 可以看到實際觀察到的是哪一種,而不是推論出來的;加 `-sV` 讓 nmap 讀回應並報出伺服器軟體。只掃你自己負責的主機。
| 你看到的訊息 | 代表什麼 |
|---|---|
Error: listen EACCES: permission denied 0.0.0.0:80 | 這不是衝突,是權限檢查。連接埠 80 在 1024 以下,所以核心在考慮它有沒有空著之前,就先拒絕了非特權行程的綁定。請用 init 系統啟動服務、在 Linux 上給那支執行檔 `CAP_NET_BIND_SERVICE`、改聽 8080 再在前面擺一層代理或轉向,或者在你清楚這還會放行什麼的前提下,調低 Linux 的 `net.ipv4.ip_unprivileged_port_start`。 |
Error: listen EADDRINUSE: address already in use :::80 | 這次真的有東西占著連接埠。跑探測指令,動手砍之前先讀綁定位址:綁在萬用位址上的監聽者會擋掉同一個連接埠上任何對特定位址的綁定,而兩個各自綁在不同特定位址的行程可以共存。在 Windows 上,持有者常常是核心的 HTTP.sys 驅動程式,不是一般行程。 |
curl: (7) Failed to connect to example.com port 80 after 12 ms: Couldn't connect to server | reset 幾乎是立刻回來的,所以主機連得到,只是連接埠 80 上沒有人在聽。要嘛網頁伺服器停了、要嘛它綁在回送位址上、要嘛你撞到的是一台只設定了 HTTPS 監聽者的負載平衡器。速度就是線索:被拒絕是立刻的。 |
curl: (28) Failed to connect to example.com port 80 after 130004 ms: Connection timed out | 完全沒有回應,所以是某個過濾器安靜地把封包丟了。去看雲端安全群組、主機防火牆,以及你和伺服器之間任何一段網路 ACL。被丟掉要等好幾十秒,被拒絕只要幾毫秒,所以光看等待時間就能先分出這兩種原因,什麼都還不用查。 |
Let's Encrypt: Timeout during connect (likely firewall problem) on an http-01 renewal | ACME 伺服器從公開網際網路碰不到 TCP 連接埠 80,而 RFC 8555 不允許它為了這個挑戰改用別的連接埠。要嘛把挑戰路徑上的 80 對全世界開放,要嘛改用 DNS-01 挑戰——它靠一筆 TXT 記錄驗證,完全不需要任何入站連接埠。這種續約失敗在憑證過期之前都是無聲的。 |
The site works from the server itself but not from another machine | 幾乎一定是綁定位址的問題,不是防火牆。探測指令會顯示 `127.0.0.1:80`,那只服務本機;伺服器要改綁 `0.0.0.0` 或某個對外位址。這件事先查,再去動防火牆規則:它只要一行指令,而且是比較常見的原因。 |
IANA 依 RFC 9110 把 80 指派給 `http`,TCP、UDP、SCTP 都有登記,實務上只用 TCP。在現代伺服器上,真正在那裡聽的很少是應用程式本身,而是 nginx、Caddy 或 Traefik 這類反向代理,它們在這個連接埠上的全部工作就是回一個轉向到 HTTPS——外加一個常常讓人踩坑的例外。ACME,也就是 Let's Encrypt 和多數自動憑證背後的協定,定義了一種 `http-01` 挑戰,RFC 8555 明文規定它的驗證請求「必須送到 HTTP 伺服器的 TCP 連接埠 80」——這個號碼是寫死的,不能談判。把連接埠 80 整個用防火牆擋掉,http-01 續約就會停止運作,而且通常是安靜地停,直到六十天後憑證過期你才發現。關於連接埠 80 的另一件事是它有特權。在類 Unix 系統上,1024 以下的連接埠只有具備適當權限的行程才綁得起來,而且核心是在確認連接埠有沒有空著之前就先檢查這件事:在 macOS 上,一般使用者試著綁 127.0.0.1:80 會立刻拿到 EACCES。Linux 把這條界線做成 sysctl `net.ipv4.ip_unprivileged_port_start`,所以請用 `sysctl net.ipv4.ip_unprivileged_port_start` 讀出你機器上的值,不要用猜的,而且要授予特定執行檔 `CAP_NET_BIND_SERVICE` 能力,而不是讓整台伺服器用 root 跑。這就是為什麼開發伺服器預設是 3000、5173 或 8080,也是為什麼同一份程式碼到了正式環境就需要一層代理或轉向。
連接埠 80 是少數幾個「對外開是正常也正確」的連接埠之一——但要很清楚它上面服務的是什麼。走這個連接埠的所有東西,路徑上的任何裝置都讀得到也改得動,所以它上面不該有登入表單、不該有 session cookie、不該有 API,也不該有管理介面。安全的形狀是一個只回答兩件事的監聽者:一個 301 轉向到對應的 HTTPS 網址,以及憑證用戶端需要的 `/.well-known/acme-challenge/` 路徑。從 HTTPS 那一側送出 `Strict-Transport-Security`,讓瀏覽器在第一次造訪之後就完全不再對你的主機使用連接埠 80;cookie 標上 `Secure`,這樣就算有什麼漏過去,它們也絕不會經由這個連接埠送出。不要因為「它只是做轉向」就以為開著 80 無害:在充滿惡意的網路上,轉向的目的地本身就是攻擊者可以改的,而那正是 HSTS 要堵的東西。最後,一個內部服務不小心聽在 `0.0.0.0:80` 而不是回送位址上,是測試環境變成公開環境最常見的途徑之一——要看綁定位址,不要只看行程名稱。
這些全都在你的瀏覽器裡跑,不會上傳任何東西。
查 80 的時候,通常也會順手看一下這幾個。
幾乎都是權限問題。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 還是不通,下一個要懷疑的就是主機防火牆或雲端的安全群組。
幾乎沒有幫助。掃全網的機器持續在掃整段範圍,換號碼買到的是幾個小時,不是安全,代價卻是每個同事都得記住它。它確實能讓針對預設埠的無差別掃描少污染一點日誌,這算是實際但有限的好處。真正會改變結果的是認證、限制來源位址的防火牆,以及一開始就不要讓它對外。
連接埠 80 還是卡住嗎?看完整的連接埠對照表,或是回到上面,從你實際看到的那行錯誤往回追。