Home  /  RSS  /  RSS Comments  /  Enter

Nginx? No thank you... Lighttpd

Wednesday, February 1, 2012, by artyom ; 18 comments

Nginx is 3rd (or 2nd depending on the statistics) most popular web server. It seems that it should be one of the best servers around however, every time I have to deal with it I understand that I really don't like it.

When you look deep down into the Nginx web server you realize that it not as good as you think it is. Even though, it is a good reverse proxy and very well suited for serving static files, when it comes to dynamic content it begins to feel sloppy.

It is not uncommon that you have a web site powered by nginx and in reality it works like a load balancer or reverse proxy in front of Apache or other services.

And this is not an accident, this popular webs sever has too many flaws:

I've worked with nginx in different configurations and what I get most of the times is a feeling that the development of this web server is not performed according to protocol specifications but rather according what feature is needed as this specific point.

For example, it was mentioned in nginx source code that "Apache' specific PATH_INFO feature was not implemented. While PATH_INFO is a standardized environment variable defined by CGI 1.1 specifications and is not some "web server specific feature"

I know such note may look petty, but this is the general feeling of the level of implementation of many features.


So despite the popularity of this web server I would not choose it for serving dynamic content. If I need a high quality lightweight web server I'd always choose Lighttpd

Before you jump and say:

Buy lighttpd leaks!

I'll answer: "not true, it does not leak". It is one of the well know urban legends on the Internet.

Quoting:

there are no memory leaks just stupid ways to use lighttpd.

e.g. it is really stupid to stream large files via a fastcgi app, when x-lighttpd-send-file could do a much more efficient job.

For further reading:

So if you consider using Nginx on your web site, I'd strongly recommend to take a look on Lighttpd first.

Comments

Justin, at 2/2/12 1:32 AM

This was the straw that broke the camel's back for me:

http://redmine.lighttpd.net/issues/665

Blatant disregard for users over a fairly trivial and basic piece of functionality.

I've had many many problems with PATH_INFO and friends with lighttpd (though I believe most of them are now fixed). The attitude you see in the bug above is a good demonstration on why those fixes took so long.

As far as config goes, apache is (of course) awful, lighttpd is slightly less awful, but still powerful, and nginx is relatively sane (but limited).

I don't mind the limitations. The "throw everything in one box" is why apache is the way it is (and why we have travesties like php around now), separation of concerns is a good thing (and the unix way), and nginx is closest to getting that right.

artyom, at 2/2/12 9:40 AM

As for me, nginx drives me crazy every time I discover that some feature that you expect to work is not implemented according to RFC specifications.

I would less care about having feature XYZ implemented. However I do explect that if it is implemented it works according to specifications, because that what makes the software portable and independent of the specific web-server platform.

And please note, I'm looking at the web server from the point of view of the framework developer that expects certain things to behave certain way. And Nginx developer looks on RFC as on "suggestion only".

About lighttpd development. There were several cases when I submitted several patches and bug reports and they were accepted and fixed.

Sergey, at 2/3/12 3:19 AM

Thanks for u'r interesting article.

"It requires from the SCGI server to send full HTTP headers instead of parsing Status header. PATH_INFO and SCRIPT_NAME do not work in the same way they were not working with fastcgi."

Igor Sysoev, ask him why he do that.

Last lighttpd 1.5 version from 2007 year?

artyom, at 2/3/12 9:15 AM

Last lighttpd 1.5 version from 2007 year?

1.5 is development version. Stable version 1.4.30 released in Dec. 2011.

Marius, at 2/4/12 5:28 PM

I was wondering about the performance though, using cppcms of course. Did you or are you considering doing some test ? After all cppcms is faster if the web server is faster.

artyom, at 2/4/12 5:39 PM

I was wondering about the performance though, using cppcms of course. Did you or are you considering doing some test ? After all cppcms is faster if the web server is faster.

As far as I remember. the performance differences are negligible between lighttpd and nginx. Also note that generally serving dynamic content is much more complex task then serving for example static file were you have nice helpers as sendfile system call. So the web server would unlikely be the bottle-neck for dynamic content.

Sergey Lavrov, at 2/5/12 3:36 PM

Dear Artyom!

First of all thank you for your great work!

