觸類旁通,從400錯誤看Nginx常見故障與修複
作者介紹
林偉壕,網絡安全DevOps新司機,先後在中國電信和網易遊戲從事數據網絡、網絡安全和遊戲運維工作。對Linux運維、虛擬化和網絡安全防護等研究頗多,目前專注於網絡安全自動化檢測、防禦係統構建。
眾所周知,Nginx是目前最流行的Web Server之一,也廣泛應用於負載均衡、反向代理等服務,使用過程中可能因為對Nginx工作原理、變量含義、參數大小等問題的理解錯誤,導致Nginx工作異常。
因此,本文將從一個Nginx錯誤代碼400引發的故障入手,談談如何分析和修複常見的Nginx異常。

故障簡述

小明某天中午在線優化一個敏感服務的Nginx配置時,發現5分鍾內Nginx errorlog裏出現了大量400錯誤,於是迅速回滾了Nginx配置。

故障詳情

原來的Nginx配置存在重複或者需廢棄的內容,於是在多次diff了新舊兩份配置內容後,小明認為最新配置是不影響業務的,因此在線推送更新配置後,直接reload了Nginx,出於double check原則,在線觀察了5分鍾Nginx日誌:

發現出現大量類似下麵的400錯誤:

400錯誤的產生,很可能影響服務端或客戶端的後續業務邏輯判斷,因此需要引起重視。

處理過程

節點1
當時回滾配置後,小明先在搜索引擎查找了Nginx 400錯誤的可能原因和解決辦法,初步確定有下麵兩種可能:1是空主機頭,2是請求包頭過大。
小明跟客戶端同學確認了客戶端請求方式,發現他們使用的是類似telnet的方式發起的http請求,類似下麵的:

為了方便後續排查,小明參考線上環境臨時搭建了一套Nginx測試環境,重現了故障:

後來小明了解到原來客戶端不是從代碼的http庫調用, 而是按照上麵的方式走TCP/telnet傳遞http參數來調用服務端http接口。但是為什麼一樣的客戶端請求方式,舊配置完全ok,新配置則會出現大量400錯誤?
節點2
至此,小明懷疑自己沒有完全diff出新舊兩份配置的差別,於是他使用vimdiff再次對比新舊兩份配置。下麵僅貼出關鍵配置:
舊配置:


新配置:

本次排查中,小明考慮的重點是新配置裏遺漏了某些配置,於是他把location ~ (.*)的相關邏輯加上,發現問題依舊:

節點3
既然前麵往缺失配置的思路走不通,下麵就按照新增配置的思路排查,結果發現新配置增加了一些包頭信息,小明懷疑是請求包過大,於是優先排查了Nginx針對包頭大小的設置,其中有這麼幾個配置:
-
client_header_buffer_size:默認是1k,所以header小於1k的話是不會出現問題的。
-
large_client_header_buffers:該命令用於設置客戶端請求的Header頭緩衝區的大小,默認值為4KB。
-
客戶端請求行不能超過large_client_header_buffers指令設置的值,客戶端請求的Header頭信息不能大於large_client_header_buffers指令設置的緩衝區大小,否則會報“Request URL too large”(414)或者“Bad-request”(400)錯誤,如果客戶端Cookie信息較大,則須增加緩衝區大小。於是小明將client_header_buffer_size和large_client_header_buffers都設置為128k。結果問題也重現了。
接下來,小明發現新配置中多了“proxy_set_header Host $http_host;”查找了Nginx官方文檔發現跟$http_host類似功能的還有$server_name和$host等變量,在他將$http_host更換成$host後,問題修複了。

原因分析

根據Nginx官方文檔介紹,400狀態碼含義如下:

上麵是http1.1的rfc關於host部分的解釋,從上麵我們了解到如果一個http1.1的請求沒有host域,那麼server應該給client段發送400的狀態碼,表明這個請求server不能處理。而對於Nginx server來說,也遵循這樣的方式,說明client發送了一個無效的請求,Nginx server無法處理,於是返回了400的狀態碼。
另外,關於$host和$http_host這兩個變量的區別如下:

本次故障中,客戶端的調用方式沒有使用host 參數,傳遞了空的Host頭給服務端,一旦Nginx設置了proxy_set_header Host $http_host,空Host頭就傳給了後端。然而,在http 1.1的規範中,Host隻要出現空,就會返回400,所以出現了這個故障。而對於需要在Host字段裏帶上端口信息的,則仍需要配置proxy_set_header Host $http_host。
最後,需要注意的是,400錯誤不一樣會影響業務,需要看具體的業務處理邏輯,比如使用nagios的check_tcp插件對Nginx server端口做檢測或者使用keepalived的tcp_check功能對後端Nginx端口的存活做檢測,這兩種情況都會在Nginx errorlog中產生400的請求。
原因也很簡單,就是一般tcp check的方式,就是建立tcp連接,但是沒有發送任何數據,當然也沒有Host頭,然後再reset或者四次揮手斷開連接。

