Home           RSS           Search

May 10, 2013





Lighttpd "how to" - Fast and Secure Web Server



The Lighttpd web server, also oddly called "lighty" by the author, is an excellent tool for small to medium sized web sites. The server is a great way to off load static content like pictures or binary downloads from an overloaded Apache server and it is perfect for small home web servers or corporate LAN. It is incredibly fast serving out pages under load and very easy to setup. Lighttpd is more configurable than tHttpd and significantly more efficient than the resource hungry Apache web server, running faster on the same hardware while using less than 1/10th the resources. Lighttpd is a fast, secure and efficient web server.

Security, speed, compliance, and flexibility -- all of these describe lighttpd (pron. lighty) which is rapidly redefining efficiency of a web server; as it is designed and optimized for high performance environments. With a small memory footprint compared to other web-servers, effective management of the CPU-load, and advanced feature set (FastCGI, SCGI, Auth, Output-Compression, URL-Rewriting and many more) lighttpd is the perfect solution for every server that is suffering load problems. And best of all it's Open Source licensed under the revised BSD license. Lighttpd powers several popular Web 2.0 sites like YouTube, wikipedia and meebo. Its high speed io-infrastructure allows them to scale several times better with the same hardware than with alternative web-servers. Its event-driven architecture is optimized for a large number of parallel connections (keep-alive) which is important for high performance AJAX applications. Lighttpd.net



Make sure to also check out our Nginx web server "how to". It is faster than Lighttpd, offers more configuration options and has a very active development team. Everything that can be done with lighty can be done more efficiently with Nginx.



The configuration file (lighttpd.conf)

In the following example we are going to setup a simple web server to explain the basics and get you started with a working server. The daemon will load a few modules to compress outgoing data and set the expires header to reduce bandwidth of client cached traffic. Full logging is on, in the default Apache format, and the internally generated reporting page is active. The security module "evasive" will be used to limit access per ip address to avoid DDOS by multi-connection abusers. Finally, we are going to set up restriction filters by ip to limit access to the "hidden_dir" directory structure where you might put more sensitive non-public data.

Our goal is to setup a fast serving and cpu/disk efficient web server, but most importantly a very secure web server. This configuration will work for Lighttpd 1.4.x and 1.5.x . For the purpose of this example we built the newest stable version of Lighttpd 1.4.x from source.

Below you will find the link to the calomel.org lighttpd config example file and below that is the same lighttpd.conf file in a text box. Both formats are available to make it easier for you to review the code. This example is a fully working config file with the exception of setting up a few variables for your environment.

#######################################################
###  Calomel.org  lighttpd.conf  BEGIN
#######################################################
#
#### modules to load
server.modules           = ( "mod_expire",
			     "mod_auth",
                             "mod_access",
                             "mod_evasive",
                             "mod_compress",
                             "mod_status",
                             "mod_redirect",
                             "mod_accesslog" )

#### performance options (aggressive timeouts)
server.max-keep-alive-requests = 6
server.max-keep-alive-idle = 15
server.max-read-idle     = 15
server.max-write-idle    = 15

## number of child worker processes to spawn (0 for lightly loaded sites)
# server.max-worker      = 0

## number of file descriptors (leave off for lighty loaded sites)
# server.max-fds         = 512

## maximum concurrent connections the server will accept (1/2 of server.max-fds)
# server.max-connections = 256

## single client connection bandwidth limit in kilobytes (0=unlimited)
connection.kbytes-per-second = 0

## global server bandwidth limit in kilobytes (0=unlimited)
server.kbytes-per-second = 0

#### bind to interface (default: all interfaces)
server.bind              = "127.0.0.1"

#### bind to port (default: 80)
server.port              = 80

#### run daemon as uid (default: don't care)
server.username          = "lighttpd"

#### run daemon as gid (default: don't care)
server.groupname         = "lighttpd"

#### set the pid file (newsyslog)
server.pid-file          = "/var/run/lighttpd.pid"

#### name the server daemon publicly displays
server.tag               = "lighttpd"

#### static document-root
server.document-root     = "/var/www/htdocs/"

#### chroot() to directory (default: no chroot() )
server.chroot            = "/"

#### files to check for if .../ is requested
index-file.names         = ( "index.html" )

