Introduction

Running lighttpd2

You need two config files for lighttpd, which are usually in the following two locations:

The contrib/ directory in the sources includes example config files.

Then start lighttpd2 with:

/usr/sbin/lighttpd2 -c /etc/lighttpd2/angel.conf

The process will not fork into background, you need to do that yourself if you want that.
Our recommended way to run lighttpd2 is runit (have a look at the contrib/service directory).

Main Configuration

At the heart of each webserver is the configuration file. It controls the whole behaviour and therefore has to be mighty but at the same time easy enough to get the desired results without hassle.
In the lighttpd config you control how it reacts on requests. To achieve this you have to express some kind of logic – just like in a programming language.

Basic Syntax

The syntax for the lighttpd 2.0 configuration file is somewhat similar to various programming languages – kind of a mixture. But don’t be afraid, it is really simple. No pointers involved :)
The basic blocks are values, variables, function calls and conditions.

Values

Boolean

There are two boolean values:

  • true
  • false

Integers

Integers are stored as signed ints with 64 bits (max value around 9 * 10^18). There are three basic ways to use them:

  • decimal integers: starting with any non zero digit, like 128
  • octal integers: starting with a zero, like 0644
  • hexadecimal integers: starting with 0x like 0xff

All three can have a suffix representing a factor the number gets multiplied with:

  • byte: 1
  • kbyte: 1024
  • mbyte: 1024*1024
  • gbyte: 1024*1024*1024
  • tbyte: 1024*1024*1024*1024
  • pbyte: 1024*1024*1024*1024*1024
  • bit: 1 / 8
  • kbit: 1024 / 8
  • mbit: 1024*1024 / 8
  • gbit: 1024*1024*1024 / 8
  • tbit: 1024*1024*1024*1024 / 8
  • pbit: 1024*1024*1024*1024*1024 / 8
  • sec: 1
  • min: 60
  • hours: 3600
  • days: 24*3600

For sizes the base unit is byte, and for intervals seconds.

Strings

There are 4 ways to specify a string:

  • 'hello world'
  • "hello world"
  • e'hello world'
  • e"hello world"

The basic escape rules are the same for both:

  • "\n" is a newline, "\r" a carriage return, "\t" a tab stop
  • "\\" is one \, "\"" is one double quote " and "\'" a single quote '
  • escaping single/double quote is optional if the symbol is not used to terminate the string, i.e. '\"' = '"' and "\'" = "'"
  • "\xNN": NN must be hexadecimal characters, and the string is replaced with the decoded 8-bit value as a single byte
  • All other \ occurences are not removed from the string.

The e'..' and e"..." variant do not allow any occurences of the last kind to happen.

All other characters are allowed (the strings are parsed as 8-bit binary; lighttpd2 usually doesn’t care about the encoding).

Lists

Lists are ordered collections of other values:

  • [1, true, "foo"] (simple list)
  • [] (empty list)
  • [1] (list with one element)
  • [[1,2],[3,4],5] (nested lists)
  • [1,2,] (final value can have a trailing , too)
  • (1, 2, 3) (alternative brackets with more than one element)

Note that (1) is not a list; parentheses can also be used to group expressions as in 1*(2+3), and it is therefore recommend to use only [] to specify lists.

Key-Value Lists

Key-value lists associates keys to values (both can be of any type); the syntax is:

  • [ "a" => 1, "b" => 10 ]

As with normal lists the final value can have a trailing , too. You cannot mix simple values and key-value associations in one list (nesting them works).

The key => value operator is also only syntactic sugar for a [key, value] list, the above example is the same as:

  • [ [a, 1], [b, 10] ]

This especially means that key-value lists are ordered too, although they can get converted to hash tables in certain function calls internally.

Expressions

There are operators available for some value types:

  • for integers: +, -, * and /
  • for strings: + (concatenate two strings)
  • for lists: + (append lists)

Also you can cast strings and booleans to integers and any value to strings:

  • cast(int) "256" (only supports decimal representations and no suffix like above)
  • cast(int) true (true maps to 1 and false to 0)
  • cast(string) 5

Expressions can be grouped to override default association:

  • 3 * (1 + 2) vs. 3 * 1 + 2 and so on

Action Blocks

An action block consists of a list of function calls (in action context) and conditionals; it is treated like a value, i.e. can be assigned to variables and used as parameter in function calls.

Action blocks can also contain variable assignments and setup blocks/function calls, which are not part of the “action block value” itself, but are evaluated while parsing the config.

The syntax is:

{ log "hello world"; if req.path =$ ".hidden" { static; } }

The complete config is an action block too (but without the surrounding curly braces); conditionals also use action blocks for their branches.

Each action block that is not a condition branch starts a new nested variable scope;

Variables

Variable names start with a alphabetic character (a-z and A-Z) or an underscore _, and are followed by alphanumeric characters, underscores _ and dots .; keywords are not allowed as variable names.

Variables store values, and the name can be used in place of an actual value; later modifications to a variable have no influence on previous uses (i.e. are only evaluated while parsing the config).

Variables are assigned values with = (and a terminating ;)

my_types = [".txt" => "text/html"]; php = { if phys.path =$ ".php" { fastcgi "unix:/var/run/lighttpd/php.sock"; } };

By default variables assignment overwrites an existing variable (in its previous scope) or, if it doesn’t exist, creates a new one in the local scope (i.e. it will only be available in the current scope and nested descendants).

You can explicitly create a new variable in the local scope (hiding variables in parent scopes with the same name) by prefixing the assignment with local:

local wwwpath = "/var/www/example.com";

You can also create variables in the global scope by prefixing the assignment with global.
The main config already is in a nested scope (i.e. not the global scope). The global scope is not destroyed after config loading, and can be used in delayed config loading (say from SQL in the future).

If a variable name is used in a context it will always use the definition from the nearest scope.

Example

This example illustrates that variables are evaluated while parsing the config.

foo = "bar";
if req.path == "/somepath" {
	foo = "baz";
}

# at this point foo will ALWAYS contain "baz" no matter if "/somepath" was requested or not

Example

This example illustrates scoping.

foo = "bar";
php = {
	local foo = "baz";
	# in this block (and nested blocks within) foo is now "baz"
	# ...
};
# foo is now "bar" again

Special Variables

sys.* variables are readonly. right now the following sys.* variables are available:

  • sys.pid: the process id of lighttpd
  • sys.cwd: the current working directory
  • sys.env.X: the system environment variable with name X (for any X)

Function calls

There are three types of function calls:

  • actions
  • setups
  • options

Actions can only be used in action (block) context, setups only in setup (block) context, and options can be used in both.

A setup context is started with the keyword setup, and is followed by either a single setup function call or a setup block.

Setup function calls are run immediately when they occur (they are used to “setup” the webserver environment, like listening on TCP sockets, and setting default options), while action function calls are run for each request (mapping request urls to physical paths, handling requests, modifying the default options).

The actions, setups and options are provided by the modules.

Includes

Includes are similar to function calls in the syntax, but are directly handled by the config parser. They are only allowed in action context, as they insert a reference to an action block at the point they are used.

There are three types of includes:

  • include "/etc/lighttpd/vhosts/*.conf";: include files like the main config itself; the path can contain wildcards
  • include_shell "/etc/lighttpd/config_generator.sh";: runs the specified command, and parses the output of it as config file
  • include_lua "/etc/lighttpd/complex.lua": includes a Lua config file. The single action to be executed must be returned in the global actions variable (or leave it empty to do nothing). See also lua.handler.

Includes also create a new nested scope.

Debug Print

Similar to includes __print is a special function, but is available in action and setup context. It logs the string values of its parameter(s) with log level “debug”.

Conditions

Conditions (known from Lighttpd 1.x) are the equivalent to “if”s in most programming languages. There are also “else” and “elseif” equivalents.
They are created by a comparison of a condition variable and a value that is evaluated at runtime (for each request).
Conditions can be nested, and you can group them with and and or operators. and binds stronger than or, although it is preferred to group them with parentheses.

Example

if req.host == "mydomain.tld" {
	if req.path == "/" or req.path == "/index.html" {
		static;
	} else if req.path == "/upload" {
		if req.content_length > 100mbyte {
			access.deny;
		} else {
			proxy "127.0.0.1:8080";
		}
	}
}

Syntax

The basic syntax forms are:

  • if <expr> { ... }
  • if <expr> { ... } else { ... }
  • if <expr> { ... } else if <expr2> { ... }
  • if <expr> { ... } else if <expr2> { ... } ... (continue with else or else if)

A condition expression <expr> is:

  • (<expr>)
  • <expr1> and <expr2>
  • <expr1> or <expr1> (<expr1> and <expr2> or <expr3> = (<expr1> and <expr2>) or <expr3>; and has higher precedence)
  • <condvar> <op> <value> for <condvar> being a condition variable (with a type different from boolean), <op> an condition operator and <value> a string or a number.
  • <condvar> or !<condvar> for a boolean condition variable.

Condition variables

There are three categories of condvars:

  • request.xyz (can be abbreviated by req.xyz)
  • physical.xyz (can be abbreviated by phys.xyz)
  • response.xyz (can be abbreviated by resp.xyz)
variable description
request.localip ip address of the listing socket, the client connected to (filename for unix sockets)
request.localport port number of the listening socket, -1 for unix sockets
request.remoteip ip address of the client
request.remoteport port number of the client, -1 for unix sockets
request.path the path part of the requested url. not including the querystring.
request.raw_path the raw path (not urldecoded, not simplified) of the requested url, including the querystring.
request.host requested hostname
request.scheme scheme of the request. “http” or “https”
request.query the querystring of the requested url
request.method method of the request. “GET”, “POST”, “HEAD” etc.
request.length integer. length of the content for e.g. POST methods
request.header[“name”] request header name e.g. request.header[“referer”]
request.is_handled boolean condition, does request already have a handler (static, fastcgi..)
request.environment[“name”] (or short request.env[“name”]) CGI environment
physical.path physical path of the file to be served. e.g. document root + path
physical.exists boolean condition, indicates whether the requested file (normal file, directory or even a special file) exists
physical.size integer. size of the requested file. -1 if file doesn’t exist
physical.is_dir boolean condition, indicates whether the requested file is a directory
physical.is_file boolean condition, indicates whether the requested file is a normal file (e.g. no unix socket etc)
physical.docroot document root
physical.pathinfo pathinfo
response.status response status code (blocks request until response header is available)
response.header[“name”] response header (blocks request until response header is available)

Condition operators

op description op description
== compares two values on equality != negative ==
<= less than or equal < less than
>= greater than or equal > greater than
=~ regular expression match !~ negative =~
=^ prefix match !^ negative =^
=$ suffix match !$ negative =$
=/ cidr match !/ negative =/

Angel Configuration

lighttpd2 consists of two main binaries: the angel (lighttpd2) and the worker (lighttpd2-worker). The main configuration is used by the worker, and this chapter describes the configuration for the angel.
A standard distribution should install a angel config in /etc/lighttpd2/angel.conf with reasonable defaults which should work for most basic setups.

Angel concept

You can start the worker without the angel, but the angel provides some useful features:

  • The angel itself usually runs as root (needed for example to bind to privileged ports), but will spawn the worker with dropped privileges (usually a user like www-data is used). The worker doesn’t do any privilege dropping itself.
  • The angel can open/create log files for the worker with root permissions
  • The angel supports a graceful restart of the worker for config reloading: a new instance is spawned, and if it started successfully (checking config, …) it will replace the old instance. The old instance will finish the remaining requests.
    As the angel is responsible for creating the listening network sockets, it can keep them open all the time and no request is lost.
  • The angel also does a simple supervise: if the worker crashes the angel will respawn it.

Config items

The config syntax is very similar to the main configuration, although it has no action blocks, setup blocks, conditionals and scopes.

user

drops privileges for spawning the worker

user username;
username
username to drop privileges to for spawning the worker

This item can only be specified once; if it is not specified it won’t drop privileges at all, which is useful if the angel itself doesn’t run as root. It should go without saying that you should never run the worker as root.
The username is also used to find all groups the user is in.

Example

user "www-data";

group

drops privileges for spawning the worker

group groupname;
groupname
groupname to drop privileges to for spawning the worker

Specify the main group to drop privileges to; a process can have multiple groups, and the others are given by the groups the user specified by user is in.
The default is the main group of the user specified by user, or not dropping privileges at all.

Example

group "www-data";

binary

specifies path to worker binary

binary path;
path
path to the lighttpd2-worker binary

This item should only be needed if you didn’t install the binaries at all (for testing).

Example

binary "/home/source/lighttpd2/autobuild/src/main/lighttpd2-worker";

config

specifies path to main config file

config path;
path
path to the main config file

By default /etc/lighttpd2/lighttpd.conf is used.

Example

config "/etc/lighttpd2-test/lighttpd.conf";

luaconfig

specifies path to a lua config file

luaconfig path;
path
path to the lua config file

By default a normal config file is used; you must use either a normal config file or a lua config file.

Example

luaconfig "/etc/lighttpd2/lighttpd.lua";

modules_path

specifies path to directory containing modules for the worker

modules_path path;
path
path to the directory containing modules for the worker

This item should only be needed if you didn’t install the binaries at all (for testing). For autotool builds the “real” module binaries are in a .libs subdirectory.

Example

modules_path "/home/source/lighttpd2/autobuild/src/modules/.libs";

wrapper

prefix worker command with other commands

wrapper wrappers;
wrappers
path to a wrapper command and its arguments

This item appends all given strings to the command prefix list (which starts as empty list). Before spawning the worker the binary path to the worker and its arguments (config, module path) are appended.
Wrappers can be used to run the worker with valgrind, strace and similar.

Example

# in multiple lines
wrapper [ "/usr/bin/valgrind" ];
wrapper [ "--leak-check=full", "--show-reachable=yes" ]
wrapper [ "--leak-resolution=high" ];

# or as one
wrapper [ "/usr/bin/valgrind", "--leak-check=full", "--show-reachable=yes", "--leak-resolution=high" ];

env

add environment variables for the worker

env vars;
vars
list of environment variables to add for the worker to run with

Append the given list of environment variables (starts empty), which can be either strings of the form "var=xyz" or key-value pairs "var" => "xyz" (the keys must not contain any =).

Example

# helps debugging with valgrind:
env [ "G_SLICE=always-malloc", "G_DEBUG=gc-friendly,fatal_criticals" ];

copy_env

copies environment variables for the worker from current environment

copy_env varnames;
varnames
list of environment variable names to copy