Second, I share your opinion about nginx mostly based on my experience in implementing large-scale distributed systems, which of cause not only base on serve static files. I tried many times to use nginx in my projects and projects of my customers, but always had small or big problems in the implementation of the requirements using ngnix. Personaly I really like lighttpd and use it for ages without any problem. For my local tests using cppcms lighttpd better (faster) than ngnix.

DenBrown, at 2/22/12 11:46 PM

"First of all it is not capable to handle CGI scripts at all."

Good for security and perfomance. If somebody still needs this obsolete interface, so there are a plenty of wrappers.

"It does not support automatic control of a lifetime of FastCGI and SCGI processes."

Lighttpd itself dosn't support it too. Basically it's a job for application server like uWSGI, Phusion Passenger, PHP-FPM, etc...

"FastCGI support was broken for a long time and only recently we got an option to setup a CGI variable PATH_INFO correctly as required by CGI RFC."

It's not true. With NGINX you have a lof of ways to set PATHINFO. And fastcgisplitpathinfo isn't the only one. Btw, this directive appeared in version 0.7.31, which was released more then 3 years ago. In terms of stagnating Lighttpd it may be "recently".

"SCGI support is totally broken as"

You're just wrong here. Probably you even never use nginx yourself.

"Basic security feature like Options -FollowSymLinks that is implemented in all normal web servers starting from a "fat" Apache to a small and tiny thttpd is not implemented!"

I don't know much about tiny thttpd. But in Apache and Lighttpd this option is just noop, cuz of race conditions in the code. Yeah, it will protect you from teenager, but don't protect you from purposeful attack.

Btw, nginx folks implement disable_symlinks option in the recent version WITHOUT race condition in the code. Therefore, it's really safe.

"The documentation quality is overall lower then the quality of documents of Lighttpd or Apache web servers. Also the configuration is generally much more complicated and verbose even for simple tasks."

Documentation maybe. But, IMHO, nginx configuration is much simpler than Apache and more flexible than lighty.

artyom, at 2/24/12 6:09 PM

First of all it is not capable to handle CGI scripts at all.

Good for security and perfomance.

Security? How exactly

Performance? Depends on the task you do.

If somebody still needs this obsolete interface, so there are a plenty of wrappers.

If is far from being obsolete

It does not support automatic control of a lifetime of FastCGI and SCGI processes."

Lighttpd itself dosn't support it too.

Starting, stopping, restarting on fail and handling a pool is not considered handling lifetime? Hmmm....

It's not true. With NGINX you have a lof of ways to set PATHINFO. And fastcgi_split_path_info isn't the only one.

First of all it is the only right way. Because PATH_INFO must be urldecoded according to RFC specifications.

SCGI support is totally broken as

You're just wrong here. Probably you even never use nginx yourself.

Have to tried to use Status header as expected by the CGI specifications it is based on? I assume you hadn't. Without full HTTP headers it would just not work.

Second is it has same PATH_INFO problem as FastCGI used to have. PATH_INFO must be URL-decoded... And it is not possible to specify it this way in Nginx.

For me it is broken. Of course you can workaround it on application side... But it makes SCGI considerably less useful.

Btw, nginx folks implement disable_symlinks option in the recent version WITHOUT race condition in the code. Therefore, it's really safe.

It protects if the LAST file is symbolic link, if there is a symbolic link in the path (directory) it would not handle it.

So don't tell me stories about its "safety"

VBart, at 2/29/12 4:13 PM

Hello, may I clarify some things?

First of all it is the only right way. Because PATH_INFO must be urldecoded according to RFC specifications.

You can set PATH_INFO this way:

location ~ \.php(?P<pathinfo>.+)$ {
    ...
    fastcgi_param PATH_INFO $pathiinfo;
}

and it will be urldecoded. NGINX configuration syntax is very flexible.

Have to tried to use Status header as expected by the CGI specifications it is based on?

NGINX SCGI upstream module can parse the standard response Status-Line as well as the "Status" header. If you encounter any bugs here, please, fill a bug-report.

http://trac.nginx.org/nginx/

It protects if the LAST file is symbolic link, if there is a symbolic link in the path (directory) it would not handle it.

http://nginx.org/r/disable_symlinks

"on" - If any component of the pathname is a symbolic link, access to a file is denied.

artyom, at 3/1/12 1:27 PM

You can set PATH_INFO this way