#### disable auto index directory listings
dir-listing.activate     = "disable"

#### disable ssl if not needed
ssl.engine               = "disable"

#### compress module
compress.cache-dir       = "/tmp/lighttpd_tmp/"
compress.filetype        = ("text/plain", "text/html", "text/css", "image/png")

#### expire module
expire.url               = ( "" => "access plus 6 hours")

#### accesslog format (enable for using a proxy, like Pound, in front of Lighttpd)
# accesslog.format       = "%{X-Forwarded-For}i %V %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\""

#### accesslog module
accesslog.filename       = "/var/log/lighttpd/access.log"

#### error log
server.errorlog          = "/var/log/lighttpd/error.log"

#### error pages
server.errorfile-prefix  = "/var/www/htdocs/errors/errorcode-"

#### enable debugging (un-comment to debug server problems)
# debug.log-request-header   = "enable"
# debug.log-response-header  = "enable"
# debug.log-request-handling = "enable"
# debug.log-file-not-found   = "enable"

#### mod_evasive
evasive.max-conns-per-ip = 250

#### limit request method "POST" size in kilobytes (KB)
server.max-request-size  = 1

#### disable multi range requests
server.range-requests    = "disable"

#### disable symlinks
server.follow-symlink    = "disable"

#### ONLY serve files with all lowercase file names
server.force-lowercase-filenames = "disable"

#### server status
status.status-url        = "/hidden_dir/server-status"

#### password protected area
# auth.backend                   = "htdigest"
# auth.backend.htdigest.userfile = "/var/www/htdocs/hidden_dir/passwd_file"
# auth.require                   = ( "/hidden_dir" =>
#                                    (
#                                      "method"  => "digest",
#                                      "realm"   => "REALM_NAME",
#                                      "require" => "user=USER_NAME"
#                                    )
#                                  )

##
#### Blocks Section: The order is important.
#### Test all block rules before going live.
##

#### request method restrictions (v1.5.x ONLY)
# $HTTP["request-method"] !~ "^(GET|HEAD)" {
#     url.access-deny = ( "" )
#  }

#### deny access to unwanted bots or bad clients 
# $HTTP["useragent"] =~ "(Google|BadGuy)" {
#   url.access-deny = ( "" )
# }

#### access control list for hidden_dir (not for use behind proxies)
# $HTTP["remoteip"] !~ "127.0.0.1|10.10.10.2|20.10.20.30" {
#     $HTTP["url"] =~ "^/hidden_dir/" {
#       url.access-deny = ( "" )
#     }
#  }

#### url redirect requests for calomel.org to www.calomel.org
# $HTTP["host"] =~ "^(calomel.org)$" {
#         url.redirect = ( "/(.*)" => "http://www.%1/$1")
# }

#### stop image hijacking (anti-hotlinking)
# $HTTP["referer"] !~ "^(http://calomel\.org|http://www\.calomel\.org)" {
#     url.access-deny = ( ".jpg", ".jpeg", ".png", ".avi", ".mov" )
# }

#### virtual host limits
# $HTTP["host"] !~ "^(calomel\.org|www\.calomel\.org)" {
#     url.access-deny = ( "" )
#  }

#### stop referer spam
# $HTTP["referer"] =~ "(tarotathome|casinospam)" {
#     url.access-deny = ( "" )
#  }

