nginx 报500 Internal Server Error

原文:https://blog.csdn.net/starbucks_star/article/details/78427033

 

 

这个问题是其他同事反映过来的,应该说比较罕见,需要同时满足三种条件才能发生。为了保持神秘,原因暂时不提,不过背景得交待一下。该案例的大概架构就是部署两个nginx服务器,nginx1作为普通的web server,nginx2作为反向代理部署在nginx1的后端。出于测试目的,取消了临时文件所在目录client_body_temp的访问权限,此为条件一 。

关于client_body_temp目录的作用,简单说就是如果客户端POST一个比较大的文件,长度超过了nginx缓冲区的大小,需要把这个文件的部分或者全部内容暂存到client_body_temp目录下的临时文件。

引起我们注意的是nginx的一个配置指令,client_body_buffer_size,如果把它设置为比较大的数值,例如256k,那么,无论使用firefox还是IE浏览器,来提交任意小于256k的图片,都很正常。如果注释该指令,使用默认的client_body_buffer_size设置,也就是操作系统页面大小的两倍,8k或者16k(此为条件二 ),问题就出现了。

无论使用firefox4.0还是IE8.0,提交一个比较大,200k左右的图片,都返回500 Internal Server Error错误。这其实也没有问题,200k大于当前的client_body_buffer_size(8k或者16k),提交的内容需要写入临时文件,前面取消了目录访问权限导致出错。

但是,如果提交一个比较小,30k左右的图片,firefox和IE的返回结果却有所不同。IE仍然返回500错误,这很好理解,30k仍然大于当前的client_body_buffer_size(8k或者16k),出错是正常的,不出错才奇怪。然而,firefox就是神奇地返回了经过resize server处理后的页面!

这太奇怪了,难道firefox发送的数据与IE发送的有所不同?使用tcpdump抓包发现,的确是有很大的不一样。

IE发送的数据包截图如下,建立连接时三次握手清晰可见,第10行是IE向nginx1发送http头消息,第24行是nginx1发送应答,然后再发送Content-Disposition和Content-Type两行header和body。Body与header的主干部分是分开不同的包发送的。


 

而firefox发送的数据包截图如下,三次握手不再赘述,第13行是firefox向nginx1发送http头消息,奇怪的是除了发送头消息,还附带了部分body,见第33行,第35行才是nginx1对该头消息的应答。也就是说,firefox把一部分body塞到header包里。


 

Firefox的这种行为使同事注意到nginx里设置的另一个缓冲区大小:client_header_buffer_size。原来此前他们设置了该值为128k,此为条件三 。

综合以上现象,就有了初步推断:对于IE的请求,nginx把body只放到body的缓冲区处理,所以不受header的缓冲区大小的影响,而对于firefox的请求,nginx可能会把body放到header的缓冲区处理,所以,分别设置header、body缓冲区为128k、8k/16k的时候,POST 30k的图片能够成功而POST 200k的图片返回500错误,分别设置header、body缓冲区为128k、256k的时候,POST 200k的图片也能成功。

如何验证这个结论呢?究竟什么情况下body数据会放到header缓冲区处理呢?

“有问题,看日志”是一个好习惯。但是默认情况下,nginx记录的日志比较简单,不能满足要求,需要这样打开调试日志功能:

编译nginx,configure时使用--with-debug打开调试信息,然后make && make install
编辑nginx.conf,在server的error_log指令的文件名后面加上debug:
        error_log     logs/8085_error.log debug ;

使用firefox POST 30k左右的图片时,截取到的部分日志如下:
70 2011/05/26 09:53:28 [debug] 23622#0: *33 http client request body preread 2398
71 2011/05/26 09:53:28 [debug] 23622#0: *33 http read client request body
72 2011/05/26 09:53:28 [debug] 23622#0: *33 recv: fd:3 5840 of 28645
73 2011/05/26 09:53:28 [debug] 23622#0: *33 http client request body recv 5840
74 2011/05/26 09:53:28 [debug] 23622#0: *33 http client request body rest 22805

按这几行的关键字,可以搜索到对应的函数为src/http/ngx_http_request_body.c 的ngx_http_read_client_request_body 和ngx_http_do_read_client_request_body。前者会把预读的body(在源代码里,作者把被塞进http头消息包里的那部分body称为preread,即预读的body)暂存到header缓冲区,而只要这块header缓冲区足够大,足以容纳剩下的body的时候,会调用后者把它们也一起读进来。于是,上面30k左右的图片就能穿透过去,看上去不可思议的事情就这样发生了。

简单地分析一下相关源码。

在这里开始正式读取body。IE的请求,preread为零,而Firefox的请求,preread非零:
 