Yes it works for FastCGI (not SCGI) for latest Nginx version. It wasn't for a long time ago.

NGINX SCGI upstream module can parse the standard response Status-Line as well as the "Status" header. If you encounter any bugs here, please, fill a bug-report.

Quoting: http://wiki.nginx.org/NgxSCGIModule

The current implementation also only supports SCGI response that conforms to HTTP 1.0; this means that the response must start with an HTTP 1.x status line (like HTTP 1.0 200 OK) and not by using the Status header.

And the last time I checked it was not working.

I assume you refer to this fix:

http://trac.nginx.org/nginx/changeset/4438/nginx

That was done... four weeks ago? So cool!

"on" - If any component of the pathname is a symbolic link, access to a file is denied.

But this is now what is required frequently...

VBart, at 3/2/12 1:20 PM

Yes it works for FastCGI (not SCGI) for latest Nginx version. It wasn't for a long time ago.

Hm...

location ~ \.php(?P<pathinfo>.+)$ {
    ...
    scgi_param PATH_INFO $pathiinfo;
}

doesn't work?

assume you refer to this fix: http://trac.nginx.org/nginx/changeset/4438/nginx That was done... four weeks ago? So cool!

No, it's just some minor fix according to this ticket: http://trac.nginx.org/nginx/ticket/66

Quoting: http://wiki.nginx.org/NgxSCGIModule

This page is about 3rd party module and was outdated, since NGINX has got buit-in SCGI module (added in nginx 0.8.42 - 21 Jun 2010), which supports the "Status" header.

Just lack of official documentation, and now we're actively working on it:

http://trac.nginx.org/nginx/timeline?repo-nginx_org=on

http://nginx.org/en/docs/

VBart, at 3/2/12 3:12 PM

btw, actual wiki page about SCGI is here: http://wiki.nginx.org/HttpScgiModule and it is mentioned on build-in modules wiki page: http://wiki.nginx.org/Modules

hm, I don't know how you could found the old one... anyway it must be deleted.

artyom, at 3/2/12 5:02 PM
scgi_param PATH_INFO $pathiinfo;

doesn't work?

No: it would not urldecode PATH_INFO

No, it's just some minor fix

I've actually tested SCGI with Nginx:

http://cppcms.com/wikipp/en/page/cppcms_1x_tut_web_server_config#Nginx..SCGI

It works only if full HTTP headers sent. It does not behave correctly with CGI headers... I actually tested this when I had written this wiki.

SCGI is broken in Nginx... Sorry

VBart, at 3/2/12 7:20 PM
$ cat flup-scgi.py
#!/usr/bin/env python