#### mimetype mapping
mimetype.assign             = (
  ".pdf"          =>      "application/pdf",
  ".sig"          =>      "application/pgp-signature",
  ".spl"          =>      "application/futuresplash",
  ".class"        =>      "application/octet-stream",
  ".ps"           =>      "application/postscript",
  ".torrent"      =>      "application/x-bittorrent",
  ".dvi"          =>      "application/x-dvi",
  ".gz"           =>      "application/x-gzip",
  ".pac"          =>      "application/x-ns-proxy-autoconfig",
  ".swf"          =>      "application/x-shockwave-flash",
  ".tar.gz"       =>      "application/x-tgz",
  ".tgz"          =>      "application/x-tgz",
  ".tar"          =>      "application/x-tar",
  ".zip"          =>      "application/zip",
  ".mp3"          =>      "audio/mpeg",
  ".m3u"          =>      "audio/x-mpegurl",
  ".wma"          =>      "audio/x-ms-wma",
  ".wax"          =>      "audio/x-ms-wax",
  ".ogg"          =>      "application/ogg",
  ".wav"          =>      "audio/x-wav",
  ".gif"          =>      "image/gif",
  ".jpg"          =>      "image/jpeg",
  ".jpeg"         =>      "image/jpeg",
  ".png"          =>      "image/png",
  ".xbm"          =>      "image/x-xbitmap",
  ".xpm"          =>      "image/x-xpixmap",
  ".xwd"          =>      "image/x-xwindowdump",
  ".css"          =>      "text/css",
  ".html"         =>      "text/html",
  ".htm"          =>      "text/html",
  ".js"           =>      "text/javascript",
  ".asc"          =>      "text/plain",
  ".c"            =>      "text/plain",
  ".cpp"          =>      "text/plain",
  ".log"          =>      "text/plain",
  ".conf"         =>      "text/plain",
  ".text"         =>      "text/plain",
  ".txt"          =>      "text/plain",
  ".dtd"          =>      "text/xml",
  ".xml"          =>      "text/xml",
  ".mpeg"         =>      "video/mpeg",
  ".mpg"          =>      "video/mpeg",
  ".mov"          =>      "video/quicktime",
  ".qt"           =>      "video/quicktime",
  ".avi"          =>      "video/x-msvideo",
  ".asf"          =>      "video/x-ms-asf",
  ".asx"          =>      "video/x-ms-asf",
  ".wmv"          =>      "video/x-ms-wmv",
  ".bz2"          =>      "application/x-bzip",
  ".tbz"          =>      "application/x-bzip-compressed-tar",
  ".tar.bz2"      =>      "application/x-bzip-compressed-tar"
 )
#
#######################################################
###  Calomel.org lighttpd.conf  END
#######################################################





Starting the install

To get started, you need to first install lighttpd on your machine. The source code is available from the lighttpd.net site and practically every distribution has pre-made packages if you prefer those. The install is very easy and it will not take you much time.

If you are building from source then our example is built with the following command arguments:

./configure --without-bzip2 && make && make install

Once the install is done you need to look at the config file for Lighttpd called "lighttpd.conf" above. This file is the primary config file and will contain all of the directives needed to make lighttpd work like you want it to. The lighttpd.conf file can normally be found under the /etc directory. For example /etc/lighttpd.conf . Move or rename the config the from default install and replace it with the config supplied above.



Explaining the directives in lighttpd.conf

Now we need to edit the config file for your environment. Lets take a look at each of the directives that need attention.

performance options (aggressive timeouts) :   These are the timeouts for all connections to the server. We are using aggressive timeouts to close the child processes quickly to save memory and CPU time. No need to keep a connection open if it is not being used.

server.max-worker :   Is the number of worker processes to spawn. A worker is the same as a child process in Apache. Editing this is usually only needed on servers which are fairly loaded and the network handler calls delay often; for example if new requests are not handled instantaneously. (Default: 0) NOTE: If you use server.max-worker, the mod_status module will not show the statistics of all the server's children combined. You will see different stats with each request.

server.max-fds :   This is maximum amount of file descriptors also referred to as open files on OpenBSD. To see what the current max file descriptors are type "ulimit". On OpenBSD it is the value for "ulimit -n". The file descriptor limit is maximum amount of files a single user or process of that user can have open at any one time. In terms of the web server, this is the total amount of functions, accepted user connections, or any other jobs including php or mysql the daemon/user wants to do.

server.max-connections :   This specifies how many total concurrent connections you want the server to support. A safe number is 1/2 the value specified for "server.max-fds".

connection.kbytes-per-second :   This is the limit in kilobytes (KB) that each individual connection will be limited to. Keep in mind that a limit below 32kb/s might actually limit the traffic to 32kb/s. This is caused by the size of the TCP send buffer.

server.kbytes-per-second :   This is the limit in kilobytes (KB) the entire web server will be limited to. Keep in mind that a limit below 32kb/s might actually limit the traffic to 32kb/s. This is caused by the size of the TCP send buffer.