[cpp] view plain copy

  1. 110     preread = r->header_in->last - r->header_in->pos;  
  2. 111  
  3. 112     if (preread) {  
  4. …  
  5. 166         rb->rest = r->headers_in.content_length_n - preread;  
  6. 167  
  7. 168         if (rb->rest <= (off_t) (b->end - b->last)) {  
  8. 169  
  9. 170             /* the whole request body may be placed in r->header_in */  
  10. 171  
  11. 172             rb->to_write = rb->bufs;  
  12. 173  
  13. 174             r->read_event_handler = ngx_http_read_client_request_body_handler;  
  14. 175  
  15. 176             return ngx_http_do_read_client_request_body(r);  
  16. 177         }  



在168行有个条件判断,rb->rest是未读body的剩余长度,b->end – b->last就是空余的缓冲区大小。当header缓冲区不够大时,显然是不会跑到上面176行那里去的,而是会掉到下面,重新根据client_body_buffer_size的大小分配缓冲区处理。于是,200k那么大的图片就被挡住了。而30k左右的图片,在设置了比较大的client_header_buffer_size的时候是可以过去的。

IE的请求会跑到这里:

[cpp] view plain copy

  1. 181     } else {  
  2. 182         b = NULL;  
  3. 183         rb->rest = r->headers_in.content_length_n;  
  4. 184         next = &rb->bufs;  
  5. 185     }  
  6. 186  



前面处理不完的都跑到这里,用了两次client_body_buffer_size,意思是剩余的内容不超过缓冲区大小的1.25倍,一次读完(1.25可能是经验值吧),否则,按缓冲区大小读取。

[cpp] view plain copy

  1. 187     size = clcf->client_body_buffer_size;  
  2. 188     size += size >> 2;  
  3. 189  
  4. 190     if (rb->rest < size) {  
  5. 191         size = (ssize_t) rb->rest;  
  6. 192  
  7. 193         if (r->request_body_in_single_buf) {  
  8. 194             size += preread;  
  9. 195         }  
  10. 196  
  11. 197     } else {  
  12. 198         size = clcf->client_body_buffer_size;  
  13. 199  
  14. 200         /* disable copying buffer for r->request_body_in_single_buf */  
  15. 201         b = NULL;  
  16. 202     }  
  17. 203  
  18. 204     rb->buf = ngx_create_temp_buf(r->pool, size);  
  19. 205     if (rb->buf == NULL) {  
  20. 206         return NGX_HTTP_INTERNAL_SERVER_ERROR;  
  21. 207     }  
  22. …  
  23. 236     return ngx_http_do_read_client_request_body(r);  

 


其实这样的处理流程也是无可厚非的,遇到body比较小,刚好header缓冲区又能够放得下,不用白不用,是不是?

最后,整理一下出现这个问题需要的条件。值得注意的是,目前各种版本的nginx都有这个现象(0.7.68、0.8.54、1.0.2都有) 
1) client_body_temp设置为不可访问,使得没有权限写临时文件
2) client_body_buffer_size使用默认设置,8k或者16k
3) client_header_buffer_size设置得比较大

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
当看到nginx vhost 500 internal server error错误时,通常表示在处理客户端请求时发生了服务器端错误。这个错误状态码表示出现了内部错误,服务器无法完成请求。 要解决这个错误,可以采取以下步骤: 1. 检查nginx服务器日志:查看nginx服务器日志文件,如error.log文件,找出具体的错误信息。这些错误信息可以帮助我们确定导致500错误的原因。 2. 检查配置文件:确保nginx配置文件中的vhost设置正确。检查是否有语法错误或拼写错误。特别注意每个vhost的root指令和相关选项是否正确配置。 3. 检查文件权限:确保nginx服务器能够读取和访问vhost配置文件和根目录中的文件。检查文件和目录权限是否正确设置,确保nginx用户(通常是www-data)具有足够的权限。 4. 检查后端服务:如果vhost使用了代理转发请求给后端服务,例如PHP-FPM或其他应用服务器,确保这些服务正在运行并且没有出错。查看后端服务的错误日志,确保没有连接问题或内部错误。 5. 检查PHP设置:如果vhost是用于处理PHP请求的,检查PHP配置是否正确。确保PHP版本正确,并且PHP配置文件(通常是php.ini)没有错误。 6. 检查资源限制:如果服务器资源(例如内存、CPU)受到限制,可能会导致500错误。检查系统资源使用情况,并确保资源限制足够满足nginx和后端服务的需求。 7. 重启nginx:如果以上步骤都没有解决问题,尝试重新启动nginx服务器。这可能有助于清除一些临时问题或缓存问题。 总之,要解决nginx vhost 500 internal server error,需要综合考虑各种因素,包括配置文件、文件权限、后端服务和系统资源等。根据错误日志和具体情况,逐步排查问题,并采取相应的修复措施。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值