Adds copies of variables from the current environment. By default all variables will be dropped.

Example

env_copy [ "PATH" ];

max_core_file_size

sets limit of core file size for the worker

max_core_file_size limit;
limit
limit in bytes

Maximum size of a core file, in bytes, that may be created by the worker. Core files are created when the worker crashes.
0 disables core files, and by default the limit is not changed.

max_open_files

sets limit of maximum open file for the worker

max_open_files limit;
limit
maximum number of open files

The worker limits the maximum number of connection based on the maximum number of open files (max connections = max open files / 4).
By default the limit is not changed.

Example

# max 4096 connections
max_open_files 16384;

allow_listen

allow worker to listen on sockets

allow_listen list;
list
list of network mask (CIDR) + optional port or unix domain socket addresses

The worker uses the angel to bind TCP/unix sockets; the angel checks whether those binds are allowed. If no allow_listen is specified, all TCP binds (IPv4 and IPv6) using port 80 or 443 are allowed.
IPv4 and IPv6 use different masks (no IPv4 to IPv6 mapping), the network length for the CIDR mask is optional (defaults to a host address), and the port is optional too (allowing both 80 and 443 if omitted).
Formats:

  • TCP on IPv4: ipv4, ipv4:port, ipv4/net, ipv4/net:port
  • TCP on IPv6: ipv6, ipv6/net, [ipv6], [ipv6/net], [ipv6]:port, [ipv6/net]:port
  • Unix domain: unix:/wildcard/path/to/*.socket

Example

Only allow TCP port 8080 for IPv4 and IPv6 and unix domain socket /run/lighttpd/internal.sock.

allow_listen [ "0.0.0.0/0:8080", "[::/0]:8080" ];
allow_listen "unix:/run/lighttpd/internal.sock";

Patterns

The lighttpd config supports “patterns” in various places (docroot, redirect, rewrite, env.set, …); and they share the following structure.

There are two kinds of “captures” available; one from the action itself (like captures from a regular expression in redirect/rewrite, or the labels from the hostname in docroot), and the captures from the previous matching regular expression conditional in the action stack. If there was no capture of the selected kind the values will be empty strings.

Syntax

A pattern is a string consisting of the following parts:

  • simple text. can contain special characters $ and % only when they are escaped with \ – remember, that the \ has to be escaped too for the config, so you’ll probably have to use \\ to escape. You are allowed to escape ? too (used for special “split” in rewrite).
  • ”%” capture references (previous matching regular expression conditional); either followed by a single digit, or a range (see below for range syntax)
  • ”$” capture references (depends on action); either followed by a single digit, or a range (see below for range syntax)
  • ”%” references to condition variables, for example: %{req.path}; the conditional can be prefixed with “enc:” (%{enc:req.path}), in which case the value will be urlencoded.

Ranges

Ranges can either be a single element [n] (n can have more than one digit), a closed range [n-m] or an open range [n-] or [-m];
the open end is always replaced with “G_MAXUINT” (a very big positive integer). ranges can be “reversed”, i.e. n > m.

There are now two different ways ranges are used:

  • ranges of regular expression captures: the captures are just inserted for all values in the range; if the range is reversed, it starts with the highest index in the range.
  • ranges of labels in a hostname: similar to the first range, but the inserted labels are separated by a “.”; the index 0 stands for “complete hostname”, and ranges including 0 are reduced to the complete hostname; the labels are numbered from top-level, and the range is interpreted reversed (just have a look at the examples, and it will be clear).

Example: simple redirect

redirect "http://%{req.host}%{req.path}?redirected=1";

Example: docroot

  • a request to “http://example.com/project/trunk” would lead to docroot “/var/www/project/trunk/htdocs”
  • a request to “http://sub.example.com/” would lead to docroot “/var/www/vhosts/com/sub.example”
if req.path =~ "^/project/([^/]*)" {
	docroot "/var/www/projects/%1/htdocs";
} else {
	docroot "/var/www/vhosts/$1/${2-}/htdocs";
}

Regular expressions

lighttpd2 uses the “Perl-compatible regular expressions” implementation from GLib, see their Regular expression syntax documentation.

The config format has different ways to provide strings (you can quote with either ' or "; the character used to quote has to be escaped with \ if used inside the string).
The simple (standard) way "text" has the following escape rules:

  • "\n" is a newline, "\r" a carriage return, "\t" a tab stop
  • "\\" is one \, "\"" is one double quote " and "\'" a single quote '
  • escaping single/double quote is optional if the symbol is not used to terminate the string, i.e. '\"' = '"' and "\'" = "'"
  • "\xNN": NN must be hexadecimal characters, and the string is replaced with the decoded 8-bit value as a single byte
  • All other \ occurences are not removed from the string.

This way is the preferred one for regular expressions; only to actually match a \ you have to do additional escaping ("\\\\"; "\x5C" = "\\" is not working), and \\ is usually not doing what you wanted (matching a digit: "\\d" = "\d"). All other escape rules are compatible with what pcre is doing.

The second way is to place an e before the string like this: e"text". It has the same rules like the normal string, but does not allow unknown escape sequences (the last rule above). To match a digit with pcre this way you’d have to write e"\\d" instead of "\d".

Fetch API

The Fetch API provides a common interface between lighttpd modules to lookup entries in a database. Both lookup key and data are simple (binary) strings.

So far only a fetch.files_static is providing a database, and only gnutls is using it to lookup SNI certificates.

Lua API

Lua can be used to generate configs (like a shortcut to include_shell) or to write actual response handlers.

Using Lua to generate configs doesn’t have any performance impact; in this case Lua is only run at startup to generate the config, and there is no Lua involved for processing requests.

As a lua_State itself is not thread-safe, you have two ways to use Lua configs:

  • include_lua and lua.plugin: using a global server lock, but with sharing the same lua_State in all workers
  • lua_handler: without locking, and every worker has its own lua_State (and they cannot share their global context).

Lua Config

This section describe how to translate concepts from the main config to Lua. You can write the whole config in Lua or only parts and include them (for example with include_lua).

Example - debug.lua

The following Lua snippet saved as “debug.lua” could for example be included with include_lua "debug.lua".

function mydebug(vr)
	local url_fields = { "raw", "raw_path", "raw_orig_path", "scheme", "authority", "path", "query", "host" }
	local phys_fields = { "path", "doc_root", "pathinfo" }
	if vr:handle_direct() then
		vr.resp.status = 200
		vr.resp.headers["Content-Type"] = "text/plain"
		vr.out:add("Hello World!\n\n")
		vr.out:add("http method: " .. vr.req.http_method .. "\n")
		vr.out:add("http version: " .. vr.req.http_version .. "\n")
		for k, v in vr.env:pairs() do
			vr.out:add("Env['" .. k .. "'] = '" .. v .. "'\n")
		end
		vr.out:add("\n")
		for k, v in pairs(url_fields) do
			vr.out:add("vr.req.uri['" .. v .. "'] = '" .. vr.req.uri[v] .. "'\n")
		end
		vr.out:add("\n")
		for k, v in pairs(phys_fields) do
			vr.out:add("vr.phys['" .. v .. "'] = '" .. vr.phys[v] .. "'\n")
		end
		vr.out:add("\n")
		for k, v in vr.req.headers:pairs() do
			vr.out:add("vr.req.headers['" .. k .. "'] = '" .. v .. "'\n")
		end
	end
end

actions = mydebug

Values

  • Boolean: Lua supports true and false directly
  • Integers: Lua has its own number type (usually a double), and doesn’t know any of the suffixes.
  • Strings: Lua supports strings directly. Check the Lua reference for the various quoting styles.
  • Lists and Key-Value-Lists: Lua has a “table” type; it can contain sequential lists and associative mappings. Use {1, 2, 3} to create simple lists, {a=1, b=2} to create unique mappings (which get converted to Key-Value-Lists) or {{"a",1},{"a",2}} to explicitly create Key-Value-Lists (where a key can be used more than once and the order matters).
    Don’t mix sequential lists and associative mappings.
    If you get a List (possible a Key-Value-List) value from lighttpd it is represented as sequential list but has a special __index meta-table method supporting strings and nil as lookup parameter, i.e. you can treat a Key-Value-List like an associative mapping in Lua (see for example the options handling in contrib/secdownload.lua).
  • Expressions and variables just are the usual Lua things; there is no direct access to the lighttpd config variables (yet).
  • Action blocks: you can make an action from a list of actions using the list action (act = action.list(act1, act2))

Function calls

Action context is given by prefixing the function name with action., and setup context by prefixing with setup.. Don’t try to call setups in request handling.

Also each Lua function can act as an action (see the debug.lua example above), taking a virtual request object as parameter.

Includes are not supported, neither is the debug __print (there are other logging methods available).

Conditions

Conditions are the ugliest part: there is no way translating native Lua if statements into the lighttpd config, so they need to be constructed manually.

Only the long names of the condition variables are available in Lua. The condition operators are all given names and appended to the condition variable, and then called with the value to compare with.

op Lua name op Lua name
== :eq != :ne
<= :le < :lt
>= :ge > :gt
=~ :match !~ :nomatch
=^ :prefix !^ :notprefix
=$ :suffix !$ :notsuffix
=/ :ip !/ :notip

Boolean condition variables are called with :is() or :isnot().

The result of such call (a “condition”) is then passed as first parameter to action.when.

Example - admin only

Translating if req.env["REMOTE_USER"] != "admin" { auth.deny; } to Lua:

actions = action.when(request.environment["REMOTE_USER"]:ne("admin"), action.auth.deny())

Example - physical files only

Translating if !phys.exists { auth.deny; } to Lua:

actions = action.when(physical.exists:isnot(), action.auth.deny())

API

This section documents the object types you need to handle requests; you will probably start from the Virtual Request object you get as parameter in your handler.

Object fields should be accessed with .field or ["field"], for example:

e = vr.env e["XXX"] = "abc"

Fields tagged with (ro) are read only; that does not mean the fields value can’t be modified, you only cannot overwrite the field with another object. Readonly string / number properties are really read only though.

Call object methods with :method(...):

vr:print("Hello World")

Note: The obj:method(par1, par2, ...) syntax is just another way to say obj["method"](obj, par1, par2, ...) (but obj is only evaluated once), so field and method names live in the same namespace.
This means that our container types cannot provide access to fields which have the same names as the methods (and the methods starting with “__” are not listed here), so you have to use explicit access methods to read generic fields in such containers (write is not a problem as we don’t allow writing methods).
All container types should provide a get and a set method to provide “clean” access to the container contents.

pairs()

Some objects may provide a :pairs() method to loop through the fields (not the methods); this works for simple things like

for k, v in vr.env:pairs() do vr:print("env['" .. k .. "'] = '" .. v .. "'") end

lua expects that the :pairs method returns a next, obj, startkey tuple and loops through the list with k = startkey; while k, v = next(obj, k) do ... end; but the next() method is supposed to use k as previous key and to return the next one.
Our next methods will keep the current position in an internal object (associated with the next function as upvalue), and will advance on every call ignoring the obj and k parameter.

Global constants

liHandlerResult enumeration values:

  • lighty.HANDLER_GO_ON
  • lighty.HANDLER_COMEBACK
  • lighty.HANDLER_WAIT_FOR_EVENT
  • lighty.HANDLER_ERROR

Global methods

  • lighty.print (and lighty.error and print): print parameters via lua “tostring” method as ERROR in global server context
  • lighty.warning: print parameters via lua “tostring” method as WARNING in global server context
  • lighty.info: print parameters via lua “tostring” method as INFO in global server context
  • lighty.debug: print parameters via lua “tostring” method as DEBUG in global server context
  • lighty.filter_in(class): creates a new action, which adds a incoming filter from class:new(vr) if called at runtime
  • lighty.filter_out(class): creates a new action, which adds a outgoing filter from class:new(vr) if called at runtime
  • lighty.md5(str): calculates the md5 checksum of the string str (returns the digest as string in hexadecimal)
  • lighty.sha1(str): calculates the sha1 checksum of the string str (returns the digest as string in hexadecimal)
  • lighty.sha256(str): calculates the sha256 checksum of the string str (returns the digest as string in hexadecimal)
  • lighty.path_simplify(str): return simplified path

Example

lighty.print("Hello World!")

Example

local MyFilterclass = { }
MyFilterClass.__index = MyFilterClass

function MyFilterClass:new(vr)
  local o = { }
  setmetatable(o, self)
  return o -- return nil if you want to skip the filter this time
end

function MyFilterClass:handle(vr, outq, inq) ... end

actions = lighty.filter_out(MyFilterClass)

Virtual Request

Fields:

  • con(ro): Connection
  • in(ro): Chunk Queue, read request post content
  • out(ro): Chunk Queue, write response content
  • env(ro): Environment, (fast)cgi environment
  • req(ro): Request, data from request header
  • resp(ro): Response, response header data
  • phys(ro): Physical, paths and filenames
  • is_handled(ro): whether vrequest is already handled
  • has_response(ro): whether the response headers (and status) is available

Methods:

  • print(...): print parameters via lua tostring method as ERROR in Virtual Request context
  • warning(...): print parameters via lua tostring method as WARNING in Virtual Request context
  • info(...): print parameters via lua tostring method as INFO in Virtual Request context
  • debug(...): print parameters via lua tostring method as DEBUG in Virtual Request context
  • handle_direct(): handle vrequest (i.e. provide headers and body); returns true if not already handled.
  • enter_action(act): push a new action on the action stack (return HANDLER_WAIT_FOR_EVENT to rerun after the pushed actions are done, HANDLER_GO_ON if you are done)
  • st, res, errno, msg = stat(filename): async stat(filename). Following results are possible
    • st is the stat result, res == HANDLER_GO_ON, if the file was found. errno and msg are NIL. In all other cases st is NIL and res != HANDLER_GO_ON.
    • res == HANDLER_WAIT_FOR_EVENT: stat() is in progress, just try again later (and return HANDLER_WAIT_FOR_EVENT in the meantime)
    • res == HANDLER_ERROR: if stat() failed, errno contains the errno and msg the error message for the errno code.
  • add_filter_in(obj): adds obj as lua incoming filter (needs to respond to obj:handle(vr, outq, inq) and optionally obj:finished()); returns a Filter object
  • add_filter_out(obj): adds obj as lua outgoing filter (needs to respond to obj:handle(vr, outq, inq) and optionally obj:finished()); returns a Filter object

Connection

  • local: address of local socket
  • remote: address of remote host

Environment

Fields are the keys in the environment, so it behaves like a lua table; if you use keys starting with “__” or keys with the name of one of the methods below, you have to use the get method to read them, for example:

x = env["set"]      -- doesn't work, returns the set method instead
x = env:get("set")  -- use this instead

x = env[y]          -- don't do this, as y may be a special key like "set"
x = env:get(y)      -- just do it the safe way if you are not sure

Methods:

  • get(k): safe way for env[k]
  • set(k, v): safe way for env[k] = v
  • unset(k): safe way for env[k] = nil
  • weak_set(k, v): don’t override old value, safe way for env[k] = env[k] or v
  • pairs(): use to loop through keys: for k, v in env:pairs() do ... end
  • clear(): remove all entries

Chunk Queue

Fields:

  • is_closed: whether the ChunkQueue is closed

Methods:

  • add(s): appends a string to the queue
  • add({filename="/..."}): appends a file to the queue (only regular files allowed)
  • reset(): removes all chunks, resets counters
  • steal_all(from): steal all chunks from another queue (useful in a filter if you decide to pass all data through it)
  • skip_all(): skips all chunks (removes all chunks but does not reset counters)

Request

Fields:

  • headers(ro): HTTP Headers
  • http_method(ro): HTTP method string (“GET”, “POST”, “HEAD”, …)
  • http_version(ro): HTTP version string (“HTTP/1.0”, “HTTP/1.1”)
  • content_length(ro): Numeric value of Content-Length header (not updated automatically if someone changes the header value), -1 if not specified
  • uri: Request URI

Request URI

Fields:

  • raw: Request uri as it was in the HTTP Request Line (or a rewrite result)
  • raw_path: not decoded path with querystring (will be the same as raw for most requests, unless someone does something like GET http://example.com/test?abc HTTP/1.1)
  • raw_orig_path: same as raw_path, but saved before any rewrite happened
  • scheme: “http” or “https”
  • authority: complete host name header (or authority in an absolute url), e.g. “user@www.example.com.:8080”
  • path: decoded and simplified path name, without authority, scheme, query-string; e.g. “/index.php”
  • host: simple hostname, without auth information, without port, without trailing dot; e.g. “www.example.com”
  • query: The querystring, e.g. “a=1&b=2”

Response

Fields:

  • headers(ro): HTTP Headers
  • status: HTTP status code

Physical

Fields:

  • path: physical path
  • doc_root: document root
  • pathinfo: pathinfo

HTTP Headers

Same restriction as Environment for fields.

Methods:

  • get(k): joins all header values for the key k with “, “ (as the rfc allows it)
  • set(k, v): removes all headers with key k and, if v is not nil, appends new “k: v” header
  • append(k, v): appends “, v” to last header value with key k if it already exists, insert(k, v) otherwise
  • insert(k, v): appends new “k: v” header to list
  • unset(k): removes all headers with key k
  • pairs(): loops through all headers. Please note that the keys are not unique!
  • list(k): loops through all headers with key k
  • clear(): remove all headers

Filter

Represents a “liFilter”.

Fields:

  • in(ro): Chunk Queue, incoming stream
  • out(ro): Chunk Queue, outgoing stream

Stat struct

Represents “struct stat”. Most fields should be self explaining (man 2 stat (debian manpage) if you don’t know them).

Fields:

  • is_file(ro): S_ISREG(mode)
  • is_dir(ro): S_ISDIR(mode)
  • is_char(ro): S_ISCHR(mode)
  • is_block(ro): S_ISBLK(mode)
  • is_socket(ro): S_ISSOCK(mode)
  • is_link(ro): S_ISLNK(mode)
  • is_fifo(ro): S_ISFIFO(mode)
  • mode(ro)
  • mtime(ro)
  • ctime(ro)
  • atime(ro)
  • uid(ro)
  • gid(ro)
  • size(ro)
  • ino(ro)
  • dev(ro)

Module index

Modules

name description
plugin_core contains the core features for generic request handling, static files, log files and buffer limits.
mod_access lets you filter clients by IP address.
mod_accesslog logs requests handled by lighttpd to files, pipes or syslog. The format of the logs can be customized using printf-style placeholders.
mod_auth requires authentication from clients using a username and password. It supports the basic (digest not yet) authentication method as well as plaintext, htpasswd and htdigest backends.
mod_balance balances between different backends.
mod_cache_disk_etag caches generated content on disk if an etag response header is set; if the backend sends an already cached etag, the backend is closed and the file is sent directly.
mod_debug offers various utilities to aid you debug a problem.
mod_deflate mod_deflate compresses content on the fly
mod_dirlist lists files inside a directory. The output can be customized in various ways from style via css to excluding certain entries.
mod_expire add "Expires" and "Cache-Control" headers to the response
mod_fastcgi connect to FastCGI backends for generating response content
mod_flv provides flash pseudo streaming
mod_fortune loads quotes (aka fortune cookies) from a file and provides actions to add a random quote as response header (X-fortune) or display it as a page.
mod_gnutls listens on separate sockets for TLS connections (https) using GnuTLS
mod_limit limits concurrent connections or requests per second.
mod_lua load lua plugins and actions
mod_core (lua) provides some useful helpers written in lua
mod_secdownload (lua) protects files with a time limited code
mod_memcached caches content on memcached servers
mod_openssl listens on separate sockets for TLS connections (https) using OpenSSL
mod_progress track connection progress (state) via a unique identifier
mod_proxy connect to HTTP backends for generating response content
mod_redirect redirect clients by sending a http status code 301 plus Location header
mod_rewrite modifies request path and querystring
mod_scgi connect to SCGI backends for generating response content
mod_status displays a page with internal statistics like amount of requests (total or per second), active connections etc.
mod_throttle limits outgoing bandwidth usage
mod_userdir allows you to have user-specific document roots being accessed through http://domain/~user/
mod_vhost offers various ways to implement virtual webhosts

Actions

name module description
access.check mod_access allows or denies access based on client IP address
access.deny mod_access denies access by returning a 403 status code
alias plugin_core sets doc-root depending on a matching prefix
auth.deny mod_auth handles request with "401 Unauthorized"
auth.htdigest mod_auth requires authentication using a htdigest file
auth.htpasswd mod_auth requires authentication using a htpasswd file
auth.plain mod_auth requires authentication using a plaintext file
auth.require_user mod_core (lua) require a specific authenticated user
balance.rr mod_balance balance between actions (list or single action) with Round-Robin
balance.sqf mod_balance balance between actions (list or single action) with SQF
cache.disk.etag mod_cache_disk_etag cache responses based on the ETag response header
core.cached_html mod_core (lua) try to find a file for the current url with ".html" suffix, if we couldn't find a static file for the url yet and the url doesn't already have the ".html" suffix.
core.wsgi mod_core (lua) Splits the url into SCRIPT_NAME (the subdirectory a web application is mounted at) and PATH_INFO (the path the web application should route)
core.xsendfile mod_core (lua) provides a simple X-Sendfile feature; send a "X-Sendfile: /path/to/file" response header from your backend
debug.profiler_dump mod_debug dumps all allocated memory to the profiler output file if profiling enabled
debug.show_connections mod_debug shows a page similar to the one from mod_status, listing all active connections
debug.show_events mod_debug shows a plain text list of all events
deflate mod_deflate waits for response headers; if the response can be compressed deflate adds a filter to compress it
dirlist mod_dirlist lists file in a directory
docroot plugin_core sets doc-root, and builds physical path for requested file
env.add plugin_core sets a connection environment variable if not already set
env.clear plugin_core removes all connection environment variables
env.remove plugin_core removes a connection environment variable
env.set plugin_core sets a connection environment variable
expire mod_expire adds an "Expires" header to the response
fastcgi mod_fastcgi connect to FastCGI backend
flv mod_flv pseudo stream the current file as flash
fortune.header mod_fortune adds a random quote as response header "X-fortune".
fortune.page mod_fortune shows a random cookie as response text
header.add plugin_core adds a new response header line
header.append plugin_core appends value to response header line
header.overwrite plugin_core overwrite response header line or add new one
header.remove plugin_core remove existing response header
index plugin_core default filenames to show in a directory
io.buffer_in plugin_core set memory limit for incoming chunkqueues (default is 256KiB)
io.buffer_out plugin_core set memory limit for outgoing chunkqueues (default is 256KiB)
io.throttle mod_throttle set the outgoing throttle limits for current connection
io.throttle_ip mod_throttle adds the current connection to an IP-based throttle pool for outgoing limits
io.throttle_pool mod_throttle adds the current connection to a throttle pool for outgoing limits
limit.con mod_limit limits the total amount of concurrent connections to the specified limit.
limit.con_ip mod_limit limits the total amount of concurrent connections per IP to the specified limit.
limit.req mod_limit limits the amount of requests per second to the specified limit.
limit.req_ip mod_limit limits the amount of requests per second per IP to the specified limit.
list plugin_core (lua) combines a list of actions into one action, only needed in lua
log plugin_core overwrite log targets for all log levels
log.write plugin_core writes a log message to the "info" log level
lua.handler mod_lua load file as lua config
map plugin_core maps the result of a pattern to a user defined action
memcached.lookup mod_memcached searches the content in a memcached database
memcached.store mod_memcached stores the generated response in a memcached database
openssl.setenv mod_openssl set SSL environment strings
pathinfo plugin_core splits physical path into existing file/directory and the remaining PATH_INFO
progress.show mod_progress returns state information about the request tracked with the ID specified by the X-Progress-ID querystring parameter
progress.track mod_progress tracks the current request if the X-Progress-ID querystring key is supplied
proxy mod_proxy connect to HTTP backend
redirect mod_redirect redirect clients
req_header.add plugin_core adds a new request header line
req_header.append plugin_core appends value to request header line
req_header.overwrite plugin_core overwrite request header line or add new one
req_header.remove plugin_core remove existing request header
respond plugin_core returns a quick response with optional body
rewrite mod_rewrite modify request path and querystring
rewrite_raw mod_rewrite modify request path and querystring, matching and writing raw path
scgi mod_scgi connect to SCGI backend
secdownload mod_secdownload (lua) protect files with a time limited code
set_status plugin_core modify HTTP status code
static plugin_core handle GET and HEAD requests with a static file from disk
static_no_fail plugin_core handle GET and HEAD requests with a static file from disk
status.info mod_status returns the status page to the client
userdir mod_userdir builds the document root by replacing certain placeholders in path with (parts of) the username.
vhost.map mod_vhost maps given hostnames to action blocks
vhost.map_regex mod_vhost maps matching hostname patterns to action blocks
when plugin_core (lua) build a conditional block (only usable in lua)

Setups

name module description
debug.show_events_after_shutdown mod_debug time in seconds after start of shutdown to log remaining active events
fetch.files_static plugin_core starts a Fetch API provider
fortune.load mod_fortune loads cookies from a file, can be called multiple times to load data from multiple files
gnutls mod_gnutls setup a TLS socket
io.timeout plugin_core sets the global I/O timeout (wait for network read and write)
listen plugin_core listen to a socket address, see above for accepted formats (default TCP port is 80)
log plugin_core sets default log targets for all log levels
log.timestamp plugin_core sets the format string to use for timestamps in the log
lua.plugin mod_lua load file as lua plugin
module_load plugin_core load the given module(s)
openssl mod_openssl setup a TLS socket
progress.ttl mod_progress Time to live in seconds for entries after a disconnect in the internal lookup table
stat_cache.ttl plugin_core set TTL for stat cache entries
tasklet_pool.threads plugin_core sets number of background threads for blocking tasks
workers plugin_core sets worker count; each worker runs in its own thread and works on the connections it gets assigned from the master worker
workers.cpu_affinity plugin_core binds worker threads to a cpu, only available on Linux systems

Options

name module description
access.log_blocked mod_access whether to log when access was denied (with log level "info")
access.redirect_url mod_access url to redirect to if access was denied (not implemented yet)
accesslog mod_accesslog defines the log target
accesslog.format mod_accesslog defines the log format
auth.debug mod_auth enable debug output
balance.debug mod_balance enable debug output
buffer_request_body plugin_core enable buffering request body on disk
debug.log_request_handling plugin_core enable debug output for request handling
deflate.debug mod_deflate enable debug output
etag.use plugin_core list of properties used to calculate etag; specify empty list to disable etags. Available: "inode", "mtime", "size"
fastcgi.log_plain_errors mod_fastcgi whether to prepend timestamp and other info to FastCGI stderr lines in the "backend" log
keepalive.requests plugin_core maximum number of requests a client is allowed to make in one connection
keepalive.timeout plugin_core how long a keep-alive connection is kept open (in seconds)
mime_types plugin_core maps file extensions to MIME types
progress.debug mod_progress enable debug output
progress.methods mod_progress Request methods to track
redirect.debug mod_redirect enable debug output
rewrite.debug mod_rewrite enable debug output
server.name plugin_core server name; is used in some places instead of the HTTP request hostname if the latter was not specified in the (HTTP/1.0) request
server.tag plugin_core used to display server name + version in different places (HTTP response header, CGI environment, mod_dirlist footer, ...)
stat.async plugin_core enables async stat() calls
static.exclude_extensions plugin_core don't deliver static files with one of the listed extensions
static.range_requests plugin_core enabled ranged requests
status.css mod_status defines the stylesheet to use. available: unset (default), "blue" or any url you wish
strict.post_content_length plugin_core require Content-Length for POST requests
vhost.debug mod_vhost enable debug output

plugin_core

plugin_core contains the core features for generic request handling, static files, log files and buffer limits.

Socket addresses

The following address formats can be used:

IPv4

Either with port 192.168.0.1:80 or without 192.168.0.1; you can either use real IPs or 0.0.0.0 to listen on all network interfaces.

IPv6

Similar to IPv4; just put the IPv6 between “[” and “]” like this: [::1]:80 (IPv6 localhost with port 80).

Please note that lighttpd always listens to IPv6 only (some platforms listen to IPv4 too on [::] by default).

Unix domain sockets

A unix domain socket needs a filename where the socket is placed; use unix:/path/to/socket as socket address.

Please don’t put unix domain sockets in /tmp. Use /var/run/lighttpd/ or something like that, where only root or selected “trusted” users can create files.

This may be not supported in all places where a socket address can be specified.

Options

debug.log_request_handling (option)

enable debug output for request handling

debug.log_request_handling value;
Default value: false

Example

debug.log_request_handling true;

static.range_requests (option)

enabled ranged requests

static.range_requests value;
Default value: true

Example

static.range_requests false;

keepalive.timeout (option)

how long a keep-alive connection is kept open (in seconds)

keepalive.timeout timeout;
Default value: 5

Example

keepalive.timeout 30;

keepalive.requests (option)

maximum number of requests a client is allowed to make in one connection

keepalive.requests requests;
Default value: 0

Example

keepalive.requests 10;

etag.use (option)

list of properties used to calculate etag; specify empty list to disable etags. Available: "inode", "mtime", "size"

etag.use properties;
Default value: ("inode", "mtime", "size")

Example

etag.use ();

stat.async (option)

enables async stat() calls

stat.async value;
Default value: true

If a filename is in lighttpd’s stat “cache”, lighttpd assumes the kernel still has the entry in memory, and stat() therefore is unlikely to block.
Otherwise it will ask a background thread to call stat(), so the main worker threads are not waiting on a slow disk (or a network filesystem), but only if stat.async is enabled.

If you know your disk is fast enough (perhaps a ramdisk?) and want to save the context switch to the background thread you can disable this.

buffer_request_body (option)

enable buffering request body on disk

buffer_request_body value;
Default value: true

Some backends like to wait for the complete response before forwarding/handling it. For this they require this option to save some memory.

strict.post_content_length (option)

require Content-Length for POST requests

strict.post_content_length value;
Default value: true

Some clients don’t send Content-Length for POST requests with empty body; they should send Content-Length: 0. When this check is enabled they’ll get a 411 Length required error.

static.exclude_extensions (option)

don't deliver static files with one of the listed extensions

static.exclude_extensions extensions;
Default value: []

Example

static.exclude_extensions [ ".php", ".htaccess", ".htpasswd" ];

server.name (option)

server name; is used in some places instead of the HTTP request hostname if the latter was not specified in the (HTTP/1.0) request

server.name hostname;
Default value: ""

Even HTTP/1.0 clients usually specify a Host: header; without Host: header you could only run one domain on one IP address.
This option is for the rare case that you want to handle clients without Host: header support in a nice way.

Example

server.name "lighttpd.net";

server.tag (option)

used to display server name + version in different places (HTTP response header, CGI environment, mod_dirlist footer, ...)

server.tag tag;
Default value: "lighttpd/2.0.0"

The default is “lighttpd/” + the current version.

mime_types (option)

maps file extensions to MIME types

mime_types mapping;
Default value: []

Default MIME type is “application/octet-stream”. The sources contain a mimetypes example config with many standard mappings.

The longest matching suffix is used (".tar.gz" always wins over ".gz"), and in case of duplicate entries the last one is used.

Example

mime_types [ ".htm" => "text/html", ".txt" => "text/plain; charset=utf-8" ];

Actions needed from lua

These action are not needed (or usable) in non-lua configs.

list (action)

(lua) combines a list of actions into one action, only needed in lua

list actions;
actions
list of actions to combine

when (action)

(lua) build a conditional block (only usable in lua)

when (condition, action1, action2);
condition
A condition; can only be constructed in lua
action1
action to run if condition was true or lua "nil"
action2
(optional) action to run if condition was false

Mapping URL paths to filenames

docroot (action)

sets doc-root, and builds physical path for requested file

docroot patterns;
patterns
One or more patterns to build docroot from

Uses patterns to build document roots (base location of files to server).
docroot uses the first pattern that results in an existing directory; otherwise it uses the last entry.
You’ll want the docroot action before alias actions!

Example

docroot ("/var/www/vhosts/$0/htdocs", "/var/www/default/htdocs");

alias (action)

sets doc-root depending on a matching prefix

alias mapping;
mapping
maps prefix to base location on disk

The prefix is removed from the URL path before it is appended to the base location.
You’ll want the docroot action before alias actions!

Patterns are supported for alias targets as in docroot. As only one pattern per prefix can be given alias does not check whether the target exists.

Trailing slashes in the prefix used to indicate “directory handling” and get ignored for matching; “directory handling” is now always on.
That means URL paths only match at separator boundaries; the prefix /a (and /a/) matches the paths /a, /a/ and /a/b, but not /ab.

Example

docroot ("/var/www/vhosts/$0/htdocs", "/var/www/default/htdocs");
alias [
	"/phpmyadmin/" => "/usr/share/phpmyadmin",
	"/pma/" => "/usr/share/phpmyadmin",
	"/.well-known/openpgpkey/" => "/var/lib/gnupg/wks/$0/",
];
alias "/favicon.ico" => "/var/www/favicon.ico";

index (action)

default filenames to show in a directory

index filenames;
filenames
filenames to look for

If the physical path is a directory search for the specified filenames; prefix a filename with ‘/’ to search in the doc-root.

It works like this:

  • if current physical path points to a regular file do nothing
  • walk through the list of filenames to look for:
    • if filename does not start with ‘/’ and the current physical path doesn’t point to a directory, ignore the entry
    • if filename does not start with ‘/’ and the url didn’t end in a ‘/’, redirect request to url with ‘/’ appended
    • if filename does not start with ‘/’ search for it in current physical path (which is a directory)
    • if filename does start with ‘/’ search for it in the doc-root

Example

setup {
	module_load "mod_dirlist";
}

# if a directory was requested, first search for some default files
index ["index.php", "index.html", "/index.php"];
# if none of them did exists show a simple directory listing
dirlist;
# ... + handle PHP and static files

pathinfo (action)

splits physical path into existing file/directory and the remaining PATH_INFO

pathinfo;

Searches for the longest prefix of the physical path name that exists, splitting only at the directory separator /; also never leaves the document root (technically speaking the filename can’t get shorter than the document root).

Example

The following example maps http://example.com/index.php/some/site to the file /var/www/index.php with PATH_INFO=/some/site (given /var/www/index.php is a normal file).

docroot "/var/www";
pathinfo;
if phys.path =$ ".php" { fastcgi "unix:/var/run/lighttpd/php.sock"; }

Example

The following example maps http://example.com/some/site to the file /var/www/index.php with PATH_INFO=/some/site (given /var/www/index.php is a normal file, and /var/www/some does not exist).

docroot "/var/www";
pathinfo;
index ("index.php");
if phys.path =$ ".php" { fastcgi "unix:/var/run/lighttpd/php.sock"; }

Generating responses

static (action)

handle GET and HEAD requests with a static file from disk

static;

This action is automatically appended to the global config (unless a lua config is specified at the command line).

Does nothing if:

  • the request is already handled
  • no physical path was set (missing docroot, alias, …)
  • the physical path points to a directory

All other problems lead to an error page, for example:

  • wrong request method (405)
  • file not found (404)
  • couldn’t open file (403)
  • filename matches static.exclude_extensions (403)

static_no_fail (action)

handle GET and HEAD requests with a static file from disk

static_no_fail;

same as static, but doesn’t return any error pages; instead request handling continues.

respond (action)

returns a quick response with optional body

respond (status, content);
status
HTTP response status code
content
(optional) pattern for response body

Generates a simple response (our favorite benchmark handler).
The body is parsed as pattern.

Example

respond 403 => "Forbidden";

Example

respond 200 => "benchmark content!";

Logging

Log levels

For standard logging (“error.log”) lighttpd knows the following levels:

  • debug
  • info
  • warning
  • error
  • abort (right before terminating the process)
  • backend (for log data from backends, like FastCGI stderr stream)

Log targets

The following log targets are known:

  • not logging: empty string
  • files: file:/var/log/error.log or just /var/log/error.log
  • stderr: stderr: or stderr
  • syslog: syslog: (not supported yet)
  • pipes: pipe:command or | command (not supported yet)

Unknown strings are mapped to stderr.

log (action)

overwrite log targets for all log levels

log map;
map
mapping log levels (or default) to log targets

Example

log [
	"error" => "/var/log/lighttpd/error.log",
	"abort" => "/var/log/lighttpd/error.log",
	"backend" => "/var/log/lighttpd/backend.log",
	default => "/var/log/lighttpd/debug.log",
];

log.write (action)

writes a log message to the "info" log level

log.write message;
message
message pattern string

Writes the specified message to the log using level info; the message is parsed as pattern.

Example

log.write "hello world";

log (setup)

sets default log targets for all log levels

log map;
map
mapping log levels (or default) to log targets

Example

setup {
	log [
		"error" => "/var/log/lighttpd/error.log",
		"abort" => "/var/log/lighttpd/error.log",
		"backend" => "/var/log/lighttpd/backend.log",
		default => "/var/log/lighttpd/debug.log",
	];
}

log.timestamp (setup)

sets the format string to use for timestamps in the log

log.timestamp format;
format
a strftime format string

See strftime for the format string syntax.

The default format string is "%d/%b/%Y %T %Z".

Connection environment

The connection environment is a set of variable with names and values (both simple strings). CGI backends will forward the environment in addition to the standard CGI environment variables.
The connection environment overwrites the standard CGI values.

env.set (action)

sets a connection environment variable

env.set (name, value);
name
the variable name to set
value
the pattern value to set

The value is parsed as pattern.

Example

env.set "INFO" => "%{req.path}";

env.add (action)

sets a connection environment variable if not already set

env.add (name, value);
name
the variable name to set
value
the pattern value to set

The value is parsed as pattern. env.add does not overwrite already existing values.

Example

env.add "INFO" => "%{req.path}";

env.remove (action)

removes a connection environment variable

env.remove name;
name
the variable name to remove

Example

env.remove "INFO";

env.clear (action)

removes all connection environment variables

env.clear;

Example

env.clear;

Response header

All header values that get set are parsed as patterns.

header.add (action)

adds a new response header line

header.add (name, value);
name
header name
value
pattern header value

The HTTP spec requires that multiple headers with the same name could be merged by joining their values with “,”.
In real life this doesn’t work always, especially not for “Cookie” headers; so this action actually adds a separate header line.

Example

header.add "Cache-Control" => "public";

header.append (action)

appends value to response header line

header.append (name, value);
name
header name
value
pattern header value

If header already exists appends new value separated by “, “; otherwise adds a new header line.

header.overwrite (action)

overwrite response header line or add new one

header.overwrite (name, value);
name
header name
value
pattern header value

If header already exists overwrites the value; otherwise a new line gets added.

header.remove (action)

remove existing response header

header.remove name;
name
header name

Example

# ... some PHP handling
# wait for response headers to be ready
if resp.status >= 0 {
	header.remove "X-Powered-By";
}

set_status (action)

modify HTTP status code

set_status;

Modifies the HTTP status code, but doesn’t handle the request in any way.
Later actions could overwrite the status, or a backend (FastCGI, proxy, …) might overwrite it if the response is parsed later.
Only works if some action actually handled the request.

Lighttpd will generate error pages (if it knows the code) if the action that handled the request didn’t generate a response body and a body is allowed.

Example

# hide all 404s at end of config by setting 403
static;
if resp.status == 404 { set_status 403; }

Request headers

All header values that get set are parsed as patterns.

req_header.add (action)

adds a new request header line

req_header.add (name, value);
name
header name
value
pattern header value

Same as header.add for request headers.

req_header.append (action)

appends value to request header line

req_header.append (name, value);
name
header name
value
pattern header value

Same as header.append for request headers.

req_header.overwrite (action)

overwrite request header line or add new one

req_header.overwrite (name, value);
name
header name
value
pattern header value

Same as header.overwrite for request headers.

req_header.remove (action)

remove existing request header

req_header.remove name;
name
header name

Same as header.remove for request headers.

Example

Remove Accept-Encoding request header to workaround the BREACH vulnerability in https.

if request.scheme == "https" {
	# create a copy of the header value
	req_header.add "HTTPS-Accept-Encoding" => '%{req.header[Accept-Encoding]}';
	req_header.remove "Accept-Encoding";
}

io.buffer_out (action)

set memory limit for outgoing chunkqueues (default is 256KiB)

io.buffer_out limit;
limit
limit in bytes (0 means unlimited)

Example

io.buffer_out 512kbyte;

io.buffer_in (action)

set memory limit for incoming chunkqueues (default is 256KiB)

io.buffer_in limit;
limit
limit in bytes (0 means unlimited)

Example

io.buffer_in 512kbyte;

map (action)

maps the result of a pattern to a user defined action

map (pattern, mapping);
pattern
the evaluation of this pattern is used as key in the mapping
mapping
maps strings (or default) to actions

The pattern is parsed as pattern. Have a look at mod_vhost for special mappings on hostnames.

Example

map "%{req.path}" => [
	"/" => {
		respond 200 => "root";
	},
	"/news" => {
		respond 200 => "news";
	},
	default => {
		respond 404;
	},
];

listen (setup)

listen to a socket address, see above for accepted formats (default TCP port is 80)

listen socket-address;
socket-address
socket address to listen to

Example

setup {
	listen "0.0.0.0";
	listen "[::]";
	listen "127.0.0.1:8080";
}

workers (setup)

sets worker count; each worker runs in its own thread and works on the connections it gets assigned from the master worker

workers count;
count
number of workers (default is 1)

Example

setup {
	workers 2;
}

workers.cpu_affinity (setup)

binds worker threads to a cpu, only available on Linux systems

workers.cpu_affinity mapping;
mapping
list of integers or a list of lists of integers

Example

workers.cpu_affinity [0, 1];

module_load (setup)

load the given module(s)

module_load names;
names
string or list of strings with the module name(s)

modules can be “loaded” more than once without error

Example

setup {
	module_load "mod_rewrite";
}

io.timeout (setup)

sets the global I/O timeout (wait for network read and write)

io.timeout timeout;
timeout
timeout value in seconds, default is 300s

stat_cache.ttl (setup)

set TTL for stat cache entries

stat_cache.ttl ttl;
ttl
time to live in seconds, default is 10s

tasklet_pool.threads (setup)

sets number of background threads for blocking tasks

tasklet_pool.threads threads;
threads
number of threads

For example the stat cache uses such background threads.

if threads = 0 the tasks are run in foreground (no background threads).
if threads < 0 all worker share a GThreadPool.
if threads > 0 each worker has its own thread pool with threads threads.

fetch.files_static (setup)

starts a Fetch API provider

fetch.files_static (name, filename-pattern);
name
name of the storage
filename-pattern
A filename pattern including exactly on *

Loads all filenames matching the wildcard pattern (which must include exactly on *) into the fetch storage.

Example

setup {
	fetch.files_static "sni" => "/etc/certs/lighttpd_sni_*.pem";
}

mod_access

mod_access lets you filter clients by IP address.

access.deny (action)

denies access by returning a 403 status code

access.deny;

access.check (action)

allows or denies access based on client IP address

access.check rules;
rules
A key value list mapping "access" and/or "deny" keys to a list of CIDR addresses or "all".

Checks the client IP address against the rules. Default is to deny all addresses. The most precise matching rule defines the result (“192.168.100.0/24” takes precedence over “192.168.0.0/16”; similar to routing tables); if the same CIDR is in both lists the second action is taken. “all” is a synonym for “0.0.0.0/0” and “::/0”, matching all IPv4 and IPv6 addresses.

Example: restrict access to local network

Limit access to clients from the local network. The deny rule isn’t strictly required, as the default is to deny anyway. The smaller CIDR strings for the local networks override the global deny rule.

setup {
	module_load "mod_access";
}

access.check (
	"allow" => ("127.0.0.0/8", "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"),
	"deny" => ("all")
);

Example: restrict access to subnet with exception

Limit access to clients from “192.168.10.0/24”, but deny access to “192.168.10.1”. As “192.168.10.1” (equivalent to “192.168.10.1/32”) is a more precise match it overwrites the allow rule for the subnet “192.168.10.0/24” containing it.

setup {
	module_load "mod_access";
}

access.check (
	"allow" => ("192.168.10.0/24"),
	"deny" => ("192.168.10.1")
);

access.redirect_url (option)

url to redirect to if access was denied (not implemented yet)

access.redirect_url url;
Default value: not set

NOT IMPLEMENTED YET

access.log_blocked (option)

whether to log when access was denied (with log level "info")

access.log_blocked url;
Default value: false

mod_accesslog

mod_accesslog logs requests handled by lighttpd to files, pipes or syslog. The format of the logs can be customized using printf-style placeholders.

accesslog.format (option)

defines the log format

accesslog.format format;
Default value: "%h %V %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\""

Some format specifiers take a mandatory key, enclosed in curly braces between percent sign and the actual specifier. CLF means “common log format”, if a value is zero, a ‘-‘ is used instead.

specifier description
%% Percent sign itself
%a Remote IP-address
%A Local IP-address
%b Size of response in bytes, excluding HTTP headers (CLF)
%B Size of response in bytes, excluding HTTP headers
%{foobar}C (not implemented yet) Contents of cookie foobar of the request
%D Time taken to serve the request in microseconds
%{foobar}e Contents of the request environment variable foobar
%f Path to physical file
%h Remote IP-address (same as %a)
%{foobar}i Contents of request header foobar
%m Request method (GET, POST, etc)
%{foobar}o Contents of response header foobar
%p Local port
%q Querystring
%r First line of request (GET /foo.html?bar HTTP/1.1)
%s Response status code
%t Time/date the request was received in standard english format
%T Time taken to serve the request in seconds
%u Authed user (from mod_auth). Same as %{REMOTE_USER}e
%U Request path (not including querystring)
%v Server name as set through the server.name option or the request hostname of server.name is not set
%V Request hostname
%X Connection status after response: “X” if aborted before completed, “+” if keepalive, “-“ if no keepalive
%I Bytes received including HTTP headers and request body
%O Bytes sent including HTTP headers and response body

Modifiers right after the percent sign like Apache provides them, are not supported. “<” or “>” are ignored, everything else results in a parse error. Specifiers supported by Apache but not lighty: %l, %n, %P

Example

accesslog.format "%h %V %u %t \"%r\" %>s %b";

accesslog (option)

defines the log target

accesslog target;
Default value: logging disabled

Enable logging by setting a log target. Supports the same log targets as log.

Example

setup {
	module_load "mod_accesslog";

	accesslog "/var/log/lighttpd/access.log";
}

mod_auth

mod_auth requires authentication from clients using a username and password. It supports the basic (digest not yet) authentication method as well as plaintext, htpasswd and htdigest backends.

IMPORTANT NOTE: You need to put the auth actions before generating content! If a content handler is already active (like php or static or dirlist), auth will be ignored!

  • Basic:
    The “basic” method transfers the username and the password in cleartext over the network (base64 encoded) and might result in security problems if not used in conjunction with an encrypted communication channel between client and server.
    It is recommend to use https in conjunction with basic authentication.
  • Digest (not supported yet):
    The “digest” method only transfers a hashed value over the network which performs a lot of work to harden the authentication process in insecure networks (like the internet).
    The “digest” method doesn’t work with the htpasswd backend, only with plaintext and htdigest.

NOTE: The digest method is broken in Internet Explorer < 7. Use basic instead if this is a problem for you. (not supported for now anyway)

auth.plain (action)

requires authentication using a plaintext file

auth.plain options;
options
A key-value table with the following entries:
method
"basic" or "digest" - for now only "basic" is supported, but you still have to specify it.
realm
the realm name to send in the "Need authentication" response to the browser; used in the hash for htdigest too.
file
the filename of the backend data
ttl
(optional) after how many seconds lighty reloads the password file if it got changed and is needed again (defaults to 10 seconds)

requires authentication using a plaintext file containing user:password pairs separated by newlines (\n).

auth.htpasswd (action)

requires authentication using a htpasswd file

auth.htpasswd options;
options
A key-value table with the following entries:
method
only "basic" is supported
realm
the realm name to send in the "Need authentication" response to the browser; used in the hash for htdigest too.
file
the filename of the backend data
ttl
(optional) after how many seconds lighty reloads the password file if it got changed and is needed again (defaults to 10 seconds)
  • requires authentication using a htpasswd file containing user:encrypted_password pairs separated by newlines (\n)
  • passwords are encrypted using crypt(3), use the htpasswd binary from apache to manage the file
    • hashes starting with “$apr1$” ARE supported (htpasswd -m)
    • hashes starting with “{SHA}” ARE supported (followed by sha1_base64(password), htpasswd -s)

auth.htdigest (action)

requires authentication using a htdigest file

auth.htdigest options;
options
A key-value table with the following entries:
method
"basic" or "digest" - for now only "basic" is supported, but you still have to specify it.
realm
the realm name to send in the "Need authentication" response to the browser; used in the hash for htdigest too.
file
the filename of the backend data
ttl
(optional) after how many seconds lighty reloads the password file if it got changed and is needed again (defaults to 10 seconds)
  • requires authentication using a htdigest file containing user:realm:hashed_password tuples separated by newlines (\n)
  • the hashes are bound to the realm, so you can’t change the realm without resetting the passwords
  • passwords are saved as (modified) md5 hashes:
    md5hex(username + ":" + realm + ":" + password)

auth.deny (action)

handles request with "401 Unauthorized"

auth.deny;

auth.debug (option)

enable debug output

auth.debug value;
Default value: false

Example

setup {
	module_load "mod_auth";
}

#/members/ is for known users only
if request.path =^ "/members/" {
	auth.plain [ "method" => "basic", "realm" => "members only", "file" => "/etc/lighttpd/users.txt"];
	if req.env["REMOTE_USER"] !~ "^(admin1|user2|user3)$" { auth.deny; }
}

Example

You can use auth.require_user from the mod_lua plugin contrib/core.lua for the REMOTE_USER check too:

setup {
	module_load ("mod_auth", "mod_lua");
	lua.plugin "contrib/core.lua";
}

#/members/ is for known users only
if request.path =^ "/members/" {
	auth.plain [ "method" => "basic", "realm" => "members only", "file" => "/etc/lighttpd/users.txt"];
	auth.require_user ("admin1", "user2", "user3");
}

mod_balance

mod_balance balances between different backends.

Using an action from mod_balance also activates a backlog: lighttpd2 will then put requests in a backlog if no backend is available and try again later.

Be careful: the referenced actions may get executed more than once (until one is successful!), so don’t loop rewrites in them or something similar.

balance.rr (action)

balance between actions (list or single action) with Round-Robin

balance.rr actions;
actions
the actions to balance between

Round-Robin (rr) the requests are distributed equally over all backends.

Example

balance.rr { fastcgi "127.0.0.1:9090"; };

Example

balance.rr ({ fastcgi "127.0.0.1:9090"; }, { fastcgi "127.0.0.1:9091"; });

balance.sqf (action)

balance between actions (list or single action) with SQF

balance.sqf actions;
actions
the actions to balance between

Shortest-Queue-First (sqf) is similar to Round-Robin and prefers the backend with the shortest wait-queue.

Example

balance.sqf { fastcgi "127.0.0.1:9090"; };

balance.debug (option)

enable debug output

balance.debug value;
Default value: false

mod_cache_disk_etag

mod_cache_disk_etag caches generated content on disk if an etag response header is set; if the backend sends an already cached etag, the backend is closed and the file is sent directly.

Please note: This will not skip the backend, as it will need at least the response headers.

Hint:
Use a cron-job like the following to remove old cached data, e.g. in crontab daily:

find /var/cache/lighttpd/cache_etag/ -type f -mtime +2 -exec rm -r {} \;

Hint:
Have a look at mod_deflate to see this module in action.

cache.disk.etag (action)

cache responses based on the ETag response header

cache.disk.etag path;
path
directory to store the cached results in

This blocks action progress until the response headers are done (i.e. there has to be a content generator before it (like fastcgi/dirlist/static file). You could insert it multiple times of course (e.g. before and after deflate).

Example

setup {
	module_load "mod_cache_disk_etag";
}

cache.disk.etag "/var/lib/lighttpd/cache_etag"

mod_debug

mod_debug offers various utilities to aid you debug a problem.

debug.show_connections (action)

shows a page similar to the one from mod_status, listing all active connections

debug.show_connections;

By specifying one or more “connection ids” via querystring (parameter “con”), one can request additional debug output for specific connections.

Example

if req.path == "/debug/connections" { debug.show_connections; }

debug.profiler_dump (action)

dumps all allocated memory to the profiler output file if profiling enabled

debug.profiler_dump;

lighttpd2 needs to be compiled with profiler support, and profiling has to be enabled by setting the environment variable LIGHTY_PROFILE_MEM to the path of a target log file.

debug.show_events (action)

shows a plain text list of all events

debug.show_events;

this is a very low level debug tool for developers.

Example

if req.path == "/debug/events" { debug.show_events; }

debug.show_events_after_shutdown (setup)

time in seconds after start of shutdown to log remaining active events

debug.show_events_after_shutdown (timeout, repeat);
timeout
timeout after which to display events (default: disabled)
repeat
timeout after which to repeat displaying events (default: disabled)

this is a very low level debug tool for developers; it shows which event listeners keep lighttpd2 alive when it should stop.

Example

setup { debug.show_events_after_shutdown 5; }
setup { debug.show_events_after_shutdown 5, 15; }

mod_deflate

mod_deflate mod_deflate compresses content on the fly

deflate (action)

waits for response headers; if the response can be compressed deflate adds a filter to compress it

deflate options;
options
A key-value table with the following entries:
encodings
supported method, depends on whats compiled in (default: "deflate,gzip,bzip2")
blocksize
blocksize is the number of kilobytes to compress at one time, it allows the webserver to do other work (network I/O) in between compression (default: 4096)
output-buffer
output-buffer is a per connection buffer for compressed output, it can help decrease the response size (fewer chunks to encode). If it is set to zero a shared buffer will be used. (default: 4096)
compression-level
0-9: lower numbers means faster compression but results in larger files/output, high numbers might take longer on compression but results in smaller files/output (depending on files ability to be compressed), this option is used for all selected encoding variants (default: 1)

Example

deflate [ "encodings" => "deflate,gzip,bzip2", "blocksize" => 4096, "output-buffer" => 4096, "compression-level" => 1 ];

Example

deflate [ "compression-level" => 6 ];

deflate.debug (option)

enable debug output

deflate.debug value;
Default value: false

Notes

Important: As deflate; waits for the response headers, you must handle the request before it (see below how to check whether the request is handled). If the request is not handled, you will get a “500 - Internal error” and a message in the error.log.

Does not compress:

  • response status: 100, 101, 204, 205, 206, 304
  • already compressed content
  • if more than one etag response header is sent
  • if no common encoding is found

Supported encodings

  • gzip, deflate (needs zlib)
  • bzip2 (needs bzip2)

deflate also:

  • modifies etag response header (if present)
  • adds “Vary: Accept-Encoding” response header
  • resets Content-Length header

Simple config

setup {
	module_load "mod_deflate";
}

# define our own "action"
do_deflate = {
	# make sure static files get handled before we try deflate
	static;
	# we can only wait for response headers if we already have a request handler! (static only handles GET/HEAD requests)
	if request.is_handled {
		# limit content-types we want to compress -> see mimetypes
		if response.header["Content-Type"] =~ "^(.*/javascript|text/.*)(;|$)" {
			deflate;
		}
	}
};

