Immich App 連不上伺服器:從手機逐層排查到 CGNAT
手機上的 Immich 突然連不上伺服器?從容器健康狀態、App 網址、行動網路與 Wi-Fi 的差異、反向代理的 WebSocket 與上傳上限、憑證到 App 大版本,最後排查到台港家用寬頻常見的 CGNAT。
Immich App 連不上伺服器,先判斷斷在哪一層
Immich App 連不上伺服器,原因多半不在 Immich 本身,而是手機到伺服器這條路上有一段斷了。原本能用、現在不能用,代表某個東西變了:伺服器、網路環境、代理設定、憑證,或是 App 的版本。以下的順序是從手機往伺服器方向走,每一步只回答一個問題,而且每一步回答的問題都不一樣。請照順序做,不要跳。
先確認一件事再往下讀:你現在連不上,是在所有網路環境下都連不上,還是只有離開家的時候連不上。這兩種情況的排查路徑完全不同,後面第三節會處理第二種。
伺服器那一端還活著嗎
先不要碰手機。用 SSH 連進伺服器,看容器狀態。
cd /opt/immich
docker compose ps
docker compose logs --tail 100 immich-server第一行請改成你實際放 docker-compose.yml 的目錄。健康的結果是 immich_server 這個容器顯示 running,而且健康狀態是 healthy。Immich 官方的 docker-compose.yml 在 immich-server 服務底下寫的是 healthcheck: disable: false,也就是健康檢查預設就開著,所以 docker compose ps 那一欄是有意義的。如果狀態是 restarting,代表容器起不來又一直重試,原因幾乎都寫在 logs 的最後幾十行。
Immich 不是單一容器。server 依賴 database 與 redis 兩個容器,資料庫沒起來,server 就算顯示 running 也無法正常服務。第二常見的原因是磁碟滿了,因為 PostgreSQL 一旦寫不進去就會停止接受寫入,App 那端看起來就像伺服器沒有回應。
df -h
docker compose exec database pg_isreadydf -h 要看的是放照片的那個掛載點,不是只看根目錄。照片與資料庫實際落在哪一層目錄,可以對照 Immich 在 Docker 裡把照片放在哪個位置 再決定要清理哪裡。
最後在伺服器本機直接打一次 HTTP:
curl -I http://127.0.0.1:22832283 是 Immich 文件寫明的預設埠。官方 docker-compose.yml 對 immich-server 寫的是 ports: - '2283:2283',環境變數文件也列出 IMMICH_PORT 的預設值為 2283。本機 curl 有回應、手機沒有,就表示 Immich 本身是好的,問題在更外面一層,往下走。如果你不確定這個埠有沒有真的對外開著,先用 從外部確認 Linux 連接埠是否開啟的方法 驗證一次,再回來繼續。
App 裡填的網址還是你以為的那一個嗎
Immich App 的登入畫面要你填的是伺服器端點網址。官方的行動 App 說明給的格式是 http://<machine-ip-address>:2283,也就是通訊協定、位址、連接埠三段都要對。
最常見的四種填錯:把 https 填給只跑 http 的伺服器;漏掉 :2283,於是瀏覽器與 App 自動補上 80 或 443,而 Immich 預設不在那兩個埠上;把只在家裡有效的區域網路 IP 當成對外網址;以及沿用舊教學在結尾多加了 /api。
驗證這一欄不要在 App 裡猜。拿同一支手機、同一個網路,打開手機瀏覽器,把 App 裡那串網址原封不動貼進網址列。瀏覽器看得到 Immich 的登入畫面,App 才有可能連得上。瀏覽器也打不開,問題就在網址或網路,繼續往下;瀏覽器打得開而 App 連不上,直接跳到憑證與版本那兩節。
為什麼在家可以、出門就不行
這一節處理的是最常被誤判成「伺服器掛了」的情況。如果 App 裡填的是 192.168.x.x、10.x.x.x 或 172.16.x.x 開頭的位址,那是私有位址,只在你家網路裡有意義。手機切到 4G 或 5G 之後,同一串位址在電信商的網路裡指向別的東西,或是根本不通,App 就會一直轉圈然後逾時。這不是故障,是設計如此。
反過來的情況也會發生:網址填的是對外網域,在外面正常,一回家反而連不上。家用路由器如果不支援 NAT 迴流(hairpin NAT,也就是從內網連自己的公網 IP 再繞回來),這種封包會被直接丟掉。
分辨方法只有一個,而且一分鐘就做得完:把手機的 Wi-Fi 關掉,只用行動網路重試一次,再打開 Wi-Fi、關掉行動網路重試一次。兩次結果不同,就確定是位址與網路環境的組合問題,伺服器是無辜的。
Immich App 只會記住一個伺服器網址,所以長久的解法是讓同一串網址在家裡和外面都通。三種做法:對外用網域名稱,並在家中的 DNS 上把同一個名稱解析到內網 IP(split DNS);或是讓手機無論在哪裡都先連 VPN,網址一律用 VPN 內的位址;或是換成對外的通道服務,讓內外走同一條路。
反向代理有沒有把 WebSocket 和大檔案放過去
這一節的症狀很好認:登入成功、縮圖看得到,但是新照片一直不出現、上傳卡在中途、或是上傳大影片時固定失敗。TCP 連得上,壞的是代理的設定。
Immich 官方的反向代理文件給的 Nginx 設定包含以下這些行:
client_max_body_size 50000M;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 600s;
proxy_send_timeout 600s;
send_timeout 600s;
proxy_request_buffering off;
proxy_pass http://<backend_url>:2283;把 <backend_url> 換成你的 Immich 容器位址。每一行對應一種故障。client_max_body_size 設太小,超過上限的上傳會被 Nginx 自己擋下來並回 413,請求根本沒有到達 Immich,所以你在 Immich 的 log 裡什麼都找不到,只能在 Nginx 的 error log 裡看到請求主體過大的紀錄。proxy_http_version 1.1 加上 Upgrade 與 Connection 兩個標頭是 WebSocket(在單一連線上雙向傳輸的協定)升級的條件:少了它們,一般 HTTP 請求照常運作,但即時更新用的那條連線建立不起來,所以畫面不會自動刷新,重開 App 才看得到新照片。proxy_request_buffering off 讓上傳邊收邊轉送,而不是先整包寫進代理的暫存檔,這在上傳原始影片時差很多。逾時拉到 600 秒則是為了慢速上傳,預設的 60 秒對手機在行動網路下傳大影片太短。
改完先測語法再重新載入,然後一邊上傳一邊看 log:
sudo nginx -t && sudo systemctl reload nginx
sudo tail -f /var/log/nginx/error.lognginx -t 回報 syntax is ok 且 test is successful 才會執行 reload,因為 && 只在前一個指令成功時才跑下一個。上傳時 error log 裡出現與請求主體大小有關的訊息,就是 client_max_body_size 的問題;出現逾時相關的訊息,就是上面那三個 timeout。每一行設定分別在做什麼,Nginx 反向代理設定逐行解釋 有更完整的說明。
憑證是被手機拒絕,不是被 Immich 拒絕
自簽憑證在桌面瀏覽器上可以按「繼續前往」,在 App 裡通常沒有這個按鈕,連線會直接失敗。看起來像伺服器不理你,實際上是手機的 TLS(傳輸層安全協定)驗證在更早的階段就失敗了,請求從來沒有送到 Immich。
三種常見狀況。第一,憑證過期:Let's Encrypt 的憑證有效期很短,自動續期一旦壞掉,沒有人會主動通知你,桌面瀏覽器多半會先跳警告,App 則是直接連不上。第二,只安裝了伺服器憑證而漏掉中繼憑證:桌面瀏覽器因為快取過中繼憑證而看起來一切正常,手機沒有快取就驗不過。第三,伺服器或手機的系統時間偏掉,憑證有效期間的比對就會出錯。
在任何一台電腦上跑這一行,就能同時看到上面三件事的線索:
echo | openssl s_client -connect photos.example.com:443 -servername photos.example.com -showcerts 2>/dev/null | openssl x509 -noout -dates -subject -issuer把網域換成你自己的。notAfter 的日期已經過去就是過期。issuer 和 subject 完全相同,代表這是自簽憑證。-showcerts 會把伺服器送出的整條憑證鏈印出來,只有一張憑證就表示中繼憑證沒裝。
過期就重新續期並重新載入 Nginx。中繼憑證漏掉就改用包含完整鏈的檔案(Let's Encrypt 的 fullchain.pem,不是 cert.pem)。如果你堅持用自簽憑證,就必須讓手機信任它,多數人更省事的選擇是改用正式憑證,或是把 Immich 放進 VPN 裡直接走 http。
App 被商店更新到伺服器跟不上的大版本
手機上的 App 會自動更新,伺服器不會。Immich 的升級文件對這件事寫得很直接:「The mobile app is typically compatible with the current and prior major version.」而「the server is only compatible with the matching major version.」所以官方的建議是「we recommend upgrading all mobile clients before upgrading the server to ensure compatibility.」
翻成白話:App 比伺服器新一個大版本通常還能用,但 App 再往前跳,伺服器就接不住了,App 會明白告訴你版本不相容。這種時候要動的是伺服器,不是 App。把 App 降級不但麻煩,在 iOS 上幾乎做不到。
docker compose pull
docker compose up -d
docker compose ps執行前先備份資料庫與上傳目錄。docker compose pull 拉新映像檔,up -d 用新映像檔重建容器,ps 用來確認每個容器都回到 running 且 healthy。大版本升級偶爾會帶資料庫遷移,遷移期間 App 連不上是正常的,看 docker compose logs -f immich-server 等它跑完。備份與還原的完整流程請看 Immich 的備份與還原步驟,升級前做一次,比出事後找救援容易太多。
台灣與香港家用寬頻的 CGNAT:對外網址本來就不會通
如果上面都排除了,症狀是「在家一切正常,離開家完全連不上」,而且你確定連接埠轉發設定沒錯,那就要確認一件更根本的事:你家那條線到底有沒有真正的公網 IP。
台灣與香港的家用寬頻,特別是非固定 IP 的方案、行動寬頻與部分社區網路,很多都走 CGNAT(carrier-grade NAT,電信級網路位址轉換)。多個用戶共用同一個公網 IP,你拿到的只是電信商內部的一個位址。手機的行動網路幾乎一律是 CGNAT。
判斷只要兩分鐘。登入路由器,看 WAN 介面拿到的 IP 是什麼。那個位址如果落在 100.64.0.0/10(也就是 100.64.x.x 到 100.127.x.x 這個為電信級 NAT 保留的區段),或落在 10.0.0.0/8、172.16.0.0/12、192.168.0.0/16,它就不是公網 IP。再用家裡的電腦到任何一個查詢對外 IP 的網站看一次:網站顯示的位址和路由器 WAN 上的位址不一樣,就確定中間還有一層電信商的 NAT。
在 CGNAT 之下,連接埠轉發(port forwarding)不會有任何效果,因為對外的那個公網 IP 不在你手上。轉發規則要寫在電信商的設備上,而你沒有權限。重設路由器、換埠號、打開 UPnP、重申請一次 DDNS,全部都是浪費時間。這一步是很多人卡最久的地方,因為網路上的自架教學幾乎都預設你有公網 IP。
可行的路有四條。第一,向電信商申請固定 IP 或可用的公網 IP,台灣的光世代有固定制方案,通常要加價,申請前先問清楚是不是真的給公網位址。第二,把 Immich 整個搬到 VPS 上,VPS 本來就有公網 IP,手機在哪裡都連得到,搬之前先照 Immich 對記憶體與儲存空間的實際需求 算好規格。第三,伺服器留在家裡,另外租一台小 VPS 當入口,用 WireGuard 之類的通道把家中主機接到 VPS,再由 VPS 上的 Nginx 反向代理對外服務,照片仍然存在家裡。第四,用 Cloudflare Tunnel 這類由內往外主動建立連線的通道,一個對外連接埠都不用開,做法見 不開任何連接埠的 Cloudflare Tunnel。
第四條有一個必須先知道的限制:截至 2026 年 9 月,Cloudflare 免費方案對單次請求主體的上限是 100 MB,而手機拍的原始影片很容易超過。要走這條路,先確認你平常上傳的檔案大小,並到 Cloudflare 的官方文件確認當下的數字。
一分鐘決定下一步該測什麼
- 伺服器上
docker compose ps是否 running 且 healthy,df -h是否還有空間。 - 伺服器上
curl -I http://127.0.0.1:2283是否有回應。 - 用手機瀏覽器貼上 App 裡那串網址,看得到登入畫面嗎。
- 關掉 Wi-Fi 只用行動網路試一次,再只用 Wi-Fi 試一次,結果一樣嗎。
- 登入正常但照片不同步或上傳失敗,去看反向代理的 WebSocket 標頭與
client_max_body_size。 - 瀏覽器開得起來而 App 開不起來,用
openssl s_client檢查憑證的日期與憑證鏈。 - App 直接說版本不相容,升級伺服器,不要降級 App。
- 只有在外面連不上而且轉發怎麼設都沒用,去看路由器 WAN 的 IP 是不是落在
100.64.0.0/10。
FAQ
為什麼 Immich App 說連不上,但我用電腦開網頁是正常的?
電腦通常和伺服器在同一個區域網路,走的是另一條路;電腦瀏覽器也可能已經快取了中繼憑證,或是允許你點掉憑證警告,而 App 不允許。先用手機瀏覽器貼上 App 裡那串完全相同的網址:手機瀏覽器打得開而 App 打不開,問題多半在憑證或 App 大版本;手機瀏覽器也打不開,問題在網址或網路環境。
手機在行動網路下連不上 Immich,Wi-Fi 下正常,要怎麼修?
這代表 App 裡填的是只在家中網路有效的位址,例如 192.168.x.x 開頭的私有位址,離開家之後那串位址不指向你的伺服器。Immich App 只記一個伺服器網址,所以要讓同一串網址在內外都通:可以對外用網域名稱並在家中 DNS 解析到內網 IP,或是讓手機一律先連 VPN 再用 VPN 內的位址。做這件事之前,請先確認你家那條線有沒有公網 IP,沒有的話對外網域怎麼設定都不會通。
Immich App 顯示版本不相容,要降級 App 還是升級伺服器?
升級伺服器。Immich 的升級文件寫的是行動 App 通常相容於目前與前一個大版本,而伺服器只相容於相同的大版本,因此建議先更新用戶端再更新伺服器。App 已經被商店自動更新之後,正確的動作是把伺服器拉到對應的大版本:先備份,再 docker compose pull 與 docker compose up -d,然後看 log 等資料庫遷移跑完。
我家是 CGNAT,一定要把 Immich 搬到 VPS 嗎?
不一定,但是再怎麼設定連接埠轉發都不會通,這一點沒有例外,因為對外的公網 IP 不在你手上。除了搬到 VPS,還可以向電信商申請公網或固定 IP、租一台小 VPS 當對外入口並用 WireGuard 把家中主機接上去、或是使用由內往外建立連線的通道服務。照片要留在家裡就選後兩種,要簡單就把整套搬上 VPS。