server.bind :   This is the interface the daemon will bind to. If you want to bind to a specific IP then put it here. If you want lighttpd to bind to all interfaces then comment this line.

server.port :   By default the daemon will bind to port 80. If you have another port you want to bind to, like 8080 for non-root bindings or alternate web servers, enter it here.

server.username and server.username :   This is the user and group the daemon will run as. If you want to have lighttpd run as a non privileged user this is the place to put it. If you comment out these lines then the daemon will run as the user who executed it or "nobody" depending on the OS distribution you use.

server.pid-file :   The process id file allows one to HUP the server or rotate logs. Make sure the pid file can be made by verifying the "run" directory of your choice.

server.tag :   This is the name of the server lighttpd will send to the remote clients in the HTTP headers. Normally this is used by bots to classify each type of server compiling a list of how many types of web servers are running on the web. The author of lighttpd would like everyone to keep it as "lighttpd", but you can put anything in this string you want. To remain a little more anonymous you might enter the name of your server or host name or the name of your favorite TV show.

server.document-root :   This is the top level document root of the web tree.

server.chroot :   The tells the daemon to chroot user access to the top level document root.

index-file.names :   The name of the index file the daemon should look for in a directory if the client does not specify one. By default we are going to look for index.html .

dir-listing.activate :   This directive enables or disables directory listings on a directory that does not contain a index.html file or any file listed in "index-file.names".

compress.cache-dir and compress.filetype :   The compress setting will allow the daemon to look for file types you specify and compress those files with gzip and place them into the cache-dir. This can save a lot of outgoing bandwidth since text can compress by 80%. As an added bonus remote clients will get the data quicker and your site looks significantly faster. It is important to remember that _not_ all file types will compress. JPG for example is natively compressed and compressing it again with gzip will actually make JPG's larger.

expire.url :   The expire header tag will tell clients they should keep a copy of the object they already downloaded for a specified amount of time. In our example we are telling the client to keep data in their local browser cache from the time they first accessed the data till at least 6 hours later. This also saves a lot of upload bandwidth for you. Instead of clients going from page to page downloading the same picture banner over and over again, they can keep a copy locally and just get the changes on your site. Imagine a client getting 5 pages from your site. Each page has a banner that is 15KB. With expires headers enabled that client will only download the banner once instead of 5 times (15KB compared to 75KB) saving your upload bandwidth and making your site look a lot quicker responding.

accesslog.format :   If you use a proxy in front of Lighttpd then the ip address of the proxy will show up in the logs instead of the remote client's ip. Uncomment this line if you use a proxy, like Pound, to read the "X-Forwarded-For" header and input the remote clients ip address into the Lighttpd access log file. If you do _not_ use a proxy then keep this line commented. The default accesslog.format lighttpd uses is the proper format and will work with Webalizer, Analog and AwStats.

accesslog.filename and server.errorlog :   This is the location of the access log and error logs for the daemon. Make sure the user that the daemon is running as can access and write to that directory.

server.errorfile-prefix :   This directive is for the custom error page directory if you wish to use one. If not, comment this line out and lighttpd will generate its own. In the example we make the directory "/var/www/htdocs/errors/" to include the error html files. Then for each of the error codes (400-417 and 500-505) we make a custom error page. The names of each error page start with the string "errorcode-" as we specified in the directive. So, error 404's file would be "errorcode-404.html" and error 505's would be "errorcode-505.html".



Want more speed? Make sure to also check out the Network Speed and Performance Guide. With a little time and understanding you could easily double your firewall's throughput.

debug.log-??? :   These are the options you would un comment if you would like to see all of the error responses and client requests. Very useful in finding bugs or see if anything goes wrong in a new website addition.

mod_evasive :   This module will limit the amount of times a simple ip address can connect to our server and receive the proper page. In the example we set the limit to 25 connections per ip. After this hard limit is reached the client will get the error page 404.

limit request method "POST" size in kilobytes (KB) :   The server.max-request-size is the limit in kilobytes for data accepted through the request method "POST". If you run a web server that does not accept uploads then you can set the POST request method data limit to one(1) kilobyte, in effect POST request will always be denied. This is not really necessary, but it will help deny confused browsers and malicious bots. The maximum size in kbytes of the request (header + body). Only applies to POST requests. Default: 2097152 (2GB)