# now add do_deflate; in places where you want deflate. for example at the end of your config:

do_deflate;

Extended config (makes use of mod_cache_disk_etag)

setup {
	module_load ("mod_deflate", "mod_cache_disk_etag");
}

# define our own "action"
do_deflate = {
	# make sure static files get handled before we try deflate
	static;
	# we can only wait for response headers if we already have a request handler! (static only handles GET/HEAD requests)
	if request.is_handled {
		# limit content-types we want to compress -> see mimetypes
		if response.header["Content-Type"] =~ "^(.*/javascript|text/.*)(;|$)" {
			deflate;
			# the following block needs mod_cache_disk_etag (and is optional)
			# only cache compressed result of static files
			if physical.is_file {
				cache.disk.etag "/var/cache/lighttpd/cache_etag";
			}
		}
	}
};

# now add do_deflate; in places where you want deflate. for example at the end of your config:

do_deflate;

mod_dirlist

mod_dirlist lists files inside a directory. The output can be customized in various ways from style via css to excluding certain entries.

dirlist (action)

lists file in a directory

dirlist options;
options
A key-value table with the following entries:
css
string: url to external stylesheet (default: inline internal css)
hide-dotfiles
boolean: hide entries beginning with a dot (default: true)
hide-tildefiles
boolean: hide entries ending with a tilde (~), often used for backups (default: true)
hide-directories
boolean: hide directories from the directory listing (default: false)
include-header
boolean: include HEADER.txt above the directory listing (default: false)
hide-header
boolean: hide HEADER.txt from the directory listing (default: false)
encode-header
boolean: html-encode HEADER.txt (if included), set to false if it contains real HTML (default: true)
include-readme
boolean: include README.txt below the directory listing (default: true)
hide-readme
boolean: hide README.txt from the directory listing (default: false)
encode-readme
boolean: html-encode README.txt (if included), set to false if it contains real HTML (default: true)
exclude-suffix
list of strings: hide entries that end with one of the strings supplied (default: empty list)
exclude-prefix
list of strings: hide entries that begin with one of the strings supplied (default: empty list)
debug
boolean: output debug information to log (default: false)
content-type
string: content-type to return in HTTP response headers (default: "text/html; charset=utf-8")