def application(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/plain')])
    yield 'Path info: ' + environ['PATH_INFO']

if __name__ == '__main__':
    from flup.server.scgi import WSGIServer
    WSGIServer(application, bindAddress=('localhost', 9000)).run()

$ ./flup-scgi.py
$ curl localhost:8000/hello/%44%45%43%4f%44%45%44%20%73%6F%72%72%79
Path info: /DECODED sorry

$ cat test.conf
error_log  logs/error.log debug;

events {}

http {
    server {
        listen      8000;
        server_name localhost;

        location ~ ^/hello(.*)$ {
            scgi_pass localhost:9000;

            scgi_param PATH_INFO      $1;
            scgi_param REQUEST_METHOD $request_method;
        }
    }
}


logs/error.log:
...
2012/03/02 21:05:24 [debug] 8473#0: *1 http request line: "GET /hello/%44%45%43%4f%44%45%44%20%73%6F%72%72%79 HTTP/1.1"
2012/03/02 21:05:24 [debug] 8473#0: *1 http uri: "/hello/DECODED sorry"
2012/03/02 21:05:24 [debug] 8473#0: *1 http args: ""
2012/03/02 21:05:24 [debug] 8473#0: *1 http exten: ""
2012/03/02 21:05:24 [debug] 8473#0: *1 http process request header line
2012/03/02 21:05:24 [debug] 8473#0: *1 http header: "User-Agent: curl/7.21.4 (x86_64-pc-linux-gnu) libcurl/7.21.4 OpenSSL/1.0.0g zlib/1.2.5 libidn/1.22"
2012/03/02 21:05:24 [debug] 8473#0: *1 http header: "Host: localhost:8000"
...
2012/03/02 21:05:04 [debug] 8473#0: *1 http script copy: "PATH_INFO"
2012/03/02 21:05:04 [debug] 8473#0: *1 http script capture: "/DECODED sorry"
2012/03/02 21:05:04 [debug] 8473#0: *1 scgi param: "PATH_INFO: /DECODED sorry"
2012/03/02 21:05:04 [debug] 8473#0: *1 http script copy: "REQUEST_METHOD"
2012/03/02 21:05:04 [debug] 8473#0: *1 http script var: "GET"
2012/03/02 21:05:04 [debug] 8473#0: *1 scgi param: "REQUEST_METHOD: GET"
2012/03/02 21:05:04 [debug] 8473#0: *1 scgi param: "HTTP_USER_AGENT: curl/7.21.4 (x86_64-pc-linux-gnu) libcurl/7.21.4 OpenSSL/1.0.0g zlib/1.2.5 libidn/1.22"
2012/03/02 21:05:04 [debug] 8473#0: *1 scgi param: "HTTP_HOST: localhost:8000"
2012/03/02 21:05:04 [debug] 8473#0: *1 scgi param: "HTTP_ACCEPT: */*"
...
2012/03/02 21:05:04 [debug] 8473#0: *1 http scgi header: "Status: 200 OK"
2012/03/02 21:05:04 [debug] 8473#0: *1 http scgi header: "Content-Type: text/plain"
2012/03/02 21:05:04 [debug] 8473#0: *1 http scgi header done
...

Sorry, it works well.

Sam Alexander, at 8/15/13 5:19 AM

Hi, sorry for necrocommenting, but I had to chime in. I'm the author of the SCGI C library (http://www.xamuel.com/scgilib/) and artyom is quite right. SCGI is broken in nginx. The following response is taken verbatim from the SCGI protocol (http://python.ca/scgi/protocol.txt):

Status: 200 OK\n\rContent-Type: text/plain\n\r\n\r42

But this causes nginx to issue an error "upstream sent invalid header while reading response header from upstream" and send the client a 502.

Nginx will happily route the intended response if it instead goes

HTTP/1.1 200 OK\n\r\n\r42

Even though this is blatantly contrary to the SCGI protocol.

This has just caused me to pull my hair out for three hours, all for something nginx's developers could've fixed in minutes. I'm deeply grateful to artyom's comments here, without them I would never have gotten to the bottom of the issue.

artyom, at 8/15/13 7:06 AM

@Sam

Actually this bug was fixed (I hope) in nginx 1.1.17 also in any case the number of problems with nginx and scgi/fcgi is so huge that it makes me wonder why the hell on earth should be use nginx.

I personally use lighttpd in heavily loaded production environment and it works very well.

salem, at 11/28/14 3:07 PM

I spent Last week configuring Two VPS servers for first time . I was have two choices Lighttpd Or Nginx So I ended installing Lighttpd on server Nginx on the other I notice Lighttpd configuration alot easier then Nginx Specially to make website work for first time . Only good about Nginx configuration syntax looks similar to Apache syntax .so who moving from Apache will see it easier . Now for real comparison Nginx use More MEM then Lighttpd

This is nginx On VPS1 USER PID %CPU %MEM VSZ RSS
www-data 3718 0.0 0.5 10136 1364 www-data 3719 0.0 0.5 10136 1364
www-data 3720 0.0 0.5 10136 1364
www-data 3721 0.0 0.6 10136 1716 Total ~5.4MB this is Lighttpd On VPS2 USER PID %CPU %MEM VSZ RSS
www-data 1396 0.0 1.0 5128 1396 Totall ~1.36MB I only add single index.html file with "Hello world" contents And Lighttpd was load 200Ms faster I test it here http://browserspy.dk/webserver.php server lighttpd/1.4.31 Time to contact web server 0.343193 seconds server nginx/1.2.1 Time to contact web server 0.588151 seconds

BTW the two VPS on diffrent provider VPS2 use and E3-1240 V2@3.40GHz 128MB and VPS1 use E3-1230 V2@3.30GHz with 256MB . I'll remove nginx on VPS1 adn wi'll install Lighttpd to see the result .

Add Comment:

 
 the email would not displayed
 

You can write your messages using Markdown syntax.

You must enable JavaScript in order to post comments.

Pages

Categories