server.range-requests :   defines if range requests are allowed or not. Range request are requests of one or more sub-ranges of a file. Range requests are very helpful for interrupted downloads and fetching small portions of huge files. The problem is download accelerators car put a huge load on your server by downloading many different parts of a file at the same time. Most of the time the download accelerator is broke and is not helping anyone by using up extra bandwidth. Internet Explorer (IE) also has problems opening up some files like PDF's is the range option is enabled. The example conf disables server.range-requests just to be safe. (Default: enabled)

server.follow-symlink :   If you do not use, and do not expect to use sym links then disable them.

server.force-lowercase-filenames :   This directive will force lighttpd to _ONLY_ serve files with lower case file names. Be careful if you enable this one. A file with mixed case, like "TeSt.html" will not be served due to upper case letters if this directive is enabled. (Default: disabled)

status.status-url :   This is the lighttpd dynamically generated status page. It will show the time the server has been running, total traffic served and some time based stats like traffic for the last 5 minutes. Notice we put this page into the "hidden_dir" which is explain below.

password protected area :   If you have an area of the web site you only want authorized personnel to see then you can protect it. This set of directives will password protect the /hidden_dir and all files and directories under it, like the server-status page. We will use the htdigest method which is the best choice to use especially for non-https sites. It will not send any of the passwords in clear text. Check "man htdigest" for details.

To make a password file use the binary htdigest in the form:

htdigest -c /var/www/htdocs/hidden_dir/passwd_file REALM_NAME USER_NAME

request method restrictions :   Request Method restrictions were added in v1.5.x. This allows you to filter on GET, HEAD, POST, SEARCH, etc. We will be limiting access to our example server to GET and HEAD requests only as we do not allow uploads or any other options due to security concerns. All other request methods will get an error.

deny access to unwanted bots or bad clients :   If you wanted to deny access to certain user agents like the Google bot or perhaps all old versions of IE you can do that here. Put in part of the string of the user agent you want to deny.

access control list :   This is a way you can define a directory and only allow clients coming from the specified ips to have access. This might be nice to allow internal LAN clients access to the status pages or employee contact information and deny other clients. In our example we will allow the clients coming from localhost 127.0.0.1, an internal LAN ip 10.10.10.2 and some external ip 20.10.20.30 to access the protected "hidden_dir" directory.

url redirect :   If you prefer clients who connect to your server to instead use the sub domain www use this rule. For example, if a browser connects to calomel.org they will be redirected to the url www.calomel.org . If they then save the bookmark it will show up as the www sub domain.

stop image hijacking :   Image hijacking is when someone makes a link to your site to one of your pictures or videos, but displays it on their site as their own content. The reason this is done is to send a browser to your server to use your bandwidth and make the content look like part of the hijacker's site. This is most common as people make links to pictures and add them to a public forum or blog listing. They get to use your picture in their content and not have to use their bandwidth or server to host the file. In order to keep your bandwidth usage low you should block access to images from those clients who are not requesting the connection from your site. Note, this function can be used for any kind on content. Just add the file types to the url.access-deny list. If would like more ideas on lowering bandwidth usage check out our Saving Webserver Bandwidth (Tips).

virtual host limits :   A virtual host is the hostname of your web server. For example this site is called calomel.org. Some bots and scanners will try to access your site using the ip address or no hostname header at all to bypass virtual host limitations. We can block this type of behavior by requiring all clients who want to access our server to reference us by our official host name. This will block anyone who is scanning ip addresses or trying to be mischievous, but allow normal clients like browsers and bots like Google.

stop referer spam :   Referer spam is more of an annoyance than a problem. A web site will connect to your server with the referer field referencing their web site. The idea is that if you publish your web logs or statistics then their hostname will show up on your page. When a search bot like Google comes by it will see the link from your site to theirs and give them more PageRank credit. First, never make your weblogs public. Second, block access to referer spammers with these lines.

mimetype.assign :   These directive simply allow our server to send the the proper file type and application type to the clients. The list in the example is the same as the default lighttpd config options.



Starting the daemon