經驗教訓

運維規範
細心的同學會發現本次配置更新是在大中午操作,而且也沒有在測試環境測試通過,這在流程上是不嚴謹的。雖然Nginx等web服務的配置更新基本上通過熱更就可以了,但沒有灰度測試或者在測試環境測試,一是無法提前發現問題,二是無法控製業務影響。所以,在運維規範上看,即使是熱更也應當在測試環境測試正常後再同步到線上,其他的更新則應在業務低穀時操作。
技術學習方法
本次故障的產生,很大程度上就是運維同學不理解Nginx變量的定義和區別,直接從搜索引擎上找了些配置,檢查覺得正確就推到了線上。這裏仍需要重申的是,以官方文檔為準!互聯網上很多知識或者配置有各種各樣的問題,隨時都有暗坑在裏邊,隻有啃過官方文檔才能避免誤讀。
Web日誌分析
針對這裏的Nginx錯誤日誌查看,我們看到小明是用在線命令查看的,其實現在有很多web日誌分析工具或係統,比如ELK(ElacticSearch+LogStash+Kibana),隻需要配置好grok正則,是可以通過可視化界麵實時監控web服務質量的。
引申
上麵介紹了Nginx 400錯誤的可能原因和解決辦法,但實際工作中,我們遇到的可不止這麼一點。於是,由此引申出去的是,針對那些Nginx常見錯誤如何去排查和解決。
-
403錯誤
403是很常見的錯誤代碼,一般就是未授權被禁止訪問的意思。
可能的原因有兩種:
-
Nginx程序用戶無權限訪問web目錄文件
-
Nginx需要訪問目錄,但是autoindex選項被關閉
修複方法:
-
授予Nginx程序用戶權限讀取web目錄文件
-
設置autoindex目錄為on

-
413錯誤
在上傳時Nginx返回了413錯誤:“413 Request Entity Too Large”,這一般就是上傳文件大小超過Nginx配置引起。
修複方法:
-
在Nginx.conf增加client_max_body_size的設置,這個值默認是1M,可以增加到8M以提高文件大小限製;
-
如果運行的是php,那麼還要檢查php.ini,這個大小client_max_body_size要和php.ini中的如下值的最大值一致或者稍大,這樣就不會因為提交數據大小不一致出現的錯誤。
post_max_size = 8M
upload_max_filesize = 2M
-
502錯誤
Nginx 502 Bad Gateway的含義是請求的PHP-CGI已經執行,但是由於某種原因(一般是讀取資源的問題)沒有執行完畢而導致PHP-CGI進程終止。一般來說Nginx 502 Bad Gateway和php-fpm.conf的設置有關。
修複方法:
1、查看FastCGI進程是否已經啟動
ps -aux | grep php-cgi
2、檢查係統Fastcgi進程運行情況
除了第一種情況,fastcgi進程數不夠用、php執行時間長、或者是php-cgi進程死掉也可能造成Nginx的502錯誤。
運行以下命令判斷是否接近FastCGI進程,如果fastcgi進程數接近配置文件中設置的數值,表明worker進程數設置太少。
netstat -anpo | grep "php-cgi" | wc -l
3、FastCGI執行時間過長
根據實際情況調高以下參數值
fastcgi_connect_timeout 300;
fastcgi_send_timeout 300;
fastcgi_read_timeout 300;
-
504錯誤
Nginx 504 Gateway Time-out的含義是所請求的網關沒有請求到,簡單來說就是沒有請求到可以執行的PHP-CGI。
Nginx 504 Gateway Time-out一般與Nginx.conf的設置有關。
頭部太大這種情況可能是由於Nginx默認的fastcgi進程響應的緩衝區太小造成的, 這將導致fastcgi進程被掛起,如果你的fastcgi服務對這個掛起處理的不好,那麼最後就極有可能導致504 Gateway Time-out。
默認的fastcgi進程響應的緩衝區是8K,可以調大以下參數:
-
fastcgi_buffer_size 128k;
fastcgi_buffers 8 128k; -
fastcgi_busy_buffers_size 由 128K 改為 256K;
fastcgi_temp_file_write_size 由 128K 改為 256K。
此外,也可能是php-cgi的問題,需要修改php.ini的配置:
-
將max_children由之前的10改為30,這樣操作是為了保證有充足的php-cgi進程可以被使用。
-
將request_terminate_timeout由之前的0秒改成60秒,這樣使php-cgi進程處理腳本的超時時間提高到60秒,可以防止進程被掛起以提高利用效率。
原文發布時間為:2017-03-23
本文來自雲棲社區合作夥伴DBAplus
最後更新:2017-05-16 11:31:26