Example

shows a directory listing including the content of HEADER.txt above the list and hiding itself from it; also hides all files ending in “.bak”

setup {
	module_load ("mod_dirlist");
}

if req.path =^ "/files/" {
	dirlist ("include-header" => true, "hide-header" => true, "hide->suffix" => (".bak"));
}

mod_expire

mod_expire add "Expires" and "Cache-Control" headers to the response

This allows you to control client-side caching of responses based on a simple rule/formula.
If a response is cached using an “Expires” and “Cache-Control” header, then the client will not issue a new request for it until the date specified by the header is reached.

Adding expire headers to static content like css, javascript, images or similar, can greatly reduce the amount of requests you get and therefor save resources.
Use “modification” as <base> if your content changes in specific intervals like every 15 minutes.

expire (action)

adds an "Expires" header to the response

expire rule;
rule
the rule to calculate the "Expires" header value with

The rule/formula used here, complies with the one mod_expire for Apache uses:

<base> [plus] (<num> <type>)+
  • <base> is one of “access”, “now” or “modification”; “now” being equivalent to “access”.
  • plus” is optional and does nothing.
  • <num> is any positive integer.
  • <type> is one of “seconds”, “minutes”, “hours”, “days”, “weeks”, “months” or “years”.

The trailing “s” in <type> is optional.