Now that you have the config file installed and configured you can start the daemon by hand with "/usr/local/sbin/lighttpd -f /etc/lighttpd.conf" or add the following into /etc/rc.local to start the lighttpd daemon on boot.

#### lighttpd start in /etc/rc.local
if [ -x /usr/local/sbin/lighttpd ]; then
   echo -n ' lighttpd'; /usr/local/sbin/lighttpd -f /etc/lighttpd.conf
fi



In Conclusion

Lighttpd has many more options if you wish to use fast-cgi or password authentication. I would highly suggest taking some time to read the lighttpd.net documentation if you need more web functionality. If you are happy with the options we have started out with then at this point all that is left is finding some content to serve and setup your web tree.



SECURITY NOTE: We highly recommend using a reverse proxy like Pound to front end your web servers. Pound offers an added layer of security and can be used to filter most malicious requests from ever hitting the web server. Use Pound to stop the bad bots and allow your web server to focus on good clients. For a complete explanation and examples check out the Calomel.org Pound reverse proxy "how to".


Questions?

Do you have a listing of all of the lighttpd directives?

The lighttpd site has all of the config directives listed on the Lighttpd Configuration File Options page.

Can you suggest a good web page optimization reporting site?

We highly suggest taking a look at PageTest - Web Page Optimization and Performance Test. It is free and will analyze a page and show graphs pertaining to your sites performance and speed of delivery.

How can I test to see if my server is actually gzip'ing served files?

Check outWhatIsMyIp Gzip Test page.

How do I rotate the access logs on OpenBSD?

You need to edit the /etc/newsyslog file and add an entry for lighttpd. The following example will rotate the access log every month on the first day ($M1D0) keeping 12 past log files. The new file will have permissions 640 and be owned/grouped as lighttpd:lighttpd.
# configuration file for newsyslog
# logfile_name               owner:group       mode count size when  flags file
/var/log/lighttpd/access.log lighttpd:lighttpd 640  12    *    $M1D0 B     /var/run/lighttpd.pid

How can I clear out the old compressed files?

If you use the mod_compress module you have a directory that holds the compressed files. If the html page is ever changed than lighttpd will make a new version of the compressed html file to reflect the changes. If you want to clean out the compress directory (/tmp/lighttpd_tmp/ in our example) then you can run the following cron job. It will run every six(6) hours and clear out any file or directory that is more than 60 minutes old. Lighttpd in turn will notice the compressed files have been removed and make new ones as they are requested by remote clients.
#minute (0-59)
#|   hour (0-23)
#|   |    day of the month (1-31)
#|   |    |   month of the year (1-12)
#|   |    |   |   day of the week (0-6 with 0=Sun)
#|   |    |   |   |   commands
#|   |    |   |   |   |
#### Lighttpd tmp compress file cleanup
00   */6 *    *   *   find /tmp/lighttpd_tmp/ -type f -mmin +60 -exec rm -rf {} \;

Can Lighttpd use multiple cores or cpu's?

Lighttpd is a single-threaded, single-process, event-based, non-blocking-IO web server. While this is perfect for the performance it doesn't scale very well over CPUs. It has been shown that lighty scales mostly linearly on multicore, multi-processor boxes if you set the number of workers (server.max-worker) equal to two times the number of cores if you are CPU bound (for heavy rewrites _and_ heavy proxying). In most cases you don't have to care about it as lighty will usually not be able to utilize the full CPU to fill the network-pipe. Most of the time the bottle neck will be the slowest IO in the system, namely the hard drive.

I am interested in setting up a SSL encrypted server (https port 443). Can your help me?

Yes. Please check out our Guide to Webserver SSL Certificates. Setting up SSL through Lighttpd is quite easy. What is most important is understanding how SSL encryption works and what your expectations should be.

Do you know about Nginx web server? Can your help me?

Nginx, pronounced "Engine X", is an incredibly efficient web server that rivals even Lighttpd. We highly recommend checking it out on the Calomel.org Nginx web server "how to" page. It is faster, more memory efficient and has more options than Lighttpd as well as having a very active development base.

I am looking for a simpler web server. Can your help me?

If you want a dead simple web server then check out the tHttpd "how to". It is a web server that is ready to use by executing one line and it works really well too.





Questions, comments, or suggestions? Contact Calomel.org or