If you use “modification” as <base> and the file does not exist or cannot be accessed, mod_expire will do nothing and request processing will go on.

The expire action will overwrite any existing “Expires” header.
It will append the max-age value to any existing “Cache-Control” header.

Example

Cache image, css, txt and js files for 1 week.

setup {
	module_load "mod_expire";
}

if req.path =~ "\.(jpe?g|png|gif|txt|css|js)$" {
	expire "access plus 1 week";
}

mod_fastcgi

mod_fastcgi connect to FastCGI backends for generating response content

fastcgi (action)

connect to FastCGI backend

fastcgi socket;
socket
socket to connect to, either "ip:port" or "unix:/path"

Don’t confuse FastCGI with CGI! Not all CGI backends can be used as FastCGI backends (but you can use fcgi-cgi to run CGI backends with lighttpd2).

Example

fastcgi "127.0.0.1:9090"

Example

Start php for example with spawn-fcgi: spawn-fcgi -n -s /var/run/lighttpd2/php.sock -- /usr/bin/php5-cgi

setup {
	module_load "mod_fastcgi";
}

if phys.path =$ ".php" and phys.is_file {
	fastcgi "unix:/var/run/lighttpd2/php.sock";
}

fastcgi.log_plain_errors (option)

whether to prepend timestamp and other info to FastCGI stderr lines in the "backend" log

fastcgi.log_plain_errors value;
Default value: false

mod_flv

mod_flv provides flash pseudo streaming

flv (action)

pseudo stream the current file as flash

flv;

Lets flash players seek with the “start” query string parameter to an (byte) offset in the file, and prepends a simple flash header before streaming the file from that offset.

Uses “video/x-flv” as hard-coded content type.

Example

if phys.path =$ ".flv" {
	flv;
}

Example

Use caching and bandwidth throttling to save traffic. Use a small burst threshold to prevent the player from buffering at the beginning.

This config will make browsers cache videos for 1 month and limit bandwidth to 150 kilobyte/s after 500 kilobytes.

if phys.path =$ ".flv" {
	expire "access 1 month";
	io.throttle 500kbyte => 150kbyte;
	flv;
}

mod_fortune

mod_fortune loads quotes (aka fortune cookies) from a file and provides actions to add a random quote as response header (X-fortune) or display it as a page.

fortune.load (setup)

loads cookies from a file, can be called multiple times to load data from multiple files

fortune.load filename;
filename
the file to load the cookies from

fortune.header (action)

adds a random quote as response header "X-fortune".

fortune.header;

fortune.page (action)

shows a random cookie as response text

fortune.page;

Example

setup {
	module_load "mod_fortune";
	fortune.load "/var/www/fortunes.txt";
}

if req.path == "/fortune" {
	fortune.page;
} else {
	fortune.header;
}

mod_gnutls

mod_gnutls listens on separate sockets for TLS connections (https) using GnuTLS

gnutls (setup)

setup a TLS socket

gnutls options;
options
A key-value table with the following entries:
listen
(mandatory) the socket address to listen on (same as "listen":plugin_core.html#plugin_core__setup_listen), can be specified more than once to setup multiple sockets with the same options
pemfile
(mandatory) file containing the private key, certificate, intermediate certificates (the root certificate is usually not included) and an OCSP response; alternatively it can be a key-value list with a "key" and a "cert" entry, and optionally a "ocsp" entry.
pin
the PIN (or password) to use when using PKCS #11 modules or encrypted keys. The pin is kept in memory.
priority
GnuTLS priority string, specifying ciphers and other GnuTLS options (default: "NORMAL")
dh-params
filename with generated dh-params (default: fixed 4096-bit parameters)
protect-against-beast
whether to force RC4 on TLS1.0 and SSL3.0 connections by appending ":-CIPHER-ALL:+ARCFOUR-128" to the priority string (default: false)
session-db-size
size of session database, 0 to disable session support (TLS ticket support is always enabled if GnuTLS supports it)
sni-backend
"fetch" backend name to search certificates in with the SNI servername as key (only available if SNI in lighttpd2 was enabled)
sni-fallback-pemfile
certificate to use if request contained SNI servername, but the sni-backend didn't find anything; if request didn't contain SNI the standard "pemfile"(s) are used; similarly with "pemfile" it can also be a key-value list with a "key" and a "cert" entry.

Simple TLS on IPv4 and IPv6

setup {
	module_load "mod_gnutls";
	gnutls (
		"priority" => "PFS:-3DES-CBC:-ARCFOUR-128:-VERS-SSL3.0:-SHA1:+SHA1:+RSA:%SERVER_PRECEDENCE",
		"listen" => "0.0.0.0:443",
		"listen" => "[::]:443",
		"pemfile" => "/etc/certs/lighttpd.pem"
	);
}

TLS with simple SNI

setup {
	module_load "mod_gnutls";
	gnutls (
		"priority" => "PFS:-3DES-CBC:-ARCFOUR-128:-VERS-SSL3.0:-SHA1:+SHA1:+RSA:%SERVER_PRECEDENCE",
		"listen" => "0.0.0.0:443",
		"listen" => "[::]:443",
		"pemfile" => "/etc/certs/www.example.com.pem"
		"pemfile" => "/etc/certs/mail.example.com.pem"
	);
}

TLS with SNI from fetch backend

For a SNI hostname example.com lighttpd2 will try to find the private key and certificate(s) in /etc/certs/lighttpd_sni_example.com.pem.

setup {
	fetch.files_static "sni" => "/etc/certs/lighttpd_sni_*.pem";

	module_load "mod_gnutls";
	gnutls (
		"priority" => "PFS:-3DES-CBC:-ARCFOUR-128:-VERS-SSL3.0:-SHA1:+SHA1:+RSA:%SERVER_PRECEDENCE",
		"sni-backend" => "sni",
		"listen" => "0.0.0.0:443",
		"listen" => "[::]:443",
		"pemfile" => "/etc/certs/lighttpd.pem"
	);
}

Simple TLS on IPv4 and IPv6 with separate files for key and certificate and encrypted key

setup {
	module_load "mod_gnutls";
	gnutls (
		"priority" => "PFS:-3DES-CBC:-ARCFOUR-128:-VERS-SSL3.0:-SHA1:+SHA1:+RSA:%SERVER_PRECEDENCE",
		"listen" => "0.0.0.0:443",
		"listen" => "[::]:443",
		"pin" => "passwordForEncryptedKey",
		"pemfile" => (
			"key" => "/etc/certs/lighttpd_key.pem",
			"cert" => "/etc/certs/lighttpd_cert.pem"
		)
	);
}

Simple TLS on IPv4 and IPv6 with SoftHSM

setup {
	module_load "mod_gnutls";
	gnutls (
		"priority" => "PFS:-3DES-CBC:-ARCFOUR-128:-VERS-SSL3.0:-SHA1:+SHA1:+RSA:%SERVER_PRECEDENCE",
		"listen" => "0.0.0.0:443",
		"listen" => "[::]:443",
		"pin" => "SoftHSM-pin",
		"pemfile" => (
			"key" => "pkcs11:model=SoftHSM;manufacturer=SoftHSM;serial=1;token=master-key;id=%ac%d5%52%69%16%09%2c%0c%9c%b0%ec%6c%3d%3b%c6%4d%55%4c%40%49;object=my-key;object-type=private",
			"cert" => "pkcs11:model=SoftHSM;manufacturer=SoftHSM;serial=1;token=master-key;id=%ac%d5%52%69%16%09%2c%0c%9c%b0%ec%6c%3d%3b%c6%4d%55%4c%40%49;object=my-key;object-type=cert"
		)
	);
}

Simple TLS on IPv4 and IPv6 with OCSP stapling

setup {
	module_load "mod_gnutls";
	gnutls (
		"priority" => "PFS:-3DES-CBC:-ARCFOUR-128:-VERS-SSL3.0:-SHA1:+SHA1:+RSA:%SERVER_PRECEDENCE",
		"listen" => "0.0.0.0:443",
		"listen" => "[::]:443",
		"pemfile" => (
			"key" => "/etc/certs/lighttpd.pem",
			"cert" => "/etc/certs/lighttpd.pem",
			"ocsp" => "/etc/certs/lighttpd-ocsp.der",
		)
	);
}

Server Name Indication (SNI)

TLS SNI means that a client can send the hostname of the server it tries to connect to unencrypted in the TLS handshake.
If you want to host multiple hostnames on the same IP address (quite common) there are some options how to do it (they can be combined):

  • Use a wildcard as “CommonName” (CN) in the certificate like *.example.com (although this usually doesn’t match example.com)
  • Use “Subject Alternative Names” in the certificate
  • Provide different certificates based on the SNI hostname in the TLS handshake.

Clients supporting SNI usually support the other options too, but not all clients support SNI.

GnuTLS has some basic SNI support built in; if you specify multiple pemfile s in the options, it will pick the first with a certificate that matches the SNI hostname.

lighttpd2 has an optional extended SNI support (which has to be enabled at compile time, and is required for the sni-* options to be available). It is designed to load certificates asynchronously from backends (files, SQL, …) on demand, using the fetch API.
In this case lighttpd2 will fetch the certificate based on the SNI hostname from the given sni-backend before GnuTLS is started for a connection.
If a SNI hostname was given by the client, but no certificate was found in the backend, it will use sni-fallback-pemfile (if set) instead of pemfile.

Combining sni-backend, sni-fallback-pemfile and multiple pemfile s won’t work - it will only use the first configured pemfile (if no SNI hostname was given by the client, otherwise sni-* certificates are used).

Also note that lighttpd2 does NOT verify whether the SNI hostname matches the hostname in the HTTP request. SNI is only used by the client to tell the server for which hostname it should send the certificate (i.e. what the client is using to verify it).

GnuTLS priority string

The GnuTLS priority string configures which ciphers and protocol versions are available, and also a small set of options (workarounds to activate and so on).

Example: "SECURE:-VERS-SSL3.0:-SHA1:+SHA1:-RSA:+RSA:%SERVER_PRECEDENCE"

  • SECURE: starts with SECURE level: only secure ciphers and secure curves
  • -VERS-SSL3.0: disables SSL3.0 support (all clients should support at least TLS1.0 now)
  • -SHA1:SHA1 puts SHA1 back at the list for MAC algorithms (preferring the other SHA variants)
  • -RSA:+RSA: prefer (RSA) DHE/ECDHE key exchanges over simple RSA
  • %SERVER_PRECEDENCE a flag telling GnuTLS to use its own order of preference instead of the order provided by the client.

See also:

OCSP stapling

OCSP stapling is used to assure a client the certificate is still valid (i.e. not revoked); you can put an OCSP response into the certificate file in an “OCSP RESPONSE”-PEM block (there is probably no standard for this, just base64-encode the DER-response you have), or specify the (DER or PEM formatted) OCSP response as separate file using “ocsp” in a “pemfile” block.

Server Name Indication (SNI) should work fine, as an OCSP response is only used if it matches the certificate in use for the connection.

The fetch backends do support OCSP stapling if the OCSP response is appended as PEM block.

Lighttpd does NOT automatically reload OCSP responses; you have to restart to load new OCSP responses (a cron job is probably the right way to do it).

If you have your certificate and the issuer-certificate (the one that signed yours) in separate files you can request an OCSP response like that (using the GnuTLS “ocsptool”):

ocsptool --ask --load-issuer issuer.pem --load-cert cert.pem --outfile ocsp.der

Converting into PEM format can be done like this:

(echo "-----BEGIN OCSP RESPONSE-----"; base64 --wrap=64 ocsp.der; echo "-----END OCSP RESPONSE-----") > ocsp.pem

If you have trouble identifying which certificates you need, here the more detailed explanation:

You usually have a list of certificates in the PEM file you pass to lighttpd. The first certificate usually has a “Subject” pointing to your server name (CN), like: “Subject: CN=lighttpd.net”. It also has a “Issuer” attribute (like “C=US, O=Let’s Encrypt, CN=Let’s Encrypt Authority X3”). The issuer certificate needs a “Subject” matching that “Issuer”, and should be the second certificate in the PEM file (unless it already is the root CA, in which case it is usually omitted).

ocsptool will always use the first certificate in a file and ignore the others, so you can use the normal PEM file you pass to lighttpd as argument after --load-cert, but you need to extract the issuer certificate if you don’t have it in a separate file. The following awk script extracts the second PEM block from a file:

awk '
	BEGIN { block = 0 }
	/^-----BEGIN / { ++block; }
	{ if (block > 1) print; }
' "certs.pem" > "issuer.pem"

protect-against-beast

BEAST is considered mitigated on clients by many now, so this workaround is no longer recommended and disabled by default.

It enabled it will enforce the usage of RC4 on TLS1.0 and before (TLS1.1 and TLS1.2 are not vulnerable); but RC4 has issues on its own.

DH parameters

The DHE_RSA key exchange requires parameters (similar to the curves used for ECDHE_RSA); the parameters specify a prime p and a group generator g of the multiplicative group of integers modulo p (i.e. for all x in 1..p-1 exists an e with g^e = x); sometimes g might only create a sufficiently large subgroup (for example of size (p-1)/2).

The security of the DH key exchange depends (among other things) on the bit length of p; therefore lighttpd includes default 4096-bit parameters (provided by the GnuTLS certtool with certtool --get-dh-params --bits=4096 in version 3.0.22), and these should be safe to use (key lengths > 1024 are not supported by some older clients; if you need to support those you either have to disable DH key exchange or specify 1024-bit parameters).

You can use either GnuTLS or openssl to generate your own parameters:

  • certtool --generate-dh-params --bits=2048
  • openssl dhparam -dsaparam 2048
  • openssl dhparam -2 2048
  • openssl dhparam -5 2048

The GnuTLS certtool only generates “DSA” parameters (any prime with a large generator), while openssl can generate “DH” parameters with a generator of 2 or 5 combined with a Sophie Germain prime (p-1)/2 (i.e. both p and (p-1)/2 are primes) or “DSA” parameters using the -dsaparam option.

“DH” parameters take a lot more time to generate, but you can reuse keys for those (although it is not recommended, search for SSL_OP_SINGLE_DH_USE in the openssl manual on SSL_CTX_set_tmp_dh_callback). Keys for “DSA” parameters should not be reused, i.e. one should generate a new private key for each connection, which is the case in mod_gnutls. The default parameters provided by lighttpd are “DH” parameters.

See also:

mod_limit

mod_limit limits concurrent connections or requests per second.

Both limits can be “in total” or per IP.

limit.con (action)

limits the total amount of concurrent connections to the specified limit.

limit.con (limit, action);
limit
the maximum number of concurrent connections
action
(optional) an action to be executed when the limit is reached

If no action is defined a 503 error page will be returned. If it is specified there is no other special handling apart from running the specified action when the limit is reached.

Example

limit.con 10;

limit.con_ip (action)

limits the total amount of concurrent connections per IP to the specified limit.

limit.con_ip (limit, action);
limit
the maximum number of concurrent connections per IP
action
(optional) an action to be executed when the limit is reached

If no action is defined a 503 error page will be returned. If it is specified there is no other special handling apart from running the specified action when the limit is reached.

Example

limit.con_ip 2;

limit.req (action)

limits the amount of requests per second to the specified limit.

limit.req (limit, action);
limit
the maximum number of requests per second
action
(optional) an action to be executed when the limit is reached

If no action is defined a 503 error page will be returned. If it is specified there is no other special handling apart from running the specified action when the limit is reached.

Example

limit.req 100;

limit.req_ip (action)

limits the amount of requests per second per IP to the specified limit.

limit.req_ip (limit, action);
limit
the maximum number of requests per second per IP
action
(optional) an action to be executed when the limit is reached

If no action is defined a 503 error page will be returned. If it is specified there is no other special handling apart from running the specified action when the limit is reached.

Example

limit.req_ip 100;

Limiting concurrent connections

This config snippet will allow only 10 active downloads overall and 1 per IP. If the limit is exceeded, either because more than 10 people try to access this resource or one person tries a second time while having one download running already, they will be redirected to /connection_limit_reached.html.

setup {
	module_load ("mod_limit","mod_redirect");
}

limit_reached = {
	redirect "/connection_limit_reached.html";
};

if req.path =^ "/downloads/" {
	limit.con 10 => limit_reached;
	limit.con_ip 1 => limit_reached;
}

Limiting requests per second

This config snippet will write a message to the log containing the client IP address if the /login page is hit more than once in a second. It will however also not do anything else. The client will be able to use the /login page as often as he wants.

setup {
	module_load "mod_limit";
}

if req.path == "/login" {
	limit.req_ip 1 => { log.write "Possible bruteforce from %{req.remoteip}"; };
}

mod_lua

mod_lua load lua plugins and actions

Also see Lua API.

lua.plugin (setup)

load file as lua plugin

lua.plugin (filename, options, lua-args);
filename
the file containing the lua code
options
A key-value table with options; no options available yet
lua-args
arguments forwarded to the lua plugin

A lua plugin can register setup and action callbacks (like any C module) by creating a setups / actions table in the global lua namespace.

The filename and the third argument lua-args are available as parameters in ... in the lua script.

Example

setup {
	module_load "mod_lua";
	lua.plugin "secdownload.lua";
}

Example plugin

(see contrib/core.lua for a real example)

local filename, args = ...

-- args are from the lua.plugin line

local function simple(actionarg)
	-- actionarg is the parameter from the 'single "/xxx";' action line

	-- create an action:
	return action.respond()
end

actions = {
	["simple"] = simple,
}

lua.handler (action)

load file as lua config

lua.handler (filename, options, lua-args);
filename
the file containing the lua code
options
A key-value table with the following entries:
ttl
time in seconds after which the file is checked for modifications and reloaded. 0 disables reloading (default 0)
lua-args
arguments forwarded to the lua plugin

lua.handler is basically the same as include_lua with the following differences:

  • each worker loads the lua file itself
  • it isn’t loaded before it is used, so you won’t see errors in the script at load time
  • it cannot call setup functions
  • it supports arguments to the script (local filename, args = ...)
  • doesn’t lock the global lua lock, so it performs better when you use multiple workers

See contrib/core.lua for how we load some external actions like contrib/core__xsendfile.lua.

Example

setup {
	module_load "mod_lua";
	lua.plugin "secdownload.lua";
}

if req.path =^ "/app" {
	lua.handler "/etc/lighttpd/pathrewrite.lua", [ "ttl" => 300 ], "/app";
}

mod_core (lua)

mod_core.lua provides some useful helpers written in lua

Install

By default distributions (and make install) should provide the necessary files; but you can always find them in the contrib folder:

  • core.lua
  • core__cached_html.lua
  • core__xsendfile.lua

That way you can modify them for your own needs if you have to (although it is recommended to change the names of the files and the actions, so you don’t get conflicts).

lighttpd should search for core.lua in the correct (install) locations, so you don’t need the absolute path here.

core.wsgi (action)

Splits the url into SCRIPT_NAME (the subdirectory a web application is mounted at) and PATH_INFO (the path the web application should route)

core.wsgi (prefix, action);
prefix
URL prefix ("subdirectory") the web application is mounted at
action
action block connecting to the wsgi backend

See Howto WSGI for an example.

WSGI applications expect the url to be split into SCRIPT_NAME and PATH_INFO (CGI environment variables); SCRIPT_NAME is their “application root”, and PATH_INFO the requested resource in the application.
By default, lighttpd uses an empty PATH_INFO (unless you used the “pathinfo;” action, but this doesn’t help as we’re not dealing with static files here).

Important: WSGI is an “extension” of CGI; it doesn’t specify a transport protocol, you can use it with plain CGI, FastCGI or SCGI (or anything else that supports the basic CGI protocol)

Example

Example: Trac in /trac, listening via FastCGI on unix:/var/run/lighttpd/trac.socket.

setup {
	module_load ("mod_lua", "mod_fastcgi");
	lua.plugin "core.lua";
}
core.wsgi ( "/trac", { fastcgi "unix:/var/run/lighttpd/trac.socket"; } );

core.cached_html (action)

try to find a file for the current url with ".html" suffix, if we couldn't find a static file for the url yet and the url doesn't already have the ".html" suffix.

core.cached_html;

Example

setup {
	module_load "mod_lua";
	lua.plugin "core.lua";
}
docroot "/some/dynamic/app/public";
core.cached_html;
if physical.is_file {
	header.add ("X-cleanurl", "hit");
} else {
	header.add ("X-cleanurl", "miss");
	fastcgi "/var/run/lighttpd/dynamic-app.sock";
}

core.xsendfile (action)

provides a simple X-Sendfile feature; send a "X-Sendfile: /path/to/file" response header from your backend

core.xsendfile docroot;
docroot
(optional) doc-root the files has be in

Example

setup {
	module_load ("mod_lua", "mod_fastcgi");
	lua.plugin "core.lua";
}
fastcgi "/var/run/lighttpd/dynamic-app.sock";
core.xsendfile "/some/dynamic/app/";

auth.require_user (action)

require a specific authenticated user

auth.require_user userlist;
userlist
list of usernames to allow

This helper constructs a regular expression to match against request.environment[“REMOTE_USER”], so the example below is the same as:

auth.plain [ "method" => "basic", "realm" => "test", "file" => "/etc/lighttpd2/test.plain" ]; if req.env["REMOTE_USER"] !~ "^(foo1|foo2)$" { auth.deny; }

This action uses lua only to create the action, no lua is executed to handle requests in this case.

Be careful: the empty username matches unauthenticated users.

Example

setup {
	module_load "mod_lua";
	lua.plugin "core.lua";
}
auth.plain [ "method" => "basic", "realm" => "test", "file" => "/etc/lighttpd2/test.plain" ];
auth.require_user ("foo1", "foo2");

mod_secdownload (lua)

mod_secdownload.lua protects files with a time limited code

Install

By default distributions (and make install) should provide the necessary files; but you can always find them in the contrib folder:

  • secdownload.lua
  • secdownload__secdownload.lua

That way you can modify them for your own needs if you have to (although it is recommended to change the names of the files and the actions, so you don’t get conflicts).

secdownload (action)

protect files with a time limited code

secdownload options;
options
A key-value table with the following entries:
prefix
URL path prefix to protect; default "/"
document-root
where the secret files are stored on disk
secret
shared secret used to create and verify urls.
timeout
how long a generated url is valid in seconds (maximum allowed time difference); default is 60

The prefix is not used to build the filename; include it manually in the document-root (works like alias "/prefix" => "/docroot", see alias).
secdownload doesn’t actually handle the (valid) request, it just provides the mapping to a filename (and rejects invalid requests).

Example

setup {
	module_load "mod_lua";
	lua.plugin "secdownload.lua";
}
secdownload [ "prefix" => "/sec/", "document-root" => "/secret/path", "secret" => "abc", "timeout" => 600 ];

Generating URLs

To generate URLs that are valid for secdownload you need the same secret.
The url takes the form prefix + md5hex(secret + filepath + timestamp) + '/' + timestamp + filepath; timestamp is the Unix time formatted as hexadecimal number.

For example with PHP:

$secret = "abc";
$uri_prefix = "/sec/";

# filename; please note file name starts with "/"
$f = "/secret-file.txt";

# current timestamp
$t = time();

$t_hex = sprintf("%08x", $t);
$m = md5($secret.$f.$t_hex);

# generate link
printf('<a href="%s%s/%s%s">%s</a>', $uri_prefix, $m, $t_hex, $f, $f);

The config example above would map this url to the file /secret/path/secret-file.txt.

For more examples see mod_secdownload (lighttpd 1.4.x).

mod_memcached

mod_memcached caches content on memcached servers

lookup tries to find data associated with the key, and returns it as http body with status 200 if it finds something.
store stores a http body (generated by another backend) in memcached.

Caching always requires you to think about what you want; you cannot cache content that changes with every request!

So most of the time you probably want to set a TTL for the stored content in memcached; your users probably don’t need new content to be available the next second, perhaps 60 seconds is still good (obviously not true for a chat…).

The other way is to purge the keys in your dynamic backend; you can set the memcached content from your backend too, which probably is faster than memcached.store.

If the key is longer than 255 bytes or contains characters outside the range 0x21 - 0x7e we will use a hash of it instead (for now sha1, but that may change).

memcached.lookup (action)

searches the content in a memcached database

memcached.lookup (options, action-hit, action-miss);
options
A key-value table with the following entries:
server
socket address as string (default: 127.0.0.1:11211)
headers
(boolean, not supported yet) whether to lookup headers too. if false content-type determined by request.uri.path (default: false)
key
pattern for lookup key (default: "%{req.path}")
action-hit
action to run on cache hit (lookup was successful)
action-miss
action to run on cache miss (lookup was not successful)

memcached.store (action)

stores the generated response in a memcached database

memcached.store options;
options
A key-value table with the following entries:
server
socket address as string (default: 127.0.0.1:11211)
flags
(integer) flags for storing data (default 0)
ttl
ttl for storing (default 30; use 0 if you want to cache "forever")
maxsize
maximum size in bytes we want to store (default: 64*1024)
headers
(boolean, not supported yet) whether to store headers too (default: false)
key
pattern for store key (default: "%{req.path}")

Example

setup {
	module_load "mod_memcached";
}

memcached.lookup (["key" => "%{req.scheme}://%{req.host}%{req.path}"], {
	header.add "X-Memcached" => "Hit";
}, {
	header.add "X-Memcached" => "Miss";
	docroot "/var/www";
#	important: You need a content handler before memcached.store
	static;
	memcached.store ["key" => "%{req.scheme}://%{req.host}%{req.path}"];
});

Lua API

mod_memcached exports a Lua API to per-worker luaStates too (for use in lua.handler):

memcached.new(address) creates a new connection; a connection provides:

  • req = con:get(key, cb | vr)
  • req = con:set(key, value, cb | vr, [ttl])
  • con:setq(key, value, [ttl])

If a callback was given, the callback gets called with a response object; otherwise the response will be in req.response when ready.

The response object has:

  • code: 1 – Ok, 2 – Not stored, 3 – Exists, 4 – Not found, 5 – Error
  • error: error message
  • key: the lookup key
  • flags
  • ttl
  • cas
  • data

mod_openssl

mod_openssl listens on separate sockets for TLS connections (https) using OpenSSL

openssl (setup)

setup a TLS socket

openssl options;
options
A key-value table with the following entries:
listen
(mandatory) the socket address to listen on (same as "listen":plugin_core.html#plugin_core__setup_listen), can be specified more than once to setup multiple sockets with the same options
pemfile
(mandatory) file containing the private key, certificate and (optionally) intermediate certificates (the root certificate is usually not included)
ca-file
file containing the intermediate certificates
ciphers
OpenSSL ciphers string (default: "HIGH !aNULL !3DES +kEDH +kRSA !kSRP !kPSK")
dh-params
filename with generated dh-params (default: fixed 4096-bit parameters)
ecdh-curve
OpenSSL ecdh-curve name
options
list of OpenSSL options (default: NO_SSLv2, NO_SSLv3, CIPHER_SERVER_PREFERENCE, NO_COMPRESSION, SINGLE_DH_USE, SINGLE_ECDH_USE)
verify
enable client certificate verification (default: false)
verify-any
allow all CAs and self-signed certificates, for manual checking (default: false)
verify-depth
sets client verification depth (default: 1)
verify-require
abort clients failing verification (default: false)
client-ca-file
file containing client CA certificates (to verify client certificates)

For ciphers see OpenSSL ciphers string

For options see options. Explicitly specify the reverse flag by toggling the “NO_” prefix to override defaults.

Simple TLS on IPv4 and IPv6

setup {
	module_load "mod_openssl";
	openssl [
		"listen" => "0.0.0.0:443",
		"listen" => "[::]:443",
		"pemfile" => "/etc/certs/lighttpd.pem",
		"options" => ["ALL", "NO_TICKET"],
	];
}

TLS with client certificate verification

setup {
	module_load "mod_openssl";
	openssl (
		"listen" => "0.0.0.0:443",
		"listen" => "[::]:443",
		"pemfile" => "/etc/certs/lighttpd.pem",
		"client-ca-file" => "/etc/certs/myCA.pem",
		"verify" => true,
		"verify-require" => true
	);
}

TLS with any client certificate

setup {
	module_load "mod_openssl";
	openssl (
		"listen" => "0.0.0.0:443",
		"listen" => "[::]:443",
		"pemfile" => "/etc/certs/lighttpd.pem",
		"verify" => true,
		"verify-any" => true,
		"verify-depth" => 9
	);
}
openssl.setenv "client-cert";

openssl.setenv (action)

set SSL environment strings

openssl.setenv list;
list
list of subsets to export

Supported subsets:

  • “client” – set SSL_CLIENT_S_DN_ short-named entries
  • “client-cert” – set SSL_CLIENT_CERT to client certificate PEM
  • “server” – set SSL_SERVER_S_DN_ short-named entries
  • “server-cert” – set SSL_SERVER_CERT to server certificate PEM

mod_progress

mod_progress track connection progress (state) via a unique identifier

mod_progress lets you track connection progress (or rather state) using a lookup table in which connections are registered via a random unique identifier specified with the request.
It is most commonly used to implement progress bars for file uploads.

A request to the webserver is registered using the progress.track action and being tracked by a random unique identifier supplied with the X-Progress-Id querystring parameter.
From that moment on, other requests can fetch the state of the first request through the progress.show action specifying the X-Progress-Id used earlier.
Even after a tracked request finished and the connection to the client is gone, requests can for a limited amount of time get the status of it to see it as “done”.

Check the source code there for further insight.

progress.ttl (setup)

Time to live in seconds for entries after a disconnect in the internal lookup table

progress.ttl ttl;
ttl
time to live in seconds (default: 30)

progress.methods (option)

Request methods to track

progress.methods methods;
Default value: ("POST")

Example

setup {
	module_load "mod_progress";
	progress.methods ("PUT", "POST");
}

progress.track (action)

tracks the current request if the X-Progress-ID querystring key is supplied

progress.track;

progress.show (action)

returns state information about the request tracked with the ID specified by the X-Progress-ID querystring parameter

progress.show format;
format
(optional) output format, one of "legacy", "json" or "jsonp". Defaults to "json".

Output formats:

  • legacy: new Object({"state": "running"", "received": 123456, "sent": 0, "request_size": 200000, "response_size": 0})
  • json: {"state": "running", "received": 123456, "sent": 0, "request_size": 200000, "response_size": 0}
  • jsonp: progress({"state": "running", "received": 123456, "sent": 0, "request_size": 200000, "response_size": 0})
    The function name (default “progress”) can be altered by supplying a X-Progress-Callback querystring parameter.

The JSON object can contain the following members:

  • state: One of "unknown", "running", "done" or "error".
  • received: Bytes received by lighty or uploaded by the client.
  • request_size: Total size of request or uploaded file as specified via the Content-Length request header.
  • sent: Bytes sent by lighty or downloaded by the client.
  • response_size: Total size of response. Attention: this might grow over time in case of streaming from a backend.
  • status: HTTP status code of response.

received, request_size, sent and response_size are only available if state is "running" or "done". status is only available if state is "error".

Example

setup {
	module_load "mod_progress";
}

if req.path == "/upload.php" { progress.track; }
if req.path == "/progress" { progress.show; }

progress.debug (option)

enable debug output

progress.debug value;
Default value: false

mod_proxy

mod_proxy connect to HTTP backends for generating response content

proxy (action)

connect to HTTP backend

proxy socket;
socket
socket to connect to, either "ip:port" or "unix:/path"

proxy uses request.raw_path for the URL (including the query string) to send to the backend.

Example

setup {
	module_load "mod_proxy";
}

proxy "127.0.0.1:8080";

mod_redirect

mod_redirect redirect clients by sending a http status code 301 plus Location header

It supports matching regular expressions and substitution with captured substrings as well as other placeholders.

redirect (action)

redirect clients

redirect rule;
rule
a simple target string or one rule, mapping a regular expression to a target string, or a list of rules.
  • The target string is a pattern.
  • The regular expressions are used to match the request path (always starts with “/”!)
  • If a list of rules is given, redirect stops on the first match.
  • By default the target string is interpreted as absolute uri; if it starts with ‘?’ it will replace the query string, if it starts with ‘/’ it will replace the current path, and if it starts with ‘./’ it is interpreted relative to the current “directory” in the path.

Example: redirect always (http to https)

setup {
	module_load "mod_redirect";
}
if request.scheme == "http" {
	if request.query == "" {
		redirect "https://%{request.host}%{enc:request.path}";
	} else {
		redirect "https://%{request.host}%{enc:request.path}?%{request.query}";
	}
}

Example: redirect always (prepend www)

setup {
	module_load "mod_redirect";
}
if request.host !~ "^www\.(.*)$" {
	if request.query == "" {
		redirect "http://www.%{request.host}%{enc:request.path}";
	} else {
		redirect "http://www.%{request.host}%{enc:request.path}?%{request.query}";
	}
}

Example: redirect if match

setup {
	module_load "mod_redirect";
}
redirect "^/old_url$" => "http://new.example.tld/url"

Example

redirect all non www. requests. for example: foo.tld/bar?x=y to www.foo.tld/bar?x=y

if request.host !~ "^www\.(.*)$" {
	redirect "." => "http://www.%1/$0?%{request.query}";
}

redirect.debug (option)

enable debug output

redirect.debug value;
Default value: false

mod_rewrite

mod_rewrite modifies request path and querystring

It supports matching regular expressions and substitution with captured substrings as well as other placeholders.

If your rewrite target does not contain any question mark (?), then the querystring will not be altered.
If it does contain ? the querystring will be overwritten with the part after the ?. To append the original querystring, use %{request.query}.

IMPORTANT: rewrite only changes the url, not the physical filename that it got mapped to by docroot or alias actions. So you need your docroot and alias actions after the rewrite.
If you have conditional rewrites like if !phys.is_file { rewrite ... } you need docroot/alias both before (so it can check for the physical file) and after it (to build the new physical path).

rewrite (action)

modify request path and querystring

rewrite rule;
rule
a simple target string or one rule, mapping a regular expression to a target string, or a list of rules.
  • The target string is a pattern.
  • The regular expressions are used to match the request path (always starts with “/” and does not include the query string!)
  • If a list of rules is given, rewrite stops on the first match.
  • Replaces the query string iff the target string contains an ?

Example: rewrite always

setup {
	module_load "mod_rewrite";
}
rewrite "/new/path";

Example: rewrite if match

setup {
	module_load "mod_rewrite";
}
rewrite "regex" => "/new/path";

Example: rewrite on first match

setup {
	module_load "mod_rewrite";
}
rewrite ("regex1" => "/new/path1", ..., "regexN" => "/new/pathN");

Example: pretty urls

Note: you really should move the logic for such rewrites into your application, and just rewrite all pretty urls to your index.php without putting the actions into the querystring.

setup {
	module_load "mod_rewrite";
}

# bad: routing login in webserver config:
rewrite (
	"^/article/(\d+)/.*$" => "/article.php?id=$1",
	"^/download/(\d+)/(.*)$" => "/download.php?fileid=$1&filename=$2"
);
rewrite "^/user/(.+)$" => "/user.php?name=$1";

# good: handle it in the backend (easier to port the config)
rewrite "^/(article|download|user)(/|$)" => "/index.php";

rewrite_raw (action)

modify request path and querystring, matching and writing raw path

rewrite_raw rule;
rule
a simple target string or one rule, mapping a regular expression to a target string, or a list of rules.

Similar to rewrite, but matches the raw path (i.e. the path before URL decoding and sanitizing) and the result is decoded again.

rewrite writes the result to request.path and possibly request.query and uses URL encoding to generate request.raw_path from those.
rewrite_raw writes request.raw_path and decodes it into request.path and request.query; this means the query string is always overwritten.
In both cases request.path gets simplified afterwards.

rewrite.debug (option)

enable debug output

rewrite.debug value;
Default value: false

mod_scgi

mod_scgi connect to SCGI backends for generating response content

scgi (action)

connect to SCGI backend

scgi socket;
socket
socket to connect to, either "ip:port" or "unix:/path"

Example

setup {
	module_load "mod_scgi";
}

if req.path =^ "/RPC2" {
	scgi "127.0.0.1:5000";
}

mod_status

mod_status displays a page with internal statistics like amount of requests (total or per second), active connections etc.

status.css (option)

defines the stylesheet to use. available: unset (default), "blue" or any url you wish

status.css url;
Default value: not set

Example

status.css = "blue";

status.info (action)

returns the status page to the client

status.info mode;
mode
(optional) "short"

The “short” mode removes connection and runtime details (recommended for “public” status).

The status page accepts the following query-string parameters:

  • ?mode=runtime: shows the runtime details
  • format=plain: shows the “short” stats in plain text format

Example

If /server-status is requested, a page with lighttpd statistics is displayed.

setup {
	module_load "mod_status";
}

if req.path == "/server-status" {
	status.info;
}

mod_throttle

mod_throttle limits outgoing bandwidth usage

All rates are in bytes/sec. The magazines are filled up in fixed intervals (compile time constant; defaults to 200ms).

io.throttle (action)

set the outgoing throttle limits for current connection

io.throttle (rate, burst);
rate
bytes/sec limit
burst
optional, defaults to 2*rate

burst is the initial and maximum value for the magazine; doing IO drains the magazine, which fills up again over time with the specified rate.

io.throttle_pool (action)

adds the current connection to a throttle pool for outgoing limits

io.throttle_pool rate;
rate
bytes/sec limit

all connections in the same pool are limited as whole. Each io.throttle_pool action creates its own pool.

Example

Using the same pool in more than one place:

setup {
	module_load "mod_throttle";
}
downloadLimit = {
	io.throttle_pool 1mbyte;
}
# now use it wherever you need it...
downloadLimit;

io.throttle_ip (action)

adds the current connection to an IP-based throttle pool for outgoing limits

io.throttle_ip rate;
rate
bytes/sec limit

all connections from the same IP address in the same pool are limited as whole. Each io.throttle_ip action creates its own pool.

Example

Using the same pool in more than one place:

setup {
	module_load "mod_throttle";
}
downloadLimit = {
	io.throttle_ip 200kbyte;
}
# now use it wherever you need it...
downloadLimit;

mod_userdir

mod_userdir allows you to have user-specific document roots being accessed through http://domain/~user/

The document root can be built by using the home directory of a user which is specified by /~username/ at the beginning of the request path.
Alternatively, mod_userdir can build the docroot from a pattern similar to vhost.pattern but using the username instead of the hostname.

userdir (action)

builds the document root by replacing certain placeholders in path with (parts of) the username.

userdir path;
path
the path to build the document root with

If path does not start with a slash (/), then the document root is prepended with the home directory of the given user as specified in /etc/passwd.
Otherwise the path specifies the absolute docroot to be used.

Placeholders are:

  • * is replaced by the complete username
  • $1-$9 are replaced by the n-th letter of the username, e.g. $2 is the second letter

Examples:

Request for http://host/~lighty/foo.html (assuming “/home/lighty” is the home of the “lighty” user):

path docroot physicalpath
“public_html” /home/lighty/public_html/ /home/lighty/public_html/foo.html
“/usr/web/*/” /usr/web/lighty/ /usr/web/lighty/foo.html
“/usr/web” /usr/web/lighty/ /usr/web/lighty/foo.html
“/www/users/$1/$1$2/*/” /www/users/l/li/lighty/ /www/users/l/li/lighty/foo.html

Note: username “root” is not allowed for security reasons.

Example

setup {
	module_load "mod_userdir";
}

userdir "public_html";

mod_vhost

mod_vhost offers various ways to implement virtual webhosts

It can map hostnames to actions and offers multiple ways to do so.
These ways differ in the flexibility of mapping (what to map and what to map to) as well as performance.

vhost.map (action)

maps given hostnames to action blocks

vhost.map mapping;
mapping
key-value list with hostnames as keys and actions as values

vhost.map offers a fast (lookup through hash-table) and flexible mapping, but maps only exact hostnames (no pattern/regex matching). The server port is never considered part of the hostname. Use the key default (keyword, not as string) to specify a default action.

Example

vhost.map ["host1" => actionblock1, "host2" => actionblock2, ..., "hostN" => actionblockN, default => actionblock0];

vhost.map_regex (action)

maps matching hostname patterns to action blocks

vhost.map_regex mapping;
mapping
key-value list with regular expressions for hostnames as keys and actions as values

vhost.map_regex walks through the list in the given order and stops at the first match; if no regular expression matched it will use the default action (if specified).

Example

vhost.map_regex ["host1regex" => actionblock1, "host2regex" => actionblock2, ..., "hostNregex" => actionblockN, default => actionblock0];

vhost.debug (option)

enable debug output

vhost.debug value;
Default value: false

Example

Combining both actions is also possible.

What it does (for example):

  • if example.com or www.example.com is requested, the block named “examplesite” will be executed
  • if mydomain.tld is requested, the block named “mydomain” will be executed
  • if mydomain.com or www.mydomain.net is requested, the block named “mydomain” will be executed (through the regex matching)
  • if www.mydomain.tld or something entirely different is requested, the block named “defaultdom” will be executed
setup {
	module_load "mod_vhost";
}

examplesite = {
	#... more config here ...
};

mydomain = {
	#... more config here ...
};

defaultdom = {
	#... more config here ...
};

vhost.map [
	"example.com" => examplesite,
	"www.example.com" => examplesite,
	"mydomain.tld" => mydomain,
	default => {
		vhost.map_regex [
			"^(www\.)?mydomain\.(com|net|org)$" => mydomain,
			default => defaultdom
		];